Add Auth interoperability library. (#1501)

* Add Auth interoperability library.

This allows other SDKs to retrieve the user ID and fetch a new
Auth token in a type-safe way through the Core container.

* Remove unnecessary import.

* Add missing copyright.

* Resolve comments.

- Remove unnecessary umbrella header.
- Move to version 1.0.
- Add tvOS support for app lifecycle changes.

* Move public interop headers to private.

* Add public_header_files too.

* Moved headers back into public.

* Shorten Interoperability and Interoperable to Interop.

* Add AuthInterop to travis linting.

* Fixed tag format in AuthInterop.podspec.

* Add AuthInterop path to Firestore Podfile.
This commit is contained in:
Ryan Wilson authored and GitHub committed 2018-07-11 17:40:25 -04:00
1 parent c6b4b03fff
commit 79f663d5ea
12 files changed
+233 -98

No files matched your search

+2
View File
@@ -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
+25
View File
@@ -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:
+1
View File
@@ -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];
+1
View File
@@ -4,6 +4,7 @@
use_frameworks!
pod 'FirebaseAuthInterop', :path => '../'
pod 'FirebaseCore', :path => '../'
pod 'GoogleUtilities', :path => '../'
+127 -95
View File
@@ -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];
[FIRComponentContainer registerAsComponentRegistrant:self];
[FIRApp registerAsConfigurable:self];
}
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
// 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;
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);
});
}];
});
// TODO: Remove this block once Firestore, Database, and Storage move to the new interop API.
app.getTokenImplementation = ^(BOOL forceRefresh, FIRTokenCallback callback) {
// 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;
}
+2 -2
View File
@@ -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.
+1 -1
View File
@@ -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;
+1
View File
@@ -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.");
+1
View File
@@ -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'
+29
View File
@@ -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
+1
View File
@@ -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 => '../../'
+42
View File
@@ -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 */