ソースを参照

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.
Ryan Wilson 7 年 前
コミット
79f663d5ea

+ 2 - 0
.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

+ 25 - 0
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:

+ 1 - 0
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];
 

+ 1 - 0
Example/Podfile

@@ -4,6 +4,7 @@
 
 use_frameworks!
 
+pod 'FirebaseAuthInterop', :path => '../'
 pod 'FirebaseCore', :path => '../'
 pod 'GoogleUtilities', :path => '../'
 

+ 127 - 95
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;
 }
 

+ 2 - 2
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.

+ 1 - 1
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;

+ 1 - 0
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.");

+ 1 - 0
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'

+ 29 - 0
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

+ 1 - 0
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 => '../../'

+ 42 - 0
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 */