Quellcode durchsuchen

Implement -signInWithConfiguration:presentingViewController:callback:

Peter Andrews vor 4 Jahren
Ursprung
Commit
8eac6fd908

+ 90 - 152
GoogleSignIn/Sources/GIDSignIn.m

@@ -17,6 +17,7 @@
 #import "GoogleSignIn/Sources/GIDSignIn_Private.h"
 
 #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDAuthentication.h"
+#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDConfiguration.h"
 #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h"
 #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDProfileData.h"
 
@@ -141,10 +142,8 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
   // set when a sign-in flow is begun via |signInWithOptions:| when the options passed don't
   // represent a sign in continuation.
   GIDSignInInternalOptions *_currentOptions;
-  // Scheme information for this sign-in instance.
-  GIDSignInCallbackSchemes *_schemes;
   // AppAuth configuration object.
-  OIDServiceConfiguration *_configuration;
+  OIDServiceConfiguration *_appAuthConfiguration;
   // AppAuth external user-agent session state.
   id<OIDExternalUserAgentSession> _currentAuthorizationFlow;
 }
@@ -194,8 +193,14 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
 
 // Authenticates the user by first searching the keychain, then attempting to retrieve the refresh
 // token from a Google Sign In app, and finally through the standard OAuth 2.0 web flow.
-- (void)signIn {
-  [self signInWithOptions:[GIDSignInInternalOptions defaultOptions]];
+- (void)signInWithConfiguration:(GIDConfiguration *)configuration
+       presentingViewController:(UIViewController *)presentingViewController
+                       callback:(GIDSignInCallback)callback {
+  GIDSignInInternalOptions *options =
+      [GIDSignInInternalOptions defaultOptionsWithConfiguration:configuration
+                                       presentingViewController:presentingViewController
+                                                       callback:callback];
+  [self signInWithOptions:options];
 }
 
 - (void)signOut {
@@ -264,44 +269,11 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
   return sharedInstance;
 }
 
-- (void)setClientID:(nullable NSString *)clientID {
-  if (![_clientID isEqualToString:clientID]) {
-    [self willChangeValueForKey:NSStringFromSelector(@selector(clientID))];
-    _clientID = [clientID copy];
-    _schemes = [[GIDSignInCallbackSchemes alloc] initWithClientIdentifier:_clientID];
-    [self didChangeValueForKey:NSStringFromSelector(@selector(clientID))];
-  }
-}
-
-- (void)setScopes:(nullable NSArray<NSString *> *)scopes {
-  scopes = [scopes sortedArrayUsingSelector:@selector(compare:)];
-  if (![_scopes isEqualToArray:scopes]) {
-    _scopes = [[NSArray alloc] initWithArray:scopes copyItems:YES];
-  }
-}
-
-- (void)setShouldFetchBasicProfile:(BOOL)shouldFetchBasicProfile {
-  shouldFetchBasicProfile = !!shouldFetchBasicProfile;
-  if (_shouldFetchBasicProfile != shouldFetchBasicProfile) {
-    _shouldFetchBasicProfile = shouldFetchBasicProfile;
-  }
-}
-
-- (void)setHostedDomain:(nullable NSString *)hostedDomain {
-  if (!(_hostedDomain == hostedDomain || [_hostedDomain isEqualToString:hostedDomain])) {
-    _hostedDomain = [hostedDomain copy];
-  }
-}
-
 #pragma mark - Private methods
 
 - (id)initPrivate {
   self = [super init];
   if (self) {
-    // Default scope settings.
-    _scopes = @[];
-    _shouldFetchBasicProfile = YES;
-
     // Check to see if the 3P app is being run for the first time after a fresh install.
     BOOL isFreshInstall = [self isFreshInstall];
 
@@ -314,12 +286,12 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
         [GIDSignInPreferences googleAuthorizationServer]];
     NSString *tokenEndpointURL = [NSString stringWithFormat:kTokenURLTemplate,
         [GIDSignInPreferences googleTokenServer]];
-    _configuration = [[OIDServiceConfiguration alloc]
+    _appAuthConfiguration = [[OIDServiceConfiguration alloc]
         initWithAuthorizationEndpoint:[NSURL URLWithString:authorizationEnpointURL]
                         tokenEndpoint:[NSURL URLWithString:tokenEndpointURL]];
 
     // Perform migration of auth state from old versions of the SDK if needed.
-    [GIDAuthStateMigration migrateIfNeededWithTokenURL:_configuration.tokenEndpoint
+    [GIDAuthStateMigration migrateIfNeededWithTokenURL:_appAuthConfiguration.tokenEndpoint
                                           callbackPath:kBrowserCallbackPath
                                           keychainName:kGTMAppAuthKeychainName
                                         isFreshInstall:isFreshInstall];
@@ -336,17 +308,17 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
     _currentOptions = options;
   }
 
-  // Explicitly throw exception for missing client ID (and scopes) here. This must come before
-  // scheme check because schemes rely on reverse client IDs.
-  [self assertValidParameters];
-
   if (options.interactive) {
+    // Explicitly throw exception for missing client ID here. This must come before
+    // scheme check because schemes rely on reverse client IDs.
+    [self assertValidParameters];
+
     [self assertValidPresentingViewController];
-  }
 
-  // If the application does not support the required URL schemes tell the developer so.
-  if (options.interactive) {
-    NSArray<NSString *> *unsupportedSchemes = [_schemes unsupportedSchemes];
+    // If the application does not support the required URL schemes tell the developer so.
+    GIDSignInCallbackSchemes *schemes =
+        [[GIDSignInCallbackSchemes alloc] initWithClientIdentifier:options.configuration.clientID];
+    NSArray<NSString *> *unsupportedSchemes = [schemes unsupportedSchemes];
     if (unsupportedSchemes.count != 0) {
       // NOLINTNEXTLINE(google-objc-avoid-throwing-exception)
       [NSException raise:NSInvalidArgumentException
@@ -361,11 +333,7 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
       if (error) {
         [self authenticateWithOptions:options];
       } else {
-        if (options.callback) {
-          options.callback(_currentUser, nil);
-        } else {
-          [_delegate signIn:self didSignInForUser:_currentUser withError:nil];
-        }
+        options.callback(_currentUser, nil);
       }
     }];
   } else {
@@ -376,31 +344,36 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
 # pragma mark - Authentication flow
 
 - (void)authenticateInteractivelyWithOptions:(GIDSignInInternalOptions *)options {
+  GIDSignInCallbackSchemes *schemes =
+      [[GIDSignInCallbackSchemes alloc] initWithClientIdentifier:options.configuration.clientID];
+  NSURL *redirectURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@:%@",
+                                             [schemes clientIdentifierScheme],
+                                             kBrowserCallbackPath]];
   NSString *emmSupport = [[self class] isOperatingSystemAtLeast9] ? kEMMVersion : nil;
   NSMutableDictionary<NSString *, NSString *> *additionalParameters = [@{} mutableCopy];
-  if (_serverClientID) {
-    additionalParameters[kAudienceParameter] = _serverClientID;
+  if (options.configuration.serverClientID) {
+    additionalParameters[kAudienceParameter] = options.configuration.serverClientID;
   }
-  if (_loginHint) {
-    additionalParameters[@"login_hint"] = _loginHint;
+  if (options.configuration.loginHint) {
+    additionalParameters[@"login_hint"] = options.configuration.loginHint;
   }
-  if (_hostedDomain) {
-    additionalParameters[@"hd"] = _hostedDomain;
+  if (options.configuration.hostedDomain) {
+    additionalParameters[@"hd"] = options.configuration.hostedDomain;
   }
   [additionalParameters addEntriesFromDictionary:
       [GIDAuthentication parametersWithParameters:options.extraParams
                                        emmSupport:emmSupport
                            isPasscodeInfoRequired:NO]];
   OIDAuthorizationRequest *request =
-      [[OIDAuthorizationRequest alloc] initWithConfiguration:_configuration
-                                                    clientId:_clientID
-                                                      scopes:[self adjustedScopes]
-                                                 redirectURL:[self redirectURI]
+      [[OIDAuthorizationRequest alloc] initWithConfiguration:_appAuthConfiguration
+                                                    clientId:options.configuration.clientID
+                                                      scopes:options.scopes
+                                                 redirectURL:redirectURL
                                                 responseType:OIDResponseTypeCode
                                         additionalParameters:additionalParameters];
   _currentAuthorizationFlow = [OIDAuthorizationService
       presentAuthorizationRequest:request
-         presentingViewController:_presentingViewController
+         presentingViewController:options.presentingViewController
                          callback:^(OIDAuthorizationResponse *_Nullable authorizationResponse,
                                     NSError *_Nullable error) {
     GIDAuthFlow *authFlow = [[GIDAuthFlow alloc] init];
@@ -470,11 +443,7 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
     NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain
                                          code:kGIDSignInErrorCodeHasNoAuthInKeychain
                                      userInfo:nil];
-    if (options.callback) {
-      options.callback(nil, error);
-    } else {
-      [_delegate signIn:self didSignInForUser:nil withError:error];
-    }
+    options.callback(nil, error);
     return;
   }
 
@@ -501,11 +470,11 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
     return;
   }
   NSMutableDictionary<NSString *, NSString *> *additionalParameters = [@{} mutableCopy];
-  if (_serverClientID) {
-    additionalParameters[kAudienceParameter] = _serverClientID;
+  if (_currentOptions.configuration.serverClientID) {
+    additionalParameters[kAudienceParameter] = _currentOptions.configuration.serverClientID;
   }
-  if (_openIDRealm) {
-    additionalParameters[kOpenIDRealmParameter] = _openIDRealm;
+  if (_currentOptions.configuration.openIDRealm) {
+    additionalParameters[kOpenIDRealmParameter] = _currentOptions.configuration.openIDRealm;
   }
   NSDictionary<NSString *, NSObject *> *params =
       authState.lastAuthorizationResponse.additionalParameters;
@@ -585,50 +554,48 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
     OIDIDToken *idToken =
         [[OIDIDToken alloc] initWithIDTokenString:authState.lastTokenResponse.idToken];
     if (idToken) {
-      if (_shouldFetchBasicProfile) {
-        // If the picture and name fields are present in the ID token, use them, otherwise make
-        // a userinfo request to fetch them.
-        if (idToken.claims[kBasicProfilePictureKey] &&
-            idToken.claims[kBasicProfileNameKey] &&
-            idToken.claims[kBasicProfileGivenNameKey] &&
-            idToken.claims[kBasicProfileFamilyNameKey]) {
-          handlerAuthFlow.profileData = [[GIDProfileData alloc]
-              initWithEmail:idToken.claims[kBasicProfileEmailKey]
-                       name:idToken.claims[kBasicProfileNameKey]
-                  givenName:idToken.claims[kBasicProfileGivenNameKey]
-                 familyName:idToken.claims[kBasicProfileFamilyNameKey]
-                   imageURL:[NSURL URLWithString:idToken.claims[kBasicProfilePictureKey]]];
-        } else {
-          [handlerAuthFlow wait];
-          NSURL *infoURL = [NSURL URLWithString:
-              [NSString stringWithFormat:kUserInfoURLTemplate,
-                  [GIDSignInPreferences googleUserInfoServer],
-                  authState.lastTokenResponse.accessToken]];
-          [self startFetchURL:infoURL
-                      fromAuthState:authState
-                        withComment:@"GIDSignIn: fetch basic profile info"
-              withCompletionHandler:^(NSData *data, NSError *error) {
-            if (data && !error) {
-              NSError *jsonDeserializationError;
-              NSDictionary<NSString *, NSString *> *profileDict =
-                  [NSJSONSerialization JSONObjectWithData:data
-                                                  options:NSJSONReadingMutableContainers
-                                                    error:&jsonDeserializationError];
-              if (profileDict) {
-                handlerAuthFlow.profileData = [[GIDProfileData alloc]
-                    initWithEmail:idToken.claims[kBasicProfileEmailKey]
-                             name:profileDict[kBasicProfileNameKey]
-                        givenName:profileDict[kBasicProfileGivenNameKey]
-                       familyName:profileDict[kBasicProfileFamilyNameKey]
-                         imageURL:[NSURL URLWithString:profileDict[kBasicProfilePictureKey]]];
-              }
-            }
-            if (error) {
-              handlerAuthFlow.error = error;
+      // If the picture and name fields are present in the ID token, use them, otherwise make
+      // a userinfo request to fetch them.
+      if (idToken.claims[kBasicProfilePictureKey] &&
+          idToken.claims[kBasicProfileNameKey] &&
+          idToken.claims[kBasicProfileGivenNameKey] &&
+          idToken.claims[kBasicProfileFamilyNameKey]) {
+        handlerAuthFlow.profileData = [[GIDProfileData alloc]
+            initWithEmail:idToken.claims[kBasicProfileEmailKey]
+                     name:idToken.claims[kBasicProfileNameKey]
+                givenName:idToken.claims[kBasicProfileGivenNameKey]
+               familyName:idToken.claims[kBasicProfileFamilyNameKey]
+                 imageURL:[NSURL URLWithString:idToken.claims[kBasicProfilePictureKey]]];
+      } else {
+        [handlerAuthFlow wait];
+        NSURL *infoURL = [NSURL URLWithString:
+            [NSString stringWithFormat:kUserInfoURLTemplate,
+                [GIDSignInPreferences googleUserInfoServer],
+                authState.lastTokenResponse.accessToken]];
+        [self startFetchURL:infoURL
+                    fromAuthState:authState
+                      withComment:@"GIDSignIn: fetch basic profile info"
+            withCompletionHandler:^(NSData *data, NSError *error) {
+          if (data && !error) {
+            NSError *jsonDeserializationError;
+            NSDictionary<NSString *, NSString *> *profileDict =
+                [NSJSONSerialization JSONObjectWithData:data
+                                                options:NSJSONReadingMutableContainers
+                                                  error:&jsonDeserializationError];
+            if (profileDict) {
+              handlerAuthFlow.profileData = [[GIDProfileData alloc]
+                  initWithEmail:idToken.claims[kBasicProfileEmailKey]
+                           name:profileDict[kBasicProfileNameKey]
+                      givenName:profileDict[kBasicProfileGivenNameKey]
+                     familyName:profileDict[kBasicProfileFamilyNameKey]
+                       imageURL:[NSURL URLWithString:profileDict[kBasicProfilePictureKey]]];
             }
-            [handlerAuthFlow next];
-          }];
-        }
+          }
+          if (error) {
+            handlerAuthFlow.error = error;
+          }
+          [handlerAuthFlow next];
+        }];
       }
     }
   }];
@@ -639,11 +606,7 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
   __weak GIDAuthFlow *weakAuthFlow = authFlow;
   [authFlow addCallback:^() {
     GIDAuthFlow *handlerAuthFlow = weakAuthFlow;
-    if (_currentOptions.callback) {
-      _currentOptions.callback(_currentUser, handlerAuthFlow.error);
-    } else {
-      [_delegate signIn:self didSignInForUser:_currentUser withError:handlerAuthFlow.error];
-    }
+    _currentOptions.callback(_currentUser, handlerAuthFlow.error);
   }];
 }
 
@@ -667,13 +630,6 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
   [fetcher beginFetchWithCompletionHandler:handler];
 }
 
-- (void)didDisconnectWithUser:(GIDGoogleUser *)user
-                        error:(nullable NSError *)error {
-  if ([_delegate respondsToSelector:@selector(signIn:didDisconnectWithUser:withError:)]) {
-    [_delegate signIn:self didDisconnectWithUser:user withError:error];
-  }
-}
-
 // Parse incoming URL from the Google Device Policy app.
 - (BOOL)handleDevicePolicyAppURL:(NSURL *)url {
   OIDURLQueryComponent *queryComponent = [[OIDURLQueryComponent alloc] initWithURL:url];
@@ -684,7 +640,7 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
   if (![@"restart_auth" isEqualToString:actionString]) {
     return NO;
   }
-  if (!_presentingViewController) {
+  if (!_currentOptions.presentingViewController) {
     return NO;
   }
   if (!_currentAuthorizationFlow) {
@@ -737,23 +693,19 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
 
 // Asserts the parameters being valid.
 - (void)assertValidParameters {
-  if (![_clientID length]) {
+  if (![_currentOptions.configuration.clientID length]) {
     // NOLINTNEXTLINE(google-objc-avoid-throwing-exception)
     [NSException raise:NSInvalidArgumentException
-                format:@"You must specify |clientID| for |GIDSignIn|"];
-  }
-  if ([self adjustedScopes].count == 0) {
-    // NOLINTNEXTLINE(google-objc-avoid-throwing-exception)
-    [NSException raise:NSInvalidArgumentException
-                format:@"You must specify |shouldFetchBasicProfile| or |scopes| for |GIDSignIn|"];
+                format:@"You must specify |clientID| in |GIDConfiguration|"];
   }
 }
 
 // Assert that the UI Delegate has been set.
 - (void)assertValidPresentingViewController {
-  if (!_presentingViewController) {
+  if (!_currentOptions.presentingViewController) {
     // NOLINTNEXTLINE(google-objc-avoid-throwing-exception)
-    [NSException raise:NSInvalidArgumentException format:@"presentingViewController must be set."];
+    [NSException raise:NSInvalidArgumentException
+                format:@"|presentingViewController| must be set."];
   }
 }
 
@@ -771,20 +723,6 @@ static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
   [GTMAppAuthFetcherAuthorization removeAuthorizationFromKeychainForName:kGTMAppAuthKeychainName];
 }
 
-// Adds basic profile scopes to |scopes| if |shouldFetchBasicProfile| is set.
-- (NSArray *)adjustedScopes {
-  NSArray<NSString *> *adjustedScopes = _scopes;
-  if (_shouldFetchBasicProfile) {
-    adjustedScopes = [GIDScopes scopesWithBasicProfile:adjustedScopes];
-  }
-  return adjustedScopes;
-}
-
-- (NSURL *)redirectURI {
-  NSString *scheme = [_schemes clientIdentifierScheme];
-  return [NSURL URLWithString:[NSString stringWithFormat:@"%@:%@", scheme, kBrowserCallbackPath]];
-}
-
 - (BOOL)saveAuthState:(OIDAuthState *)authState {
   GTMAppAuthFetcherAuthorization *authorization =
       [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState];

+ 3 - 22
GoogleSignIn/Sources/GIDSignInButton.m

@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#import "GoogleSignIn/Sources/GIDSignInButton_Private.h"
+#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignInButton.h"
 
 #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h"
 
@@ -75,11 +75,6 @@ static const CGFloat kDropShadowYOffset = 2;
 
 static const CGFloat kDisabledIconAlpha = 40.0 / 100.0;
 
-#pragma mark - Misc. Constants
-
-// The query parameter name to log the appearance of the button.
-static NSString *const kLoggingParameter = @"gpbtn";
-
 #pragma mark - Colors
 
 // All colors in hex RGBA format (0xRRGGBBAA)
@@ -204,10 +199,8 @@ static UIColor *colorForStyleState(GIDSignInButtonColorScheme style,
                 action:@selector(switchToNormal)
       forControlEvents:UIControlEventTouchDragExit |
                        UIControlEventTouchDragOutside |
-                       UIControlEventTouchCancel];
-
-  // Setup convenience touch-up-inside handler:
-  [self addTarget:self action:@selector(pressed) forControlEvents:UIControlEventTouchUpInside];
+                       UIControlEventTouchCancel |
+                       UIControlEventTouchUpInside];
 
   // Update the icon, etc.
   [self updateUI];
@@ -335,18 +328,6 @@ static UIColor *colorForStyleState(GIDSignInButtonColorScheme style,
   [self setNeedsDisplay];
 }
 
-#pragma mark - UI Actions
-
-- (void)pressed {
-  [self switchToNormal];
-  NSString *appearanceCode = [NSString stringWithFormat:@"%ld.%ld",
-                                                        (long)_style,
-                                                        (long)_colorScheme];
-  NSDictionary *params = @{ kLoggingParameter : appearanceCode };
-  [GIDSignIn.sharedInstance
-      signInWithOptions:[GIDSignInInternalOptions optionsWithExtraParams:params]];
-}
-
 #pragma mark - Helpers
 
 - (CGFloat)minWidth {

+ 0 - 28
GoogleSignIn/Sources/GIDSignInButton_Private.h

@@ -1,28 +0,0 @@
-/*
- * Copyright 2021 Google LLC
- *
- * 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.
- */
-
-#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignInButton.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-// Private |GIDSignInButton| methods that are used internally in this SDK.
-@interface GIDSignInButton ()
-
-- (void)pressed;
-
-@end
-
-NS_ASSUME_NONNULL_END

+ 14 - 2
GoogleSignIn/Sources/GIDSignInInternalOptions.h

@@ -32,17 +32,29 @@ NS_ASSUME_NONNULL_BEGIN
 /// The extra parameters used in the sign-in URL.
 @property(nonatomic, readonly, nullable) NSDictionary *extraParams;
 
+/// The configuration to use during the flow.
+@property(nonatomic, readonly, nullable) GIDConfiguration *configuration;
+
+/// The the view controller to use during the flow.
+@property(nonatomic, readonly, nullable) UIViewController *presentingViewController;
+
 /// The callback block to be called at the completion of the flow.
 @property(nonatomic, readonly, nullable) GIDSignInCallback callback;
 
+/// The scopes to be used during the flow.
+@property(nonatomic, copy, nullable) NSArray<NSString *> *scopes;
+
 /// Creates the default options.
-+ (instancetype)defaultOptions;
++ (instancetype)defaultOptionsWithConfiguration:(nullable GIDConfiguration *)configuration
+                       presentingViewController:(nullable UIViewController *)presentingViewController
+                                       callback:(GIDSignInCallback)callback;
 
 /// Creates the options to sign in silently.
 + (instancetype)silentOptionsWithCallback:(GIDSignInCallback)callback;
 
 /// Creates the options to sign in with extra parameters.
-+ (instancetype)optionsWithExtraParams:(NSDictionary *)extraParams;
++ (instancetype)optionsWithCallback:(GIDSignInCallback)callback
+                        extraParams:(NSDictionary *)extraParams;
 
 /// Creates options with the same values as the receiver, except for the "extra parameters", and
 /// continuation flag, which are replaced by the arguments passed to this method.

+ 21 - 5
GoogleSignIn/Sources/GIDSignInInternalOptions.m

@@ -14,30 +14,42 @@
 
 #import "GoogleSignIn/Sources/GIDSignInInternalOptions.h"
 
+#import "GoogleSignIn/Sources/GIDScopes.h"
+
 NS_ASSUME_NONNULL_BEGIN
 
 @implementation GIDSignInInternalOptions
 
-+ (instancetype)defaultOptions {
++ (instancetype)defaultOptionsWithConfiguration:(nullable GIDConfiguration *)configuration
+                       presentingViewController:(nullable UIViewController *)presentingViewController
+                                       callback:(GIDSignInCallback)callback {
   GIDSignInInternalOptions *options = [[GIDSignInInternalOptions alloc] init];
   if (options) {
     options->_interactive = YES;
     options->_continuation = NO;
+    options->_configuration = configuration;
+    options->_presentingViewController = presentingViewController;
+    options->_callback = callback;
+    options->_scopes = [GIDScopes scopesWithBasicProfile:@[]];
   }
   return options;
 }
 
 + (instancetype)silentOptionsWithCallback:(GIDSignInCallback)callback {
-  GIDSignInInternalOptions *options = [self defaultOptions];
+  GIDSignInInternalOptions *options = [self defaultOptionsWithConfiguration:nil
+                                                   presentingViewController:nil
+                                                                   callback:callback];
   if (options) {
     options->_interactive = NO;
-    options->_callback = callback;
   }
   return options;
 }
 
-+ (instancetype)optionsWithExtraParams:(NSDictionary *)extraParams {
-  GIDSignInInternalOptions *options = [self defaultOptions];
++ (instancetype)optionsWithCallback:(GIDSignInCallback)callback
+                        extraParams:(NSDictionary *)extraParams {
+  GIDSignInInternalOptions *options = [self defaultOptionsWithConfiguration:nil
+                                                   presentingViewController:nil
+                                                                   callback:callback];
   if (options) {
     options->_extraParams = [extraParams copy];
   }
@@ -50,6 +62,10 @@ NS_ASSUME_NONNULL_BEGIN
   if (options) {
     options->_interactive = _interactive;
     options->_continuation = continuation;
+    options->_configuration = _configuration;
+    options->_presentingViewController = _presentingViewController;
+    options->_callback = _callback;
+    options->_scopes = _scopes;
     options->_extraParams = [extraParams copy];
   }
   return options;

+ 18 - 86
GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h

@@ -17,8 +17,8 @@
 #import <Foundation/Foundation.h>
 #import <UIKit/UIKit.h>
 
+@class GIDConfiguration;
 @class GIDGoogleUser;
-@class GIDSignIn;
 
 NS_ASSUME_NONNULL_BEGIN
 
@@ -47,101 +47,26 @@ typedef void (^GIDSignInCallback)(GIDGoogleUser *_Nullable user, NSError *_Nulla
 /// Represents a callback block that takes an error if the operation was unsuccessful.
 typedef void (^GIDDisconnectCallback)(NSError *_Nullable error);
 
-/// A protocol implemented by the delegate of `GIDSignIn` to receive a refresh token or an error.
-@protocol GIDSignInDelegate <NSObject>
-
-/// The sign-in flow has finished and was successful if `error` is `nil`.
-- (void)signIn:(GIDSignIn *)signIn
-    didSignInForUser:(nullable GIDGoogleUser *)user
-           withError:(nullable NSError *)error;
-
-@optional
-
-/// Finished disconnecting `user` from the app successfully if `error` is `nil`.
-- (void)signIn:(GIDSignIn *)signIn
-    didDisconnectWithUser:(nullable GIDGoogleUser *)user
-                withError:(nullable NSError *)error;
-
-@end
-
-/// This class signs the user in with Google. It also provides single sign-on via a capable Google
-/// app if one is installed.
+/// This class signs the user in with Google.
 ///
 /// For reference, please see "Google Sign-In for iOS" at
 /// https://developers.google.com/identity/sign-in/ios
-///
-/// Here is sample code to use `GIDSignIn`:
-/// 1. Get a reference to the `GIDSignIn` shared instance:
-///    ```
-///    GIDSignIn *signIn = GIDSignIn.sharedInstance;
-///    ```
-/// 2. Call `[signIn setDelegate:self]`;
-/// 3. Set up delegate method `signIn:didSignInForUser:withError:`.
-/// 4. Call `handleURL` on the shared instance from `application:openUrl:...` in your app delegate.
-/// 5. Call `signIn` on the shared instance;
 @interface GIDSignIn : NSObject
 
 /// A shared `GIDSignIn` instance.
 @property(class, nonatomic, readonly) GIDSignIn *sharedInstance;
 
-/// The authentication object for the current user, or `nil` if there is currently no logged in
-/// user.
+/// The `GIDGoogleUser` object representing the current user or `nil` if there is no signed-in user.
 @property(nonatomic, readonly, nullable) GIDGoogleUser *currentUser;
 
-/// The object to be notified when authentication is finished.
-@property(nonatomic, weak, nullable) id<GIDSignInDelegate> delegate;
-
-/// The view controller used to present `SFSafariViewContoller` on iOS 9 and 10.
-@property(nonatomic, weak, nullable) UIViewController *presentingViewController;
-
-/// The client ID of the app from the Google APIs console.  Must set for sign-in to work.
-@property(nonatomic, copy, nullable) NSString *clientID;
-
-/// The API scopes requested by the app in an array of `NSString`s.  The default value is `@[]`.
-///
-/// This property is optional. If you set it, set it before calling `signIn`.
-@property(nonatomic, copy, nullable) NSArray<NSString *> *scopes;
-
-/// Whether or not to fetch basic profile data after signing in. The data is saved in the
-/// `GIDGoogleUser.profileData` object.
-///
-/// Setting the flag will add "email" and "profile" to scopes.
-/// Defaults to `YES`.
-@property(nonatomic, assign) BOOL shouldFetchBasicProfile;
-
-/// The login hint to the authorization server, for example the user's ID, or email address,
-/// to be prefilled if possible.
-///
-/// This property is optional. If you set it, set it before calling `signIn`.
-@property(nonatomic, copy, nullable) NSString *loginHint;
-
-/// The client ID of the home web server.  This will be returned as the `audience` property of the
-/// OpenID Connect ID token.  For more info on the ID token:
-/// https://developers.google.com/identity/sign-in/ios/backend-auth
-///
-/// This property is optional. If you set it, set it before calling `signIn`.
-@property(nonatomic, copy, nullable) NSString *serverClientID;
-
-/// The OpenID2 realm of the home web server. This allows Google to include the user's OpenID
-/// Identifier in the OpenID Connect ID token.
-///
-/// This property is optional. If you set it, set it before calling `signIn`.
-@property(nonatomic, copy, nullable) NSString *openIDRealm;
-
-/// The Google Apps domain to which users must belong to sign in.  To verify, check
-/// `GIDGoogleUser`'s `hostedDomain` property.
-///
-/// This property is optional. If you set it, set it before calling `signIn`.
-@property(nonatomic, copy, nullable) NSString *hostedDomain;
-
-/// Unavailable. Use `sharedInstance` to instantiate `GIDSignIn`.
+/// Unavailable. Use the `sharedInstance` property to instantiate `GIDSignIn`.
 + (instancetype)new NS_UNAVAILABLE;
 
-/// Unavailable. Use `sharedInstance` to instantiate `GIDSignIn`.
+/// Unavailable. Use the `sharedInstance` property to instantiate `GIDSignIn`.
 - (instancetype)init NS_UNAVAILABLE;
 
-/// This method should be called from your `UIApplicationDelegate`'s `application:openURL:options`
-/// and `application:openURL:sourceApplication:annotation` method(s).
+/// This method should be called from your `UIApplicationDelegate`'s `application:openURL:options:`
+/// method.
 ///
 /// @param url The URL that was passed to the app.
 /// @return `YES` if `GIDSignIn` handled this URL.
@@ -157,13 +82,20 @@ typedef void (^GIDDisconnectCallback)(NSError *_Nullable error);
 /// @param callback The `GIDSignInCallback` block that is called on completion.
 - (void)restorePreviousSignInWithCallback:(GIDSignInCallback)callback;
 
-/// Starts an interactive sign-in flow using `GIDSignIn`'s configuration properties.
+/// Starts an interactive sign-in flow using the provided configuration.
 ///
-/// The delegate will be called at the end of this process.  Any saved sign-in state will be
+/// The callback will be called at the end of this process.  Any saved sign-in state will be
 /// replaced by the result of this flow.  Note that this method should not be called when the app is
 /// starting up, (e.g in `application:didFinishLaunchingWithOptions:`); instead use the
-/// `restorePreviousSignIn` method to restore a previous sign-in.
-- (void)signIn;
+/// `restorePreviousSignInWithCallback` method to restore a previous sign-in.
+///
+/// @param configuration The configuration properties to be used for this flow.
+/// @param presentingViewController The view controller used to present `SFSafariViewContoller` on
+///     iOS 9 and 10.
+/// @param callback The `GIDSignInCallback` block that is called on completion.
+- (void)signInWithConfiguration:(GIDConfiguration *)configuration
+       presentingViewController:(UIViewController *)presentingViewController
+                       callback:(GIDSignInCallback)callback;
 
 /// Marks current user as being in the signed out state.
 - (void)signOut;

+ 1 - 1
Sample/Source/AppDelegate.h

@@ -16,7 +16,7 @@
 
 #import <UIKit/UIKit.h>
 
-@interface AppDelegate : UIResponder<UIApplicationDelegate>
+@interface AppDelegate : UIResponder <UIApplicationDelegate>
 
 // The sample app's |UIWindow|.
 @property(strong, nonatomic) UIWindow *window;

+ 0 - 7
Sample/Source/AppDelegate.m

@@ -22,17 +22,10 @@
 
 @implementation AppDelegate
 
-// DO NOT USE THIS CLIENT ID. IT WILL NOT WORK FOR YOUR APP.
-// Please use the client ID created for you by Google.
-static NSString * const kClientID =
-    @"589453917038-qaoga89fitj2ukrsq27ko56fimmojac6.apps.googleusercontent.com";
-
 #pragma mark Object life-cycle.
 
 - (BOOL)application:(UIApplication *)application
     didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
-  // Set app's client ID for |GIDSignIn|.
-  GIDSignIn.sharedInstance.clientID = kClientID;
   // Restore any previous sign-in session at app launch before displaying main view.  If the restore
   // succeeds, we'll have a currentUser and the view will be able to draw its UI for the signed-in
   // state.  If the restore fails, currentUser will be nil and we'll draw the signed-out state

+ 1 - 1
Sample/Source/SignInViewController.h

@@ -21,7 +21,7 @@
 // A view controller for the Google Sign-In button which initiates a standard
 // OAuth 2.0 flow and provides an access token and a refresh token. A "Sign out"
 // button is provided to allow users to sign out of this application.
-@interface SignInViewController : UITableViewController <GIDSignInDelegate>
+@interface SignInViewController : UITableViewController
 
 @property(weak, nonatomic) IBOutlet GIDSignInButton *signInButton;
 // A label to display the result of the sign-in action.

+ 25 - 49
Sample/Source/SignInViewController.m

@@ -28,7 +28,6 @@ static NSString *const kPlaceholderEmailAddress = @"<Email>";
 static NSString *const kPlaceholderAvatarImageName = @"PlaceholderAvatar.png";
 
 // Labels for the cells that have in-cell control elements.
-static NSString *const kGetUserProfileCellLabel = @"Get user Basic Profile";
 static NSString *const kButtonWidthCellLabel = @"Width";
 
 // Labels for the cells that drill down to data pickers.
@@ -38,16 +37,19 @@ static NSString *const kStyleCellLabel = @"Style";
 // Accessibility Identifiers.
 static NSString *const kCredentialsButtonAccessibilityIdentifier = @"Credentials";
 
+// DO NOT USE THIS CLIENT ID. IT WILL NOT WORK FOR YOUR APP.
+// Please use the client ID created for you by Google.
+static NSString * const kClientID =
+    @"589453917038-qaoga89fitj2ukrsq27ko56fimmojac6.apps.googleusercontent.com";
+
 @implementation SignInViewController {
   // This is an array of arrays, each one corresponding to the cell
   // labels for its respective section.
   NSArray *_sectionCellLabels;
 
-  // These sets contain the labels corresponding to cells that have various
-  // types (each cell either drills down to another table view, contains an
-  // in-cell switch, or contains a slider).
+  // These sets contain the labels corresponding to cells that have various types (each cell either
+  // drills down to another table view or contains a slider).
   NSArray *_drillDownCells;
-  NSArray *_switchCells;
   NSArray *_sliderCells;
 
   // States storing the current set of selected elements for each data picker.
@@ -56,14 +58,16 @@ static NSString *const kCredentialsButtonAccessibilityIdentifier = @"Credentials
 
   // Map that keeps track of which cell corresponds to which DataPickerState.
   NSDictionary *_drilldownCellState;
+
+  // Configuration options for GIDSignIn.
+  GIDConfiguration *_configuration;
 }
 
 #pragma mark - View lifecycle
 
 - (void)setUp {
   _sectionCellLabels = @[
-    @[ kColorSchemeCellLabel, kStyleCellLabel, kButtonWidthCellLabel ],
-    @[ kGetUserProfileCellLabel ]
+    @[ kColorSchemeCellLabel, kStyleCellLabel, kButtonWidthCellLabel ]
   ];
 
   // Groupings of cell types.
@@ -71,9 +75,6 @@ static NSString *const kCredentialsButtonAccessibilityIdentifier = @"Credentials
     kColorSchemeCellLabel,
     kStyleCellLabel
   ];
-
-  _switchCells =
-      @[ kGetUserProfileCellLabel ];
   _sliderCells = @[ kButtonWidthCellLabel ];
 
   // Initialize data picker states.
@@ -97,11 +98,7 @@ static NSString *const kCredentialsButtonAccessibilityIdentifier = @"Credentials
   // xib file doesn't count.
   [GIDSignInButton class];
 
-  GIDSignIn *signIn = GIDSignIn.sharedInstance;
-  signIn.shouldFetchBasicProfile = YES;
-  signIn.delegate = self;
-  signIn.presentingViewController = self;
-  GIDSignIn.sharedInstance.scopes = @[ @"email" ];
+  _configuration = [[GIDConfiguration alloc] initWithClientID:kClientID];
 }
 
 - (id)initWithNibName:(NSString *)nibNameOrNil
@@ -138,21 +135,19 @@ static NSString *const kCredentialsButtonAccessibilityIdentifier = @"Credentials
   [super viewWillAppear:animated];
 }
 
-#pragma mark - GIDSignInDelegate
-
-- (void)signIn:(GIDSignIn *)signIn
-    didSignInForUser:(GIDGoogleUser *)user
-           withError:(NSError *)error {
-  if (error) {
-    _signInAuthStatus.text = [NSString stringWithFormat:@"Status: Authentication error: %@", error];
-    return;
-  }
-  [self reportAuthStatus];
-  [self updateButtons];
-}
-
-- (void)presentSignInViewController:(UIViewController *)viewController {
-  [[self navigationController] pushViewController:viewController animated:YES];
+- (IBAction)signInPressed:(id)sender {
+  [GIDSignIn.sharedInstance signInWithConfiguration:_configuration
+                           presentingViewController:self
+                                           callback:^(GIDGoogleUser * _Nullable user,
+                                                      NSError * _Nullable error) {
+    if (error) {
+      self->_signInAuthStatus.text =
+          [NSString stringWithFormat:@"Status: Authentication error: %@", error];
+      return;
+    }
+    [self reportAuthStatus];
+    [self updateButtons];
+  }];
 }
 
 #pragma mark - Helper methods
@@ -304,10 +299,6 @@ static NSString *const kCredentialsButtonAccessibilityIdentifier = @"Credentials
   [self reportAuthStatus];
 }
 
-- (void)toggleBasicProfile:(UISwitch *)sender {
-  GIDSignIn.sharedInstance.shouldFetchBasicProfile = sender.on;
-}
-
 - (void)changeSignInButtonWidth:(UISlider *)sender {
   CGRect frame = self.signInButton.frame;
   frame.size.width = sender.value;
@@ -351,7 +342,6 @@ static NSString *const kCredentialsButtonAccessibilityIdentifier = @"Credentials
 - (UITableViewCell *)tableView:(UITableView *)tableView
          cellForRowAtIndexPath:(NSIndexPath *)indexPath {
   static NSString * const kDrilldownCell = @"DrilldownCell";
-  static NSString * const kSwitchCell = @"SwitchCell";
   static NSString * const kSliderCell = @"SliderCell";
 
   NSString *label = _sectionCellLabels[indexPath.section][indexPath.row];
@@ -360,8 +350,6 @@ static NSString *const kCredentialsButtonAccessibilityIdentifier = @"Credentials
 
   if ([_drillDownCells containsObject:label]) {
     identifier = kDrilldownCell;
-  } else if ([_switchCells containsObject:label]) {
-    identifier = kSwitchCell;
   } else if ([_sliderCells containsObject:label]) {
     identifier = kSliderCell;
   }
@@ -384,18 +372,6 @@ static NSString *const kCredentialsButtonAccessibilityIdentifier = @"Credentials
       cell.detailTextLabel.text = [dataState.selectedCells anyObject];
     }
     cell.accessibilityValue = cell.detailTextLabel.text;
-  } else if (identifier == kSwitchCell) {
-    UISwitch *toggle = [[UISwitch alloc] initWithFrame:CGRectZero];
-
-    if ([label isEqualToString:kGetUserProfileCellLabel]) {
-      [toggle addTarget:self
-                    action:@selector(toggleBasicProfile:)
-          forControlEvents:UIControlEventValueChanged];
-      toggle.on = GIDSignIn.sharedInstance.shouldFetchBasicProfile;
-    }
-
-    toggle.accessibilityLabel = [NSString stringWithFormat:@"%@ Switch", cell.accessibilityLabel];
-    cell.accessoryView = toggle;
   } else if (identifier == kSliderCell) {
 
     UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(0, 0, 150, 0)];

+ 30 - 21
Sample/Source/SignInViewController.xib

@@ -1,8 +1,10 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9531" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none">
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     <objects>
         <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="SignInViewController">
@@ -20,82 +22,89 @@
         </placeholder>
         <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
         <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="55">
-            <rect key="frame" x="0.0" y="0.0" width="320" height="568"/>
+            <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
             <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
-            <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+            <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
             <nil key="simulatedStatusBarMetrics"/>
             <view key="tableHeaderView" contentMode="scaleToFill" id="57">
-                <rect key="frame" x="0.0" y="0.0" width="320" height="197"/>
+                <rect key="frame" x="0.0" y="0.0" width="414" height="197"/>
                 <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                 <subviews>
-                    <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Status: Authenticated" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="72">
+                    <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" fixedFrame="YES" text="Status: Authenticated" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="72">
                         <rect key="frame" x="21" y="67" width="280" height="21"/>
                         <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                         <fontDescription key="fontDescription" type="system" pointSize="14"/>
-                        <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+                        <color key="textColor" systemColor="darkTextColor"/>
                         <nil key="highlightedColor"/>
                     </label>
-                    <imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="PlaceholderAvatar.png" id="60">
+                    <imageView userInteractionEnabled="NO" contentMode="scaleToFill" fixedFrame="YES" image="PlaceholderAvatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="60">
                         <rect key="frame" x="20" y="8" width="50" height="50"/>
                         <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                     </imageView>
-                    <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="&lt;Name&gt;" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="61">
+                    <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" fixedFrame="YES" text="&lt;Name&gt;" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="61">
                         <rect key="frame" x="78" y="8" width="153" height="21"/>
                         <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                         <fontDescription key="fontDescription" type="system" pointSize="17"/>
-                        <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+                        <color key="textColor" systemColor="darkTextColor"/>
                         <nil key="highlightedColor"/>
                     </label>
-                    <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="&lt;Email&gt;" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="7" id="62">
+                    <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" fixedFrame="YES" text="&lt;Email&gt;" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="7" translatesAutoresizingMaskIntoConstraints="NO" id="62">
                         <rect key="frame" x="78" y="34" width="153" height="21"/>
                         <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                         <fontDescription key="fontDescription" type="system" pointSize="14"/>
-                        <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+                        <color key="textColor" systemColor="darkTextColor"/>
                         <nil key="highlightedColor"/>
                     </label>
-                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="63">
+                    <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="63">
                         <rect key="frame" x="50" y="160" width="77" height="35"/>
                         <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                         <fontDescription key="fontDescription" type="boldSystem" pointSize="16"/>
                         <state key="normal" title="Sign out">
-                            <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
+                            <color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                         </state>
                         <connections>
                             <action selector="signOut:" destination="-1" eventType="touchUpInside" id="64"/>
                         </connections>
                     </button>
-                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="65">
+                    <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="65">
                         <rect key="frame" x="186" y="160" width="97" height="35"/>
                         <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                         <fontDescription key="fontDescription" type="boldSystem" pointSize="16"/>
                         <state key="normal" title="Disconnect">
-                            <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
+                            <color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                         </state>
                         <connections>
                             <action selector="disconnect:" destination="-1" eventType="touchUpInside" id="66"/>
                         </connections>
                     </button>
-                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="infoDark" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" id="80">
+                    <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="infoDark" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="80">
                         <rect key="frame" x="168" y="68" width="22" height="22"/>
                         <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                         <connections>
                             <action selector="showAuthInspector:" destination="-1" eventType="touchUpInside" id="82"/>
                         </connections>
                     </button>
-                    <view contentMode="scaleToFill" id="bKH-Ji-uCR" customClass="GIDSignInButton">
+                    <view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bKH-Ji-uCR" customClass="GIDSignInButton">
                         <rect key="frame" x="20" y="100" width="281" height="39"/>
                         <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
-                        <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                         <accessibility key="accessibilityConfiguration">
                             <accessibilityTraits key="traits" button="YES"/>
                         </accessibility>
+                        <connections>
+                            <action selector="signInPressed:" destination="-1" eventType="touchUpInside" id="6j4-hN-JQi"/>
+                        </connections>
                     </view>
                 </subviews>
-                <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+                <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
             </view>
+            <point key="canvasLocation" x="139" y="153"/>
         </tableView>
     </objects>
     <resources>
         <image name="PlaceholderAvatar.png" width="50" height="50"/>
+        <systemColor name="darkTextColor">
+            <color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
     </resources>
 </document>