diff options
-rw-r--r-- | .travis.yml | 2 | ||||
-rw-r--r-- | Example/Auth/Tests/FIRAuthTests.m | 25 | ||||
-rw-r--r-- | Example/Core/Tests/FIRAppTest.m | 1 | ||||
-rw-r--r-- | Example/Podfile | 1 | ||||
-rw-r--r-- | Firebase/Auth/Source/FIRAuth.m | 222 | ||||
-rw-r--r-- | Firebase/Auth/Source/FIRAuth_Internal.h | 4 | ||||
-rw-r--r-- | Firebase/Auth/Source/FIRUser.m | 2 | ||||
-rw-r--r-- | Firebase/Core/FIRApp.m | 1 | ||||
-rw-r--r-- | FirebaseAuth.podspec | 1 | ||||
-rw-r--r-- | FirebaseAuthInterop.podspec | 29 | ||||
-rw-r--r-- | Firestore/Example/Podfile | 1 | ||||
-rw-r--r-- | Interop/Auth/Public/FIRAuthInterop.h | 42 |
12 files changed, 233 insertions, 98 deletions
diff --git a/.travis.yml b/.travis.yml index 79903c7..59ba692 100644 --- a/.travis.yml +++ b/.travis.yml @@ -64,6 +64,7 @@ jobs: - ./scripts/if_changed.sh bundle exec pod lib lint GoogleUtilities.podspec - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseCore.podspec $ALT_SOURCES - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseAuth.podspec $ALT_SOURCES + - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseAuthInterop.podspec $ALT_SOURCES - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseDatabase.podspec $ALT_SOURCES - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseMessaging.podspec $ALT_SOURCES - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseStorage.podspec $ALT_SOURCES @@ -90,6 +91,7 @@ jobs: script: - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseCore.podspec --use-libraries $ALT_SOURCES - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseAuth.podspec --use-libraries $ALT_SOURCES + - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseAuthInterop.podspec --use-libraries $ALT_SOURCES - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseDatabase.podspec --use-libraries $ALT_SOURCES # The Protobuf dependency of FirebaseMessaging has warnings with --use-libraries - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseMessaging.podspec --use-libraries --allow-warnings $ALT_SOURCES diff --git a/Example/Auth/Tests/FIRAuthTests.m b/Example/Auth/Tests/FIRAuthTests.m index 6a047a4..c008bab 100644 --- a/Example/Auth/Tests/FIRAuthTests.m +++ b/Example/Auth/Tests/FIRAuthTests.m @@ -18,7 +18,11 @@ #import <XCTest/XCTest.h> +#import <FirebaseAuth/FirebaseAuth.h> +#import <FirebaseAuthInterop/FIRAuthInterop.h> #import <FirebaseCore/FIRAppInternal.h> +#import <FirebaseCore/FIRComponent.h> +#import <FirebaseCore/FIRComponentRegistrant.h> #import "FIRAdditionalUserInfo.h" #import "FIRAuth_Internal.h" @@ -221,6 +225,10 @@ static const NSTimeInterval kExpectationTimeout = 2; */ static const NSTimeInterval kWaitInterval = .5; +/** Category for FIRAuth to expose FIRComponentRegistrant conformance. */ +@interface FIRAuth () <FIRComponentRegistrant> +@end + /** @class FIRAuthTests @brief Tests for @c FIRAuth. */ @@ -362,6 +370,8 @@ static const NSTimeInterval kWaitInterval = .5; @brief Verifies that FIRApp's getUIDImplementation is correctly set by FIRAuth. */ - (void)testGetUID { + // TODO: Remove this test once Firestore, Database, and Storage move over to the new Auth interop + // library. FIRApp *app = [FIRApp defaultApp]; XCTAssertNotNil(app.getUIDImplementation); [[FIRAuth auth] signOut:NULL]; @@ -2221,6 +2231,21 @@ static const NSTimeInterval kWaitInterval = .5; } #endif +#pragma mark - Interoperability Tests + +/** @fn testComponentsBeingRegistered + @brief Tests that Auth provides the necessary components for interoperability with other SDKs. + */ +- (void)testComponentsBeingRegistered { + // Verify that the components are registered properly. Check the count, because any time a new + // component is added it should be added to the test suite as well. + NSArray<FIRComponent *> *components = [FIRAuth componentsToRegister]; + XCTAssertTrue(components.count == 1); + + FIRComponent *component = [components firstObject]; + XCTAssert(component.protocol == @protocol(FIRAuthInterop)); +} + #pragma mark - Helpers /** @fn mockSecureTokenResponseWithError: diff --git a/Example/Core/Tests/FIRAppTest.m b/Example/Core/Tests/FIRAppTest.m index 656f046..d88ed66 100644 --- a/Example/Core/Tests/FIRAppTest.m +++ b/Example/Core/Tests/FIRAppTest.m @@ -754,6 +754,7 @@ NSString *const kFIRTestAppName2 = @"test-app-name-2"; #pragma mark - Internal Methods +// TODO: Remove this test once the `getUIDImplementation` block doesn't need to be set in Core. - (void)testAuthGetUID { [FIRApp configure]; diff --git a/Example/Podfile b/Example/Podfile index dc3acd0..373471f 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -4,6 +4,7 @@ use_frameworks! +pod 'FirebaseAuthInterop', :path => '../' pod 'FirebaseCore', :path => '../' pod 'GoogleUtilities', :path => '../' diff --git a/Firebase/Auth/Source/FIRAuth.m b/Firebase/Auth/Source/FIRAuth.m index 691c6ce..b353ebb 100644 --- a/Firebase/Auth/Source/FIRAuth.m +++ b/Firebase/Auth/Source/FIRAuth.m @@ -18,8 +18,17 @@ #import "FIRAuth_Internal.h" +#if __has_include(<UIKit/UIKit.h>) +#import <UIKit/UIKit.h> +#endif + +#import <FirebaseAuthInterop/FIRAuthInterop.h> #import <FirebaseCore/FIRAppAssociationRegistration.h> #import <FirebaseCore/FIRAppInternal.h> +#import <FirebaseCore/FIRComponent.h> +#import <FirebaseCore/FIRComponentContainer.h> +#import <FirebaseCore/FIRComponentRegistrant.h> +#import <FirebaseCore/FIRCoreConfigurable.h> #import <FirebaseCore/FIRLogger.h> #import <FirebaseCore/FIROptions.h> #import <GoogleUtilities/GULAppEnvironmentUtil.h> @@ -63,7 +72,6 @@ #import "FIRVerifyPhoneNumberResponse.h" #if TARGET_OS_IOS -#import <UIKit/UIKit.h> #import "FIRAuthAPNSToken.h" #import "FIRAuthAPNSTokenManager.h" #import "FIRAuthAppCredentialManager.h" @@ -218,9 +226,9 @@ static NSMutableDictionary *gKeychainServiceNameForAppName; #pragma mark - FIRAuth #if TARGET_OS_IOS -@interface FIRAuth () <FIRAuthAppDelegateHandler> +@interface FIRAuth () <FIRAuthAppDelegateHandler, FIRAuthInterop, FIRComponentRegistrant, FIRCoreConfigurable, FIRComponentLifecycleMaintainer> #else -@interface FIRAuth () +@interface FIRAuth () <FIRAuthInterop, FIRComponentRegistrant, FIRCoreConfigurable, FIRComponentLifecycleMaintainer> #endif /** @property firebaseAppId @@ -300,42 +308,12 @@ static NSMutableDictionary *gKeychainServiceNameForAppName; } + (void)load { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - gKeychainServiceNameForAppName = [[NSMutableDictionary alloc] init]; - - NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; + [FIRComponentContainer registerAsComponentRegistrant:self]; + [FIRApp registerAsConfigurable:self]; +} - // Ensures the @c FIRAuth instance for a given app gets loaded as soon as the app is ready. - [defaultCenter addObserverForName:kFIRAppReadyToConfigureSDKNotification - object:[FIRApp class] - queue:nil - usingBlock:^(NSNotification *notification) { - [FIRAuth authWithApp:[FIRApp appNamed:notification.userInfo[kFIRAppNameKey]]]; - }]; - // Ensures the saved user is cleared when the app is deleted. - [defaultCenter addObserverForName:kFIRAppDeleteNotification - object:[FIRApp class] - queue:nil - usingBlock:^(NSNotification *notification) { - dispatch_async(FIRAuthGlobalWorkQueue(), ^{ - // This doesn't stop any request already issued, see b/27704535 . - NSString *appName = notification.userInfo[kFIRAppNameKey]; - NSString *keychainServiceName = [FIRAuth keychainServiceNameForAppName:appName]; - if (keychainServiceName) { - [self deleteKeychainServiceNameForAppName:appName]; - FIRAuthKeychain *keychain = [[FIRAuthKeychain alloc] initWithService:keychainServiceName]; - NSString *userKey = [NSString stringWithFormat:kUserKey, appName]; - [keychain removeDataForKey:userKey error:NULL]; - } - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] - postNotificationName:FIRAuthStateDidChangeNotification - object:nil]; - }); - }); - }]; - }); ++ (void)initialize { + gKeychainServiceNameForAppName = [[NSMutableDictionary alloc] init]; } + (FIRAuth *)auth { @@ -352,11 +330,10 @@ static NSMutableDictionary *gKeychainServiceNameForAppName; } + (FIRAuth *)authWithApp:(FIRApp *)app { - return [FIRAppAssociationRegistration registeredObjectWithHost:app - key:NSStringFromClass(self) - creationBlock:^FIRAuth *_Nullable() { - return [[FIRAuth alloc] initWithApp:app]; - }]; + // Get the instance of Auth from the container, which will create or return the cached instance + // associated with this app. + id<FIRAuthInterop> auth = FIR_COMPONENT(FIRAuthInterop, app.container); + return (FIRAuth *)auth; } - (instancetype)initWithApp:(FIRApp *)app { @@ -365,62 +342,19 @@ static NSMutableDictionary *gKeychainServiceNameForAppName; if (self) { _app = app; __weak FIRAuth *weakSelf = self; + + // TODO: Remove this block once Firestore, Database, and Storage move to the new interop API. app.getTokenImplementation = ^(BOOL forceRefresh, FIRTokenCallback callback) { - dispatch_async(FIRAuthGlobalWorkQueue(), ^{ - FIRAuth *strongSelf = weakSelf; - // Enable token auto-refresh if not aleady enabled. - if (strongSelf && !strongSelf->_autoRefreshTokens) { - FIRLogInfo(kFIRLoggerAuth, @"I-AUT000002", @"Token auto-refresh enabled."); - strongSelf->_autoRefreshTokens = YES; - [strongSelf scheduleAutoTokenRefresh]; - - #if TARGET_OS_IOS // TODO: Is a similar mechanism needed on macOS? - strongSelf->_applicationDidBecomeActiveObserver = [[NSNotificationCenter defaultCenter] - addObserverForName:UIApplicationDidBecomeActiveNotification - object:nil - queue:nil - usingBlock:^(NSNotification *notification) { - FIRAuth *strongSelf = weakSelf; - if (strongSelf) { - strongSelf->_isAppInBackground = NO; - if (!strongSelf->_autoRefreshScheduled) { - [weakSelf scheduleAutoTokenRefresh]; - } - } - }]; - strongSelf->_applicationDidEnterBackgroundObserver = [[NSNotificationCenter defaultCenter] - addObserverForName:UIApplicationDidEnterBackgroundNotification - object:nil - queue:nil - usingBlock:^(NSNotification *notification) { - FIRAuth *strongSelf = weakSelf; - if (strongSelf) { - strongSelf->_isAppInBackground = YES; - } - }]; - #endif - } - // Call back with 'nil' if there is no current user. - if (!strongSelf || !strongSelf->_currentUser) { - dispatch_async(dispatch_get_main_queue(), ^{ - callback(nil, nil); - }); - return; - } - // Call back with current user token. - [strongSelf->_currentUser internalGetTokenForcingRefresh:forceRefresh - callback:^(NSString *_Nullable token, - NSError *_Nullable error) { - dispatch_async(dispatch_get_main_queue(), ^{ - callback(token, error); - }); - }]; - }); + // In the meantime, redirect call to the interop method that provides this functionality. + __weak FIRAuth *weakSelf = self; + [weakSelf getTokenForcingRefresh:forceRefresh withCallback:callback]; }; + + // TODO: Remove this block once Firestore, Database, and Storage move to the new interop API. app.getUIDImplementation = ^NSString *_Nullable() { __block NSString *uid; dispatch_sync(FIRAuthGlobalWorkQueue(), ^{ - uid = [weakSelf getUID]; + uid = [weakSelf getUserID]; }); return uid; }; @@ -1878,7 +1812,105 @@ static NSDictionary<NSString *, NSString *> *FIRAuthParseURL(NSString *urlString return YES; } -- (nullable NSString *)getUID { +#pragma mark - Interoperability + ++ (nonnull NSArray<FIRComponent *> *)componentsToRegister { + FIRComponentCreationBlock authCreationBlock = + ^id _Nullable(FIRComponentContainer *_Nonnull container, BOOL *_Nonnull isCacheable) { + *isCacheable = YES; + return [[FIRAuth alloc] initWithApp:container.app]; + }; + FIRComponent *authInterop = [FIRComponent componentWithProtocol:@protocol(FIRAuthInterop) + creationBlock:authCreationBlock]; + return @[authInterop]; +} + +#pragma mark - FIRCoreConfigurable + ++ (void)configureWithApp:(nonnull FIRApp *)app { + // TODO: Evaluate what actually needs to be configured here instead of initializing a full + // instance. + // Ensures the @c FIRAuth instance for a given app gets loaded as soon as the app is ready. + [FIRAuth authWithApp:app]; +} + +#pragma mark - FIRComponentLifecycleMaintainer + +- (void)appWillBeDeleted:(nonnull FIRApp *)app { + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + // This doesn't stop any request already issued, see b/27704535 . + NSString *keychainServiceName = [FIRAuth keychainServiceNameForAppName:app.name]; + if (keychainServiceName) { + [[self class] deleteKeychainServiceNameForAppName:app.name]; + FIRAuthKeychain *keychain = [[FIRAuthKeychain alloc] initWithService:keychainServiceName]; + NSString *userKey = [NSString stringWithFormat:kUserKey, app.name]; + [keychain removeDataForKey:userKey error:NULL]; + } + dispatch_async(dispatch_get_main_queue(), ^{ + // TODO: Move over to fire an event instead, once ready. + [[NSNotificationCenter defaultCenter] postNotificationName:FIRAuthStateDidChangeNotification + object:nil]; + }); + }); +} + +#pragma mark - FIRAuthInterop + +- (void)getTokenForcingRefresh:(BOOL)forceRefresh withCallback:(FIRTokenCallback)callback { + __weak FIRAuth *weakSelf = self; + dispatch_async(FIRAuthGlobalWorkQueue(), ^{ + FIRAuth *strongSelf = weakSelf; + // Enable token auto-refresh if not aleady enabled. + if (strongSelf && !strongSelf->_autoRefreshTokens) { + FIRLogInfo(kFIRLoggerAuth, @"I-AUT000002", @"Token auto-refresh enabled."); + strongSelf->_autoRefreshTokens = YES; + [strongSelf scheduleAutoTokenRefresh]; + +#if TARGET_OS_IOS || TARGET_OS_TV // TODO: Is a similar mechanism needed on macOS? + strongSelf->_applicationDidBecomeActiveObserver = [[NSNotificationCenter defaultCenter] + addObserverForName:UIApplicationDidBecomeActiveNotification + object:nil + queue:nil + usingBlock:^(NSNotification *notification) { + FIRAuth *strongSelf = weakSelf; + if (strongSelf) { + strongSelf->_isAppInBackground = NO; + if (!strongSelf->_autoRefreshScheduled) { + [weakSelf scheduleAutoTokenRefresh]; + } + } + }]; + strongSelf->_applicationDidEnterBackgroundObserver = [[NSNotificationCenter defaultCenter] + addObserverForName:UIApplicationDidEnterBackgroundNotification + object:nil + queue:nil + usingBlock:^(NSNotification *notification) { + FIRAuth *strongSelf = weakSelf; + if (strongSelf) { + strongSelf->_isAppInBackground = YES; + } + }]; +#endif + } + // Call back with 'nil' if there is no current user. + if (!strongSelf || !strongSelf->_currentUser) { + dispatch_async(dispatch_get_main_queue(), ^{ + callback(nil, nil); + }); + return; + } + // Call back with current user token. + [strongSelf->_currentUser internalGetTokenForcingRefresh:forceRefresh + callback:^(NSString *_Nullable token, + NSError *_Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + callback(token, error); + }); + }]; + }); +} + +- (nullable NSString *)getUserID { return _currentUser.uid; } diff --git a/Firebase/Auth/Source/FIRAuth_Internal.h b/Firebase/Auth/Source/FIRAuth_Internal.h index 4d87a08..519ece3 100644 --- a/Firebase/Auth/Source/FIRAuth_Internal.h +++ b/Firebase/Auth/Source/FIRAuth_Internal.h @@ -69,11 +69,11 @@ NS_ASSUME_NONNULL_BEGIN - (nullable instancetype)initWithAPIKey:(NSString *)APIKey appName:(NSString *)appName NS_DESIGNATED_INITIALIZER; -/** @fn getUID +/** @fn getUserID @brief Gets the identifier of the current user, if any. @return The identifier of the current user, or nil if there is no current user. */ -- (nullable NSString *)getUID; +- (nullable NSString *)getUserID; /** @fn updateKeychainWithUser:error: @brief Updates the keychain for the given user. diff --git a/Firebase/Auth/Source/FIRUser.m b/Firebase/Auth/Source/FIRUser.m index ad0b1d4..f3832a7 100644 --- a/Firebase/Auth/Source/FIRUser.m +++ b/Firebase/Auth/Source/FIRUser.m @@ -766,7 +766,7 @@ static void callInMainThreadWithAuthDataResultAndError( callInMainThreadWithAuthDataResultAndError(completion, authResult, error); return; } - if (![authResult.user.uid isEqual:[self->_auth getUID]]) { + if (![authResult.user.uid isEqual:[self->_auth getUserID]]) { callInMainThreadWithAuthDataResultAndError(completion, authResult, [FIRAuthErrorUtils userMismatchError]); return; diff --git a/Firebase/Core/FIRApp.m b/Firebase/Core/FIRApp.m index 9f698cf..5fca127 100644 --- a/Firebase/Core/FIRApp.m +++ b/Firebase/Core/FIRApp.m @@ -517,6 +517,7 @@ static NSMutableDictionary *sLibraryVersions; } } +// TODO: Remove once SDKs transition to Auth interop library. - (nullable NSString *)getUID { if (!_getUIDImplementation) { FIRLogWarning(kFIRLoggerCore, @"I-COR000025", @"FIRAuth getUID implementation wasn't set."); diff --git a/FirebaseAuth.podspec b/FirebaseAuth.podspec index 47e211b..b08d830 100644 --- a/FirebaseAuth.podspec +++ b/FirebaseAuth.podspec @@ -61,6 +61,7 @@ supports email and password accounts, as well as several 3rd party authenticatio } s.framework = 'SafariServices' s.framework = 'Security' + s.dependency 'FirebaseAuthInterop', '~> 1.0' s.dependency 'FirebaseCore', '~> 5.0' s.dependency 'GoogleUtilities/Environment', '~> 5.0' s.dependency 'GTMSessionFetcher/Core', '~> 1.1' diff --git a/FirebaseAuthInterop.podspec b/FirebaseAuthInterop.podspec new file mode 100644 index 0000000..598db1a --- /dev/null +++ b/FirebaseAuthInterop.podspec @@ -0,0 +1,29 @@ +Pod::Spec.new do |s| + s.name = 'FirebaseAuthInterop' + s.version = '1.0.0' + s.summary = 'Interfaces that allow other Firebase SDKs to use Auth functionality.' + + s.description = <<-DESC + INTERNAL ONLY: A set of protocols that other Firebase SDKs can use to interoperate with Auth in a + safe and reliable manner. + DESC + + s.homepage = 'https://firebase.google.com' + s.license = { :type => 'Apache', :file => 'LICENSE' } + s.authors = 'Google, Inc.' + + # NOTE that these should not be used externally, this is for Firebase pods to depend on each + # other. + s.source = { + :git => 'https://github.com/firebase/firebase-ios-sdk.git', +# TODO: Remove this once it is merged in master and ready for release in M30. +# :tag => 'AuthInterop-' + s.version.to_s + :tag => 'pre-AuthInterop-' + s.version.to_s + } + s.social_media_url = 'https://twitter.com/Firebase' + s.ios.deployment_target = '8.0' + s.osx.deployment_target = '10.10' + s.tvos.deployment_target = '10.0' + s.source_files = 'Interop/Auth/**/*.h' + s.public_header_files = 'Interop/Auth/Public/*.h' +end diff --git a/Firestore/Example/Podfile b/Firestore/Example/Podfile index 86cbd82..cde5780 100644 --- a/Firestore/Example/Podfile +++ b/Firestore/Example/Podfile @@ -13,6 +13,7 @@ target 'Firestore_Example_iOS' do pod 'Firebase/CoreOnly', '5.4.0' pod 'FirebaseAuth', :path => '../../' + pod 'FirebaseAuthInterop', :path => '../../' pod 'FirebaseCore', :path => '../../' pod 'GoogleUtilities', :path => '../../' pod 'FirebaseFirestore', :path => '../../' diff --git a/Interop/Auth/Public/FIRAuthInterop.h b/Interop/Auth/Public/FIRAuthInterop.h new file mode 100644 index 0000000..5c365a3 --- /dev/null +++ b/Interop/Auth/Public/FIRAuthInterop.h @@ -0,0 +1,42 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRAuthInterop_h +#define FIRAuthInterop_h + +NS_ASSUME_NONNULL_BEGIN + +/** @typedef FIRTokenCallback + @brief The type of block which gets called when a token is ready. + */ +typedef void (^FIRTokenCallback)(NSString *_Nullable token, NSError *_Nullable error) + NS_SWIFT_NAME(TokenCallback); + +/// Common methods for Auth interoperability. +NS_SWIFT_NAME(AuthInterop) +@protocol FIRAuthInterop + +/// Retrieves the Firebase authentication token, possibly refreshing it if it has expired. +- (void)getTokenForcingRefresh:(BOOL)forceRefresh withCallback:(FIRTokenCallback)callback; + +/// Get the current Auth user's UID. Returns nil if there is no user signed in. +- (nullable NSString *)getUserID; + +@end + +NS_ASSUME_NONNULL_END + +#endif /* FIRAuthInterop_h */ |