diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 221650b3..d2ec3f52 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -17,8 +17,7 @@ jobs: os: [macos-12] podspec: [GoogleSignIn.podspec, GoogleSignInSwiftSupport.podspec] flag: [ - "", - "--use-libraries", + "", "--use-static-frameworks" ] include: @@ -33,7 +32,7 @@ jobs: - name: Lint podspec using local source run: | pod lib lint ${{ matrix.podspec }} --verbose \ - ${{ matrix.includePodspecFlag }} ${{ matrix.flag }} + ${{ matrix.includePodspecFlag }} ${{ matrix.flag }} spm-build-test: runs-on: ${{ matrix.os }} diff --git a/GoogleSignIn.podspec b/GoogleSignIn.podspec index b82e5dcc..d8407e90 100644 --- a/GoogleSignIn.podspec +++ b/GoogleSignIn.podspec @@ -12,6 +12,7 @@ The Google Sign-In SDK allows users to sign in with their Google account from th :git => 'https://github.com/google/GoogleSignIn-iOS.git', :tag => s.version.to_s } + s.swift_version = '4.0' ios_deployment_target = '10.0' osx_deployment_target = '10.15' s.ios.deployment_target = ios_deployment_target @@ -32,8 +33,8 @@ The Google Sign-In SDK allows users to sign in with their Google account from th ] s.ios.framework = 'UIKit' s.osx.framework = 'AppKit' - s.dependency 'AppAuth', '~> 1.5' - s.dependency 'GTMAppAuth', '>= 1.3', '< 3.0' + s.dependency 'AppAuth', '~> 1.6' + s.dependency 'GTMAppAuth', '~> 4.0' s.dependency 'GTMSessionFetcher/Core', '>= 1.1', '< 4.0' s.resource_bundle = { 'GoogleSignIn' => ['GoogleSignIn/Sources/{Resources,Strings}/*'] diff --git a/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m b/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m deleted file mode 100644 index 814a73fc..00000000 --- a/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2022 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 - -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - -#import "GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h" - -#import "GoogleSignIn/Sources/GIDEMMSupport.h" - -#ifdef SWIFT_PACKAGE -@import AppAuth; -@import GTMAppAuth; -#else -#import -#import -#endif - -NS_ASSUME_NONNULL_BEGIN - -// The specialized GTMAppAuthFetcherAuthorization delegate that handles potential EMM error -// responses. -@interface GIDAppAuthFetcherAuthorizationEMMChainedDelegate : NSObject - -// Initializes with chained delegate and selector. -- (instancetype)initWithDelegate:(id)delegate selector:(SEL)selector; - -// The callback method for GTMAppAuthFetcherAuthorization to invoke. -- (void)authentication:(GTMAppAuthFetcherAuthorization *)auth - request:(NSMutableURLRequest *)request - finishedWithError:(nullable NSError *)error; - -@end - -@implementation GIDAppAuthFetcherAuthorizationEMMChainedDelegate { - // We use a weak reference here to match GTMAppAuthFetcherAuthorization. - __weak id _delegate; - SEL _selector; - // We need to maintain a reference to the chained delegate because GTMAppAuthFetcherAuthorization - // only keeps a weak reference. - GIDAppAuthFetcherAuthorizationEMMChainedDelegate *_retained_self; -} - -- (instancetype)initWithDelegate:(id)delegate selector:(SEL)selector { - self = [super init]; - if (self) { - _delegate = delegate; - _selector = selector; - _retained_self = self; - } - return self; -} - -- (void)authentication:(GTMAppAuthFetcherAuthorization *)auth - request:(NSMutableURLRequest *)request - finishedWithError:(nullable NSError *)error { - [GIDEMMSupport handleTokenFetchEMMError:error completion:^(NSError *_Nullable error) { - if (!self->_delegate || !self->_selector) { - return; - } - NSMethodSignature *signature = [self->_delegate methodSignatureForSelector:self->_selector]; - if (!signature) { - return; - } - id argument1 = auth; - id argument2 = request; - id argument3 = error; - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; - [invocation setTarget:self->_delegate]; // index 0 - [invocation setSelector:self->_selector]; // index 1 - [invocation setArgument:&argument1 atIndex:2]; - [invocation setArgument:&argument2 atIndex:3]; - [invocation setArgument:&argument3 atIndex:4]; - [invocation invoke]; - }]; - // Prepare to deallocate the chained delegate instance because the above block will retain the - // iVar references it uses. - _retained_self = nil; -} - -@end - -@implementation GIDAppAuthFetcherAuthorizationWithEMMSupport - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-implementations" -- (void)authorizeRequest:(nullable NSMutableURLRequest *)request - delegate:(id)delegate - didFinishSelector:(SEL)sel { -#pragma clang diagnostic pop - GIDAppAuthFetcherAuthorizationEMMChainedDelegate *chainedDelegate = - [[GIDAppAuthFetcherAuthorizationEMMChainedDelegate alloc] initWithDelegate:delegate - selector:sel]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [super authorizeRequest:request - delegate:chainedDelegate - didFinishSelector:@selector(authentication:request:finishedWithError:)]; -#pragma clang diagnostic pop -} - -- (void)authorizeRequest:(nullable NSMutableURLRequest *)request - completionHandler:(GTMAppAuthFetcherAuthorizationCompletion)handler { - [super authorizeRequest:request completionHandler:^(NSError *_Nullable error) { - [GIDEMMSupport handleTokenFetchEMMError:error completion:^(NSError *_Nullable error) { - handler(error); - }]; - }]; -} - -@end - -NS_ASSUME_NONNULL_END - -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST diff --git a/GoogleSignIn/Sources/GIDAuthStateMigration.h b/GoogleSignIn/Sources/GIDAuthStateMigration.h index 1da012d7..329be59f 100644 --- a/GoogleSignIn/Sources/GIDAuthStateMigration.h +++ b/GoogleSignIn/Sources/GIDAuthStateMigration.h @@ -16,14 +16,20 @@ #import +@class GTMKeychainStore; +@class GTMAuthSession; + NS_ASSUME_NONNULL_BEGIN -// A class providing migration support for auth state saved by older versions of the SDK. +/// A class providing migration support for auth state saved by older versions of the SDK. @interface GIDAuthStateMigration : NSObject -// Perform a one-time migration for auth state saved by GPPSignIn 1.x or GIDSignIn 1.0 - 4.x to the -// GTMAppAuth storage introduced in GIDSignIn 5.0. -+ (void)migrateIfNeededWithTokenURL:(NSURL *)tokenURL +/// Creates an instance of this migration type with the keychain storage wrapper it will use. +- (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore NS_DESIGNATED_INITIALIZER; + +/// Perform a one-time migration for auth state saved by GPPSignIn 1.x or GIDSignIn 1.0 - 4.x to the +/// GTMAppAuth storage introduced in GIDSignIn 5.0. +- (void)migrateIfNeededWithTokenURL:(NSURL *)tokenURL callbackPath:(NSString *)callbackPath keychainName:(NSString *)keychainName isFreshInstall:(BOOL)isFreshInstall; diff --git a/GoogleSignIn/Sources/GIDAuthStateMigration.m b/GoogleSignIn/Sources/GIDAuthStateMigration.m index f6ae73e4..f0827f03 100644 --- a/GoogleSignIn/Sources/GIDAuthStateMigration.m +++ b/GoogleSignIn/Sources/GIDAuthStateMigration.m @@ -16,13 +16,12 @@ #import "GoogleSignIn/Sources/GIDSignInCallbackSchemes.h" +@import GTMAppAuth; + #ifdef SWIFT_PACKAGE @import AppAuth; -@import GTMAppAuth; #else #import -#import -#import #endif NS_ASSUME_NONNULL_BEGIN @@ -39,9 +38,28 @@ // Keychain service name used to store the last used fingerprint value. static NSString *const kFingerprintService = @"fingerprint"; +@interface GIDAuthStateMigration () + +@property (nonatomic, strong) GTMKeychainStore *keychainStore; + +@end + @implementation GIDAuthStateMigration -+ (void)migrateIfNeededWithTokenURL:(NSURL *)tokenURL +- (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore { + self = [super init]; + if (self) { + _keychainStore = keychainStore; + } + return self; +} + +- (instancetype)init { + GTMKeychainStore *keychainStore = [[GTMKeychainStore alloc] initWithItemName:@"auth"]; + return [self initWithKeychainStore:keychainStore]; +} + +- (void)migrateIfNeededWithTokenURL:(NSURL *)tokenURL callbackPath:(NSString *)callbackPath keychainName:(NSString *)keychainName isFreshInstall:(BOOL)isFreshInstall { @@ -55,14 +73,15 @@ + (void)migrateIfNeededWithTokenURL:(NSURL *)tokenURL // action and go on to mark the migration check as having been performed. if (!isFreshInstall) { // Attempt migration - GTMAppAuthFetcherAuthorization *authorization = - [self extractAuthorizationWithTokenURL:tokenURL callbackPath:callbackPath]; + GTMAuthSession *authSession = + [self extractAuthSessionWithTokenURL:tokenURL callbackPath:callbackPath]; // If migration was successful, save our migrated state to the keychain. - if (authorization) { + if (authSession) { + NSError *err; + [self.keychainStore saveAuthSession:authSession error:&err]; // If we're unable to save to the keychain, return without marking migration performed. - if (![GTMAppAuthFetcherAuthorization saveAuthorization:authorization - toKeychainForName:keychainName]) { + if (err) { return; }; } @@ -72,10 +91,10 @@ + (void)migrateIfNeededWithTokenURL:(NSURL *)tokenURL [defaults setBool:YES forKey:kMigrationCheckPerformedKey]; } -// Returns a |GTMAppAuthFetcherAuthorization| object containing any old auth state or |nil| if none +// Returns a |GTMAuthSession| object containing any old auth state or |nil| if none // was found or the migration failed. -+ (nullable GTMAppAuthFetcherAuthorization *) - extractAuthorizationWithTokenURL:(NSURL *)tokenURL callbackPath:(NSString *)callbackPath { +- (nullable GTMAuthSession *)extractAuthSessionWithTokenURL:(NSURL *)tokenURL + callbackPath:(NSString *)callbackPath { // Retrieve the last used fingerprint. NSString *fingerprint = [GIDAuthStateMigration passwordForService:kFingerprintService]; if (!fingerprint) { @@ -83,8 +102,10 @@ + (void)migrateIfNeededWithTokenURL:(NSURL *)tokenURL } // Retrieve the GTMOAuth2 persistence string. - NSString *GTMOAuth2PersistenceString = [GTMKeychain passwordFromKeychainForName:fingerprint]; - if (!GTMOAuth2PersistenceString) { + NSError *passwordError; + NSString *GTMOAuth2PersistenceString = + [self.keychainStore.keychainHelper passwordForService:fingerprint error:&passwordError]; + if (passwordError) { return nil; } @@ -126,16 +147,17 @@ + (void)migrateIfNeededWithTokenURL:(NSURL *)tokenURL additionalTokenRequestParameters]; } - // Use |GTMOAuth2KeychainCompatibility| to generate a |GTMAppAuthFetcherAuthorization| from the + // Use |GTMOAuth2Compatibility| to generate a |GTMAuthSession| from the // persistence string, redirect URI, client ID, and token endpoint URL. - GTMAppAuthFetcherAuthorization *authorization = [GTMOAuth2KeychainCompatibility - authorizeFromPersistenceString:persistenceString - tokenURL:tokenURL - redirectURI:redirectURI - clientID:clientID - clientSecret:nil]; - - return authorization; + GTMAuthSession *authSession = + [GTMOAuth2Compatibility authSessionForPersistenceString:persistenceString + tokenURL:tokenURL + redirectURI:redirectURI + clientID:clientID + clientSecret:nil + error:nil]; + + return authSession; } // Returns the password string for a given service string stored by an old version of the SDK or diff --git a/GoogleSignIn/Sources/GIDEMMSupport.h b/GoogleSignIn/Sources/GIDEMMSupport.h index d6f4e92e..f57a6af7 100644 --- a/GoogleSignIn/Sources/GIDEMMSupport.h +++ b/GoogleSignIn/Sources/GIDEMMSupport.h @@ -20,19 +20,23 @@ #import +@import GTMAppAuth; + NS_ASSUME_NONNULL_BEGIN -// A class to support EMM (Enterprise Mobility Management). -@interface GIDEMMSupport : NSObject +/// A class to support EMM (Enterprise Mobility Management). +@interface GIDEMMSupport : NSObject + +- (instancetype)init NS_DESIGNATED_INITIALIZER; -// Handles potential EMM error from token fetch response. +/// Handles potential EMM error from token fetch response. + (void)handleTokenFetchEMMError:(nullable NSError *)error completion:(void (^)(NSError *_Nullable))completion; -// Gets a new set of URL parameters that contains updated EMM-related URL parameters if needed. +/// Gets a new set of URL parameters that contains updated EMM-related URL parameters if needed. + (NSDictionary *)updatedEMMParametersWithParameters:(NSDictionary *)parameters; -// Gets a new set of URL parameters that also contains EMM-related URL parameters if needed. +/// Gets a new set of URL parameters that also contains EMM-related URL parameters if needed. + (NSDictionary *)parametersWithParameters:(NSDictionary *)parameters emmSupport:(nullable NSString *)emmSupport isPasscodeInfoRequired:(BOOL)isPasscodeInfoRequired; diff --git a/GoogleSignIn/Sources/GIDEMMSupport.m b/GoogleSignIn/Sources/GIDEMMSupport.m index a796f5fb..0e7b0369 100644 --- a/GoogleSignIn/Sources/GIDEMMSupport.m +++ b/GoogleSignIn/Sources/GIDEMMSupport.m @@ -44,8 +44,26 @@ // New UIDevice system name for iOS. static NSString *const kNewIOSSystemName = @"iOS"; +// The error key in the server response. +static NSString *const kErrorKey = @"error"; + +// Optional separator between error prefix and the payload. +static NSString *const kErrorPayloadSeparator = @":"; + +// A list for recognized error codes. +typedef NS_ENUM(NSInteger, ErrorCode) { + ErrorCodeNone = 0, + ErrorCodeDeviceNotCompliant, + ErrorCodeScreenlockRequired, + ErrorCodeAppVerificationRequired, +}; + @implementation GIDEMMSupport +- (instancetype)init { + return [super init]; +} + + (void)handleTokenFetchEMMError:(nullable NSError *)error completion:(void (^)(NSError *_Nullable))completion { NSDictionary *errorJSON = error.userInfo[OIDOAuthErrorResponseErrorKey]; @@ -94,6 +112,22 @@ + (NSDictionary *)parametersWithParameters:(NSDictionary *)parameters return allParameters; } +#pragma mark - GTMAuthSessionDelegate + +- (nullable NSDictionary *) +additionalTokenRefreshParametersForAuthSession:(GTMAuthSession *)authSession { + return [GIDEMMSupport updatedEMMParametersWithParameters: + authSession.authState.lastTokenResponse.additionalParameters]; +} + +- (void)updateErrorForAuthSession:(GTMAuthSession *)authSession + originalError:(NSError *)originalError + completion:(void (^)(NSError * _Nullable))completion { + [GIDEMMSupport handleTokenFetchEMMError:originalError completion:^(NSError *_Nullable error) { + completion(error); + }]; +} + @end NS_ASSUME_NONNULL_END diff --git a/GoogleSignIn/Sources/GIDGoogleUser.m b/GoogleSignIn/Sources/GIDGoogleUser.m index 828ac5b6..ec300839 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser.m +++ b/GoogleSignIn/Sources/GIDGoogleUser.m @@ -19,7 +19,6 @@ #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDConfiguration.h" #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h" -#import "GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h" #import "GoogleSignIn/Sources/GIDAuthentication.h" #import "GoogleSignIn/Sources/GIDEMMSupport.h" #import "GoogleSignIn/Sources/GIDProfileData_Private.h" @@ -27,6 +26,8 @@ #import "GoogleSignIn/Sources/GIDSignInPreferences.h" #import "GoogleSignIn/Sources/GIDToken_Private.h" +@import GTMAppAuth; + #ifdef SWIFT_PACKAGE @import AppAuth; #else @@ -52,6 +53,14 @@ // Minimal time interval before expiration for the access token or it needs to be refreshed. static NSTimeInterval const kMinimalTimeToExpire = 60.0; +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST +@interface GIDGoogleUser () + +@property (nonatomic, strong) id authSessionDelegate; + +@end +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST + @implementation GIDGoogleUser { GIDConfiguration *_cachedConfiguration; @@ -179,8 +188,8 @@ - (void)refreshTokensIfNeededWithCompletion:(GIDGoogleUserCompletion)completion }]; } -- (OIDAuthState *) authState{ - return ((GTMAppAuthFetcherAuthorization *)self.fetcherAuthorizer).authState; +- (OIDAuthState *)authState { + return ((GTMAuthSession *)self.fetcherAuthorizer).authState; } - (void)addScopes:(NSArray *)scopes @@ -228,17 +237,13 @@ - (instancetype)initWithAuthState:(OIDAuthState *)authState _tokenRefreshHandlerQueue = [[NSMutableArray alloc] init]; _profile = profileData; + GTMAuthSession *authSession = [[GTMAuthSession alloc] initWithAuthState:authState]; #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - GTMAppAuthFetcherAuthorization *authorization = self.emmSupport ? - [[GIDAppAuthFetcherAuthorizationWithEMMSupport alloc] initWithAuthState:authState] : - [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState]; -#elif TARGET_OS_OSX || TARGET_OS_MACCATALYST - GTMAppAuthFetcherAuthorization *authorization = - [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState]; + _authSessionDelegate = [[GIDEMMSupport alloc] init]; + authSession.delegate = _authSessionDelegate; #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST - authorization.tokenRefreshDelegate = self; - authorization.authState.stateChangeDelegate = self; - self.fetcherAuthorizer = authorization; + authSession.authState.stateChangeDelegate = self; + _fetcherAuthorizer = authSession; [self updateTokensWithAuthState:authState]; } @@ -304,18 +309,6 @@ - (nullable NSString *)hostedDomain { return nil; } -#pragma mark - GTMAppAuthFetcherAuthorizationTokenRefreshDelegate - -- (nullable NSDictionary *)additionalRefreshParameters: - (GTMAppAuthFetcherAuthorization *)authorization { -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - return [GIDEMMSupport updatedEMMParametersWithParameters: - authorization.authState.lastTokenResponse.request.additionalParameters]; -#elif TARGET_OS_OSX || TARGET_OS_MACCATALYST - return authorization.authState.lastTokenResponse.request.additionalParameters; -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST -} - #pragma mark - OIDAuthStateChangeDelegate - (void)didChangeState:(OIDAuthState *)state { diff --git a/GoogleSignIn/Sources/GIDGoogleUser_Private.h b/GoogleSignIn/Sources/GIDGoogleUser_Private.h index 94a019ae..f07a1045 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser_Private.h +++ b/GoogleSignIn/Sources/GIDGoogleUser_Private.h @@ -18,10 +18,8 @@ #ifdef SWIFT_PACKAGE @import AppAuth; -@import GTMAppAuth; #else #import -#import #endif @class OIDAuthState; @@ -31,9 +29,8 @@ NS_ASSUME_NONNULL_BEGIN /// A completion block that takes a `GIDGoogleUser` or an error if the attempt to refresh tokens was unsuccessful. typedef void (^GIDGoogleUserCompletion)(GIDGoogleUser *_Nullable user, NSError *_Nullable error); -// Internal methods for the class that are not part of the public API. -@interface GIDGoogleUser () +/// Internal methods for the class that are not part of the public API. +@interface GIDGoogleUser () @property(nonatomic, readwrite) GIDToken *accessToken; @@ -41,7 +38,7 @@ typedef void (^GIDGoogleUserCompletion)(GIDGoogleUser *_Nullable user, NSError * @property(nonatomic, readwrite, nullable) GIDToken *idToken; -// A representation of the state of the OAuth session for this instance. +/// A representation of the state of the OAuth session for this instance. @property(nonatomic, readonly) OIDAuthState *authState; #pragma clang diagnostic push diff --git a/GoogleSignIn/Sources/GIDSignIn.m b/GoogleSignIn/Sources/GIDSignIn.m index f4cdf1af..4b942ed9 100644 --- a/GoogleSignIn/Sources/GIDSignIn.m +++ b/GoogleSignIn/Sources/GIDSignIn.m @@ -36,9 +36,10 @@ #import "GoogleSignIn/Sources/GIDProfileData_Private.h" #import "GoogleSignIn/Sources/GIDSignInResult_Private.h" +@import GTMAppAuth; + #ifdef SWIFT_PACKAGE @import AppAuth; -@import GTMAppAuth; @import GTMSessionFetcherCore; #else #import @@ -53,8 +54,6 @@ #import #import #import -#import -#import #import #if TARGET_OS_IOS || TARGET_OS_MACCATALYST @@ -166,6 +165,8 @@ @implementation GIDSignIn { id _currentAuthorizationFlow; // Flag to indicate that the auth flow is restarting. BOOL _restarting; + // Keychain manager for GTMAppAuth + GTMKeychainStore *_keychainStore; } #pragma mark - Public methods @@ -449,7 +450,7 @@ + (GIDSignIn *)sharedInstance { #pragma mark - Private methods -- (id)initPrivate { +- (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore { self = [super init]; if (self) { // Get the bundle of the current executable. @@ -459,7 +460,7 @@ - (id)initPrivate { if (bundle) { _configuration = [GIDSignIn configurationFromBundle:bundle]; } - + // Check to see if the 3P app is being run for the first time after a fresh install. BOOL isFreshInstall = [self isFreshInstall]; @@ -475,18 +476,27 @@ - (id)initPrivate { _appAuthConfiguration = [[OIDServiceConfiguration alloc] initWithAuthorizationEndpoint:[NSURL URLWithString:authorizationEnpointURL] tokenEndpoint:[NSURL URLWithString:tokenEndpointURL]]; + _keychainStore = keychainStore; #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST // Perform migration of auth state from old (before 5.0) versions of the SDK if needed. - [GIDAuthStateMigration migrateIfNeededWithTokenURL:_appAuthConfiguration.tokenEndpoint - callbackPath:kBrowserCallbackPath - keychainName:kGTMAppAuthKeychainName - isFreshInstall:isFreshInstall]; + GIDAuthStateMigration *migration = + [[GIDAuthStateMigration alloc] initWithKeychainStore:_keychainStore]; + [migration migrateIfNeededWithTokenURL:_appAuthConfiguration.tokenEndpoint + callbackPath:kBrowserCallbackPath + keychainName:kGTMAppAuthKeychainName + isFreshInstall:isFreshInstall]; #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST } return self; } +- (instancetype)initPrivate { + GTMKeychainStore *keychainStore = + [[GTMKeychainStore alloc] initWithItemName:kGTMAppAuthKeychainName]; + return [self initWithKeychainStore:keychainStore]; +} + // Does sanity check for parameters and then authenticates if necessary. - (void)signInWithOptions:(GIDSignInInternalOptions *)options { // Options for continuation are not the options we want to cache. The purpose of caching the @@ -872,8 +882,7 @@ - (void)startFetchURL:(NSURL *)URL withCompletionHandler:(void (^)(NSData *, NSError *))handler { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; GTMSessionFetcher *fetcher; - GTMAppAuthFetcherAuthorization *authorization = - [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState]; + GTMAuthSession *authorization = [[GTMAuthSession alloc] initWithAuthState:authState]; id fetcherService = authorization.fetcherService; if (fetcherService) { fetcher = [fetcherService fetcherWithRequest:request]; @@ -977,22 +986,18 @@ - (BOOL)isFreshInstall { } - (void)removeAllKeychainEntries { - [GTMAppAuthFetcherAuthorization removeAuthorizationFromKeychainForName:kGTMAppAuthKeychainName - useDataProtectionKeychain:YES]; + [_keychainStore removeAuthSessionWithError:nil]; } - (BOOL)saveAuthState:(OIDAuthState *)authState { - GTMAppAuthFetcherAuthorization *authorization = - [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState]; - return [GTMAppAuthFetcherAuthorization saveAuthorization:authorization - toKeychainForName:kGTMAppAuthKeychainName - useDataProtectionKeychain:YES]; + GTMAuthSession *authorization = [[GTMAuthSession alloc] initWithAuthState:authState]; + NSError *error; + [_keychainStore saveAuthSession:authorization error:&error]; + return error == nil; } - (OIDAuthState *)loadAuthState { - GTMAppAuthFetcherAuthorization *authorization = - [GTMAppAuthFetcherAuthorization authorizationFromKeychainForName:kGTMAppAuthKeychainName - useDataProtectionKeychain:YES]; + GTMAuthSession *authorization = [_keychainStore retrieveAuthSessionWithError:nil]; return authorization.authState; } diff --git a/GoogleSignIn/Sources/GIDSignIn_Private.h b/GoogleSignIn/Sources/GIDSignIn_Private.h index 98dd0aae..2fb71ae4 100644 --- a/GoogleSignIn/Sources/GIDSignIn_Private.h +++ b/GoogleSignIn/Sources/GIDSignIn_Private.h @@ -28,6 +28,7 @@ NS_ASSUME_NONNULL_BEGIN @class GIDGoogleUser; @class GIDSignInInternalOptions; +@class GTMKeychainStore; /// Represents a completion block that takes a `GIDSignInResult` on success or an error if the /// operation was unsuccessful. @@ -46,6 +47,9 @@ typedef void (^GIDDisconnectCompletion)(NSError *_Nullable error); /// Private initializer for |GIDSignIn|. - (instancetype)initPrivate; +/// Private initializer taking a `GTMKeychainStore` to use during tests. +- (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore; + /// Authenticates with extra options. - (void)signInWithOptions:(GIDSignInInternalOptions *)options; diff --git a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h index 434f0e56..94590e00 100644 --- a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h +++ b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h @@ -23,12 +23,10 @@ #import #endif -// We have to import GTMAppAuth because forward declaring the protocol does -// not generate the `fetcherAuthorizer` property below for Swift. #ifdef SWIFT_PACKAGE -@import GTMAppAuth; +@import GTMSessionFetcherCore; #else -#import +#import #endif @class GIDConfiguration; diff --git a/GoogleSignIn/Tests/Unit/GIDAuthStateMigrationTest.m b/GoogleSignIn/Tests/Unit/GIDAuthStateMigrationTest.m index 0ec70759..85ff7cca 100644 --- a/GoogleSignIn/Tests/Unit/GIDAuthStateMigrationTest.m +++ b/GoogleSignIn/Tests/Unit/GIDAuthStateMigrationTest.m @@ -17,15 +17,13 @@ #import "GoogleSignIn/Sources/GIDAuthStateMigration.h" #import "GoogleSignIn/Sources/GIDSignInCallbackSchemes.h" +@import GTMAppAuth; + #ifdef SWIFT_PACKAGE @import AppAuth; -@import GTMAppAuth; @import OCMock; #else #import -#import -#import -#import #import #endif @@ -58,11 +56,15 @@ @interface GIDAuthStateMigration () -+ (nullable GTMAppAuthFetcherAuthorization *) - extractAuthorizationWithTokenURL:(NSURL *)tokenURL callbackPath:(NSString *)callbackPath; - + (nullable NSString *)passwordForService:(NSString *)service; +/// Returns a `GTMAuthSession` given the provided token URL. +/// +/// This method enables using an instance of `GIDAuthStateMigration` that is created with a fake +/// `GTMKeychainStore` and thereby minimizes mocking. +- (nullable GTMAuthSession *) + extractAuthSessionWithTokenURL:(NSURL *)tokenURL callbackPath:(NSString *)callbackPath; + @end @interface GIDAuthStateMigrationTest : XCTestCase @@ -72,22 +74,24 @@ @implementation GIDAuthStateMigrationTest { id _mockUserDefaults; id _mockGTMAppAuthFetcherAuthorization; id _mockGIDAuthStateMigration; - id _mockGTMKeychain; + id _mockGTMKeychainStore; + id _mockKeychainHelper; id _mockNSBundle; id _mockGIDSignInCallbackSchemes; - id _mockGTMOAuth2KeychainCompatibility; + id _mockGTMOAuth2Compatibility; } - (void)setUp { [super setUp]; - _mockUserDefaults = OCMStrictClassMock([NSUserDefaults class]); - _mockGTMAppAuthFetcherAuthorization = OCMStrictClassMock([GTMAppAuthFetcherAuthorization class]); + _mockUserDefaults = OCMClassMock([NSUserDefaults class]); + _mockGTMAppAuthFetcherAuthorization = OCMStrictClassMock([GTMAuthSession class]); _mockGIDAuthStateMigration = OCMStrictClassMock([GIDAuthStateMigration class]); - _mockGTMKeychain = OCMStrictClassMock([GTMKeychain class]); + _mockGTMKeychainStore = OCMStrictClassMock([GTMKeychainStore class]); + _mockKeychainHelper = OCMProtocolMock(@protocol(GTMKeychainHelper)); _mockNSBundle = OCMStrictClassMock([NSBundle class]); _mockGIDSignInCallbackSchemes = OCMStrictClassMock([GIDSignInCallbackSchemes class]); - _mockGTMOAuth2KeychainCompatibility = OCMStrictClassMock([GTMOAuth2KeychainCompatibility class]); + _mockGTMOAuth2Compatibility = OCMStrictClassMock([GTMOAuth2Compatibility class]); } - (void)tearDown { @@ -97,14 +101,16 @@ - (void)tearDown { [_mockGTMAppAuthFetcherAuthorization stopMocking]; [_mockGIDAuthStateMigration verify]; [_mockGIDAuthStateMigration stopMocking]; - [_mockGTMKeychain verify]; - [_mockGTMKeychain stopMocking]; + [_mockGTMKeychainStore verify]; + [_mockGTMKeychainStore stopMocking]; + [_mockKeychainHelper verify]; + [_mockKeychainHelper stopMocking]; [_mockNSBundle verify]; [_mockNSBundle stopMocking]; [_mockGIDSignInCallbackSchemes verify]; [_mockGIDSignInCallbackSchemes stopMocking]; - [_mockGTMOAuth2KeychainCompatibility verify]; - [_mockGTMOAuth2KeychainCompatibility stopMocking]; + [_mockGTMOAuth2Compatibility verify]; + [_mockGTMOAuth2Compatibility stopMocking]; [super tearDown]; } @@ -113,44 +119,49 @@ - (void)tearDown { - (void)testMigrateIfNeeded_NoPreviousMigration { [[[_mockUserDefaults stub] andReturn:_mockUserDefaults] standardUserDefaults]; - [[[_mockUserDefaults expect] andReturnValue:@NO] - boolForKey:kMigrationCheckPerformedKey]; - [[[_mockGIDAuthStateMigration expect] andReturn:_mockGTMAppAuthFetcherAuthorization] - extractAuthorizationWithTokenURL:[NSURL URLWithString:kTokenURL] callbackPath:kCallbackPath]; - [[[_mockGTMAppAuthFetcherAuthorization expect] andReturnValue:@YES] - saveAuthorization:_mockGTMAppAuthFetcherAuthorization toKeychainForName:kKeychainName]; + [[[_mockUserDefaults expect] andReturnValue:@NO] boolForKey:kMigrationCheckPerformedKey]; [[_mockUserDefaults expect] setBool:YES forKey:kMigrationCheckPerformedKey]; - [GIDAuthStateMigration migrateIfNeededWithTokenURL:[NSURL URLWithString:kTokenURL] - callbackPath:kCallbackPath - keychainName:kKeychainName - isFreshInstall:NO]; + [[_mockGTMKeychainStore expect] saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]; + + [self setUpCommonExtractAuthorizationMocksWithFingerPrint:kSavedFingerprint]; + + GIDAuthStateMigration *migration = + [[GIDAuthStateMigration alloc] initWithKeychainStore:_mockGTMKeychainStore]; + [migration migrateIfNeededWithTokenURL:[NSURL URLWithString:kTokenURL] + callbackPath:kCallbackPath + keychainName:kKeychainName + isFreshInstall:NO]; } - (void)testMigrateIfNeeded_HasPreviousMigration { [[[_mockUserDefaults stub] andReturn:_mockUserDefaults] standardUserDefaults]; - [[[_mockUserDefaults expect] andReturnValue:@YES] - boolForKey:kMigrationCheckPerformedKey]; - - [GIDAuthStateMigration migrateIfNeededWithTokenURL:[NSURL URLWithString:kTokenURL] - callbackPath:kCallbackPath - keychainName:kKeychainName - isFreshInstall:NO]; + [[[_mockUserDefaults expect] andReturnValue:@YES] boolForKey:kMigrationCheckPerformedKey]; + [[_mockUserDefaults reject] setBool:YES forKey:kMigrationCheckPerformedKey]; + + GIDAuthStateMigration *migration = + [[GIDAuthStateMigration alloc] initWithKeychainStore:_mockGTMKeychainStore]; + [migration migrateIfNeededWithTokenURL:[NSURL URLWithString:kTokenURL] + callbackPath:kCallbackPath + keychainName:kKeychainName + isFreshInstall:NO]; } - (void)testMigrateIfNeeded_KeychainFailure { [[[_mockUserDefaults stub] andReturn:_mockUserDefaults] standardUserDefaults]; - [[[_mockUserDefaults expect] andReturnValue:@NO] - boolForKey:kMigrationCheckPerformedKey]; - [[[_mockGIDAuthStateMigration expect] andReturn:_mockGTMAppAuthFetcherAuthorization] - extractAuthorizationWithTokenURL:[NSURL URLWithString:kTokenURL] callbackPath:kCallbackPath]; - [[[_mockGTMAppAuthFetcherAuthorization expect] andReturnValue:[NSNumber numberWithBool:NO]] - saveAuthorization:_mockGTMAppAuthFetcherAuthorization toKeychainForName:kKeychainName]; - - [GIDAuthStateMigration migrateIfNeededWithTokenURL:[NSURL URLWithString:kTokenURL] - callbackPath:kCallbackPath - keychainName:kKeychainName - isFreshInstall:NO]; + [[[_mockUserDefaults expect] andReturnValue:@NO] boolForKey:kMigrationCheckPerformedKey]; + + NSError *keychainSaveError = [NSError new]; + OCMStub([_mockGTMKeychainStore saveAuthSession:OCMOCK_ANY error:[OCMArg setTo:keychainSaveError]]); + + [self setUpCommonExtractAuthorizationMocksWithFingerPrint:kSavedFingerprint]; + + GIDAuthStateMigration *migration = + [[GIDAuthStateMigration alloc] initWithKeychainStore:_mockGTMKeychainStore]; + [migration migrateIfNeededWithTokenURL:[NSURL URLWithString:kTokenURL] + callbackPath:kCallbackPath + keychainName:kKeychainName + isFreshInstall:NO]; } - (void)testMigrateIfNeeded_isFreshInstall { @@ -159,18 +170,36 @@ - (void)testMigrateIfNeeded_isFreshInstall { boolForKey:kMigrationCheckPerformedKey]; [[_mockUserDefaults expect] setBool:YES forKey:kMigrationCheckPerformedKey]; - [GIDAuthStateMigration migrateIfNeededWithTokenURL:[NSURL URLWithString:kTokenURL] - callbackPath:kCallbackPath - keychainName:kKeychainName - isFreshInstall:YES]; + GIDAuthStateMigration *migration = + [[GIDAuthStateMigration alloc] initWithKeychainStore:_mockGTMKeychainStore]; + [migration migrateIfNeededWithTokenURL:[NSURL URLWithString:kTokenURL] + callbackPath:kCallbackPath + keychainName:kKeychainName + isFreshInstall:YES]; } - (void)testExtractAuthorization { - [self extractAuthorizationWithFingerprint:kSavedFingerprint]; + [self setUpCommonExtractAuthorizationMocksWithFingerPrint:kSavedFingerprint]; + + GIDAuthStateMigration *migration = + [[GIDAuthStateMigration alloc] initWithKeychainStore:_mockGTMKeychainStore]; + GTMAuthSession *authorization = + [migration extractAuthSessionWithTokenURL:[NSURL URLWithString:kTokenURL] + callbackPath:kCallbackPath]; + + XCTAssertNotNil(authorization); } - (void)testExtractAuthorization_HostedDomain { - [self extractAuthorizationWithFingerprint:kSavedFingerprint_HostedDomain]; + [self setUpCommonExtractAuthorizationMocksWithFingerPrint:kSavedFingerprint_HostedDomain]; + + GIDAuthStateMigration *migration = + [[GIDAuthStateMigration alloc] initWithKeychainStore:_mockGTMKeychainStore]; + GTMAuthSession *authorization = + [migration extractAuthSessionWithTokenURL:[NSURL URLWithString:kTokenURL] + callbackPath:kCallbackPath]; + + XCTAssertNotNil(authorization); } #pragma mark - Helpers @@ -180,12 +209,12 @@ - (NSString *)additionalTokenRequestParametersKeyFromFingerprint:(NSString *)fin return [NSString stringWithFormat:@"%@%@", fingerprint, kAdditionalTokenRequestParametersPostfix]; } -// The parameterized extractAuthorization test. -- (void)extractAuthorizationWithFingerprint:(NSString *)fingerprint { +- (void)setUpCommonExtractAuthorizationMocksWithFingerPrint:(NSString *)fingerprint { [[[_mockGIDAuthStateMigration expect] andReturn:fingerprint] passwordForService:kFingerprintService]; - [[[_mockGTMKeychain expect] andReturn:kGTMOAuth2PersistenceString] - passwordFromKeychainForName:fingerprint]; + (void)[[[_mockKeychainHelper expect] andReturn:kGTMOAuth2PersistenceString] + passwordForService:fingerprint error:OCMArg.anyObjectRef]; + [[[_mockGTMKeychainStore expect] andReturn:_mockKeychainHelper] keychainHelper]; [[[_mockNSBundle expect] andReturn:_mockNSBundle] mainBundle]; [[[_mockNSBundle expect] andReturn:kBundleID] bundleIdentifier]; [[[_mockGIDSignInCallbackSchemes expect] andReturn:_mockGIDSignInCallbackSchemes] alloc]; @@ -194,18 +223,6 @@ - (void)extractAuthorizationWithFingerprint:(NSString *)fingerprint { [[[_mockGIDSignInCallbackSchemes expect] andReturn:kDotReversedClientID] clientIdentifierScheme]; [[[_mockGIDAuthStateMigration expect] andReturn:kAdditionalTokenRequestParameters] passwordForService:[self additionalTokenRequestParametersKeyFromFingerprint:fingerprint]]; - [[[_mockGTMOAuth2KeychainCompatibility expect] andReturn:_mockGTMAppAuthFetcherAuthorization] - authorizeFromPersistenceString:kFinalPersistenceString - tokenURL:[NSURL URLWithString:kTokenURL] - redirectURI:kRedirectURI - clientID:kClientID - clientSecret:nil]; - - GTMAppAuthFetcherAuthorization *authorization = - [GIDAuthStateMigration extractAuthorizationWithTokenURL:[NSURL URLWithString:kTokenURL] - callbackPath:kCallbackPath]; - - XCTAssertNotNil(authorization); } @end diff --git a/GoogleSignIn/Tests/Unit/GIDEMMErrorHandlerTest.m b/GoogleSignIn/Tests/Unit/GIDEMMErrorHandlerTest.m index 0b3a5685..b51519c6 100644 --- a/GoogleSignIn/Tests/Unit/GIDEMMErrorHandlerTest.m +++ b/GoogleSignIn/Tests/Unit/GIDEMMErrorHandlerTest.m @@ -21,6 +21,7 @@ #import "GoogleSignIn/Sources/GIDEMMErrorHandler.h" #import "GoogleSignIn/Sources/GIDSignInStrings.h" +#import "GoogleSignIn/Tests/Unit/UIAlertAction+Testing.h" #ifdef SWIFT_PACKAGE @import GoogleUtilities_MethodSwizzler; @@ -34,22 +35,6 @@ NS_ASSUME_NONNULL_BEGIN -// Addtional methods added to UIAlertAction for testing. -@interface UIAlertAction (Testing) - -// Returns the handler block for this alert action. -- (void (^)(UIAlertAction *))actionHandler; - -@end - -@implementation UIAlertAction (Testing) - -- (void (^)(UIAlertAction *))actionHandler { - return [self valueForKey:@"handler"]; -} - -@end - // Unit test for GIDEMMErrorHandler. @interface GIDEMMErrorHandlerTest : XCTestCase @end diff --git a/GoogleSignIn/Tests/Unit/GIDEMMSupportTest.m b/GoogleSignIn/Tests/Unit/GIDEMMSupportTest.m index aaf5c71e..990aa733 100644 --- a/GoogleSignIn/Tests/Unit/GIDEMMSupportTest.m +++ b/GoogleSignIn/Tests/Unit/GIDEMMSupportTest.m @@ -18,14 +18,21 @@ #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST +#import #import #import "GoogleSignIn/Sources/GIDEMMSupport.h" +#import "GoogleSignIn/Sources/GIDGoogleUser_Private.h" #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h" #import "GoogleSignIn/Sources/GIDEMMErrorHandler.h" #import "GoogleSignIn/Sources/GIDMDMPasscodeState.h" +#import "GoogleSignIn/Tests/Unit/OIDAuthState+Testing.h" +#import "GoogleSignIn/Tests/Unit/GIDFailingOIDAuthState.h" +#import "GoogleSignIn/Tests/Unit/GIDFakeFetcher.h" +#import "GoogleSignIn/Tests/Unit/GIDFakeFetcherService.h" +#import "GoogleSignIn/Tests/Unit/UIAlertAction+Testing.h" #ifdef SWIFT_PACKAGE @import AppAuth; @@ -53,10 +60,61 @@ static NSString *const kEMMPasscodeInfoKey = @"emm_passcode_info"; @interface GIDEMMSupportTest : XCTestCase + // The view controller that has been presented, if any. +@property(nonatomic, strong, nullable) UIViewController *presentedViewController; + @end @implementation GIDEMMSupportTest +- (void)testEMMSupportDelegate { + [self setupSwizzlers]; + XCTestExpectation *emmErrorExpectation = [self expectationWithDescription:@"EMM AppAuth error"]; + + GIDFailingOIDAuthState *failingAuthState = [GIDFailingOIDAuthState testInstance]; + GIDGoogleUser *user = [[GIDGoogleUser alloc] initWithAuthState:failingAuthState profileData:nil]; + GIDFakeFetcherService *fakeFetcherService = [[GIDFakeFetcherService alloc] + initWithAuthorizer:user.fetcherAuthorizer]; + + NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@""]]; + GTMSessionFetcher *fetcher = [fakeFetcherService fetcherWithRequest:request]; + + [fetcher beginFetchWithCompletionHandler:^(NSData * _Nullable data, NSError * _Nullable error) { + XCTAssertNotNil(error); + NSDictionary *userInfo = @{ + @"OIDOAuthErrorResponseErrorKey": @{@"error": @"emm_passcode_required"}, + NSUnderlyingErrorKey: [NSError errorWithDomain:@"SomeUnderlyingError" code:0 userInfo:nil] + }; + NSError *expectedError = [NSError errorWithDomain:kGIDSignInErrorDomain + code:kGIDSignInErrorCodeEMM + userInfo:userInfo]; + XCTAssertEqualObjects(expectedError, error); + [emmErrorExpectation fulfill]; + }]; + + // Wait for the code under test to be executed on the main thread. + XCTestExpectation *mainThreadExpectation = + [self expectationWithDescription:@"wait for main thread"]; + dispatch_async(dispatch_get_main_queue(), ^() { + [mainThreadExpectation fulfill]; + }); + [self waitForExpectations:@[mainThreadExpectation] timeout:1]; + + XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]); + UIAlertController *alert = (UIAlertController *)_presentedViewController; + XCTAssertNotNil(alert.title); + XCTAssertNotNil(alert.message); + XCTAssertEqual(alert.actions.count, 2); + + // Pretend to touch the "Cancel" button. + UIAlertAction *action = alert.actions[0]; + XCTAssertEqualObjects(action.title, @"Cancel"); + action.actionHandler(action); + + [self waitForExpectations:@[emmErrorExpectation] timeout:1]; + [self unswizzle]; +} + - (void)testUpdatedEMMParametersWithParameters_NoEMMKey { NSDictionary *originalParameters = @{ @"not_emm_support_key" : @"xyz", @@ -222,6 +280,28 @@ - (NSString *)systemVersion { return [UIDevice currentDevice].systemVersion; } +- (void)setupSwizzlers { + UIWindow *fakeKeyWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + [GULSwizzler swizzleClass:[GIDEMMErrorHandler class] + selector:@selector(keyWindow) + isClassSelector:NO + withBlock:^() { return fakeKeyWindow; }]; + [GULSwizzler swizzleClass:[UIViewController class] + selector:@selector(presentViewController:animated:completion:) + isClassSelector:NO + withBlock:^(id obj, id arg1) { self->_presentedViewController = arg1; }]; +} + +- (void)unswizzle { + [GULSwizzler unswizzleClass:[GIDEMMErrorHandler class] + selector:@selector(keyWindow) + isClassSelector:NO]; + [GULSwizzler unswizzleClass:[UIViewController class] + selector:@selector(presentViewController:animated:completion:) + isClassSelector:NO]; + self.presentedViewController = nil; +} + @end NS_ASSUME_NONNULL_END diff --git a/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h b/GoogleSignIn/Tests/Unit/GIDFailingOIDAuthState.h similarity index 63% rename from GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h rename to GoogleSignIn/Tests/Unit/GIDFailingOIDAuthState.h index baffe3c1..b2f3c4f0 100644 --- a/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h +++ b/GoogleSignIn/Tests/Unit/GIDFailingOIDAuthState.h @@ -1,5 +1,5 @@ /* - * Copyright 2022 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,23 +14,16 @@ * limitations under the License. */ -#import - -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - #ifdef SWIFT_PACKAGE -@import GTMAppAuth; +@import AppAuth; #else -#import +#import #endif NS_ASSUME_NONNULL_BEGIN -// A specialized GTMAppAuthFetcherAuthorization subclass with EMM support. -@interface GIDAppAuthFetcherAuthorizationWithEMMSupport : GTMAppAuthFetcherAuthorization +@interface GIDFailingOIDAuthState : OIDAuthState @end NS_ASSUME_NONNULL_END - -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST diff --git a/GoogleSignIn/Tests/Unit/GIDFailingOIDAuthState.m b/GoogleSignIn/Tests/Unit/GIDFailingOIDAuthState.m new file mode 100644 index 00000000..fa5267bf --- /dev/null +++ b/GoogleSignIn/Tests/Unit/GIDFailingOIDAuthState.m @@ -0,0 +1,54 @@ +/* + * Copyright 2023 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 "GIDFailingOIDAuthState.h" +#import "GoogleSignIn/Tests/Unit/OIDAuthState+Testing.h" +#import "GoogleSignIn/Tests/Unit/OIDAuthorizationResponse+Testing.h" +#import "GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h" + +@implementation GIDFailingOIDAuthState + ++ (instancetype)testInstance { + OIDAuthorizationResponse *testAuthResponse = [OIDAuthorizationResponse testInstance]; + OIDTokenResponse *testTokenResponse = [OIDTokenResponse testInstance]; + + return [[GIDFailingOIDAuthState alloc] initWithAuthorizationResponse:testAuthResponse + tokenResponse:testTokenResponse]; +} + +- (void)performActionWithFreshTokens:(OIDAuthStateAction)action + additionalRefreshParameters: + (nullable NSDictionary *)additionalParameters { + [self performActionWithFreshTokens:action + additionalRefreshParameters:additionalParameters + dispatchQueue:dispatch_get_main_queue()]; +} + +// Forces the OIDAuthState to fail with the error below to simulate an error handled by EMM support +- (void)performActionWithFreshTokens:(OIDAuthStateAction)action + additionalRefreshParameters:(NSDictionary *)additionalParameters + dispatchQueue:(dispatch_queue_t)dispatchQueue { + NSDictionary *userInfo = @{ + @"OIDOAuthErrorResponseErrorKey": @{@"error": @"emm_passcode_required"}, + NSUnderlyingErrorKey: [NSError errorWithDomain:@"SomeUnderlyingError" code:0 userInfo:nil] + }; + NSError *error = [NSError errorWithDomain:@"org.openid.appauth.resourceserver" + code:0 + userInfo:userInfo]; + action(nil, nil, error); +} + +@end diff --git a/GoogleSignIn/Tests/Unit/GIDFakeFetcher.h b/GoogleSignIn/Tests/Unit/GIDFakeFetcher.h index 10cd91ef..c31c05eb 100644 --- a/GoogleSignIn/Tests/Unit/GIDFakeFetcher.h +++ b/GoogleSignIn/Tests/Unit/GIDFakeFetcher.h @@ -20,14 +20,20 @@ #import #endif -// A fake |GTMHTTPFetcher| for testing. +/// A fake |GTMHTTPFetcher| for testing. @interface GIDFakeFetcher : GTMSessionFetcher -// The URL of the fetching request. +/// The URL of the fetching request. - (NSURL *)requestURL; // Emulates server returning with data and/or error. - (void)didFinishWithData:(NSData *)data error:(NSError *)error; - (instancetype)initWithRequest:(NSURLRequest *)request; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" +- (instancetype)initWithRequest:(NSURLRequest *)request + authorizer:(id)authorizer; +#pragma clang diagnostic pop @end diff --git a/GoogleSignIn/Tests/Unit/GIDFakeFetcher.m b/GoogleSignIn/Tests/Unit/GIDFakeFetcher.m index 79221f99..80712df0 100644 --- a/GoogleSignIn/Tests/Unit/GIDFakeFetcher.m +++ b/GoogleSignIn/Tests/Unit/GIDFakeFetcher.m @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +@import GTMAppAuth; #import "GoogleSignIn/Tests/Unit/GIDFakeFetcher.h" typedef void (^FetchCompletionHandler)(NSData *, NSError *); @@ -29,6 +30,17 @@ - (instancetype)initWithRequest:(NSURLRequest *)request { return self; } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" +- (instancetype)initWithRequest:(NSURLRequest *)request + authorizer:(id)authorizer { +#pragma clang diagnostic pop + self = [self initWithRequest:request]; + if (self) { + self.authorizer = authorizer; + } + return self; +} - (void)beginFetchWithDelegate:(id)delegate didFinishSelector:(SEL)finishedSEL { [NSException raise:@"NotImplementedException" format:@"Implement this method if it is used"]; @@ -39,6 +51,14 @@ - (void)beginFetchWithCompletionHandler:(FetchCompletionHandler)handler { [NSException raise:NSInvalidArgumentException format:@"Attempted start fetch again"]; } _handler = [handler copy]; + [self authorizeRequestWithCompletion:handler]; +} + +- (void)authorizeRequestWithCompletion:(FetchCompletionHandler)completion { + NSMutableURLRequest *mutableRequest = [NSMutableURLRequest requestWithURL:self.request.URL]; + [self.authorizer authorizeRequest:mutableRequest completionHandler:^(NSError * _Nullable error) { + completion(nil, error); + }]; } - (NSURL *)requestURL { diff --git a/GoogleSignIn/Tests/Unit/GIDFakeFetcherService.h b/GoogleSignIn/Tests/Unit/GIDFakeFetcherService.h index e54aa149..382c310d 100644 --- a/GoogleSignIn/Tests/Unit/GIDFakeFetcherService.h +++ b/GoogleSignIn/Tests/Unit/GIDFakeFetcherService.h @@ -23,7 +23,19 @@ // A fake |GTMHTTPFetcherService| for testing. @interface GIDFakeFetcherService : NSObject -// Returns the list of |GPPFakeFetcher| objects that have been created. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +/// Creates an instance of this fake with an authorizer. +- initWithAuthorizer:(id)authorizer; +#pragma clang diagnostic pop + +/// Returns the list of |GPPFakeFetcher| objects that have been created. - (NSArray *)fetchers; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" +/// The instance to use for authorizing requeests. +@property (nonatomic, strong) id authorizer; +#pragma clang diagnostic pop + @end diff --git a/GoogleSignIn/Tests/Unit/GIDFakeFetcherService.m b/GoogleSignIn/Tests/Unit/GIDFakeFetcherService.m index 002a9172..bab7c34b 100644 --- a/GoogleSignIn/Tests/Unit/GIDFakeFetcherService.m +++ b/GoogleSignIn/Tests/Unit/GIDFakeFetcherService.m @@ -13,7 +13,6 @@ // limitations under the License. #import "GoogleSignIn/Tests/Unit/GIDFakeFetcherService.h" - #import "GoogleSignIn/Tests/Unit/GIDFakeFetcher.h" @implementation GIDFakeFetcherService { @@ -32,6 +31,17 @@ - (instancetype)init { return self; } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" +- (instancetype)initWithAuthorizer:(id)authorizer { +#pragma clang diagnostic pop + self = [self init]; + if (self) { + self.authorizer = authorizer; + } + return self; +} + - (BOOL)fetcherShouldBeginFetching:(GTMSessionFetcher *)fetcher { return YES; } @@ -50,7 +60,8 @@ - (BOOL)isDelayingFetcher:(GTMSessionFetcher *)fetcher { } - (GTMSessionFetcher *)fetcherWithRequest:(NSURLRequest *)request { - GIDFakeFetcher *fetcher = [[GIDFakeFetcher alloc] initWithRequest:request]; + GIDFakeFetcher *fetcher = [[GIDFakeFetcher alloc] initWithRequest:request + authorizer:self.authorizer]; [_fetchers addObject:fetcher]; return fetcher; } diff --git a/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.h b/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.h index 3f833030..5b36f2f5 100644 --- a/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.h +++ b/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.h @@ -16,6 +16,12 @@ #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h" +#ifdef SWIFT_PACKAGE +@import AppAuthCore; +#else +#import +#endif + @interface GIDGoogleUser (Testing) - (BOOL)isEqual:(id)object; diff --git a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m index 3f42afd6..f987c7c1 100644 --- a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m +++ b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m @@ -30,6 +30,8 @@ #import "GoogleSignIn/Tests/Unit/OIDTokenRequest+Testing.h" #import "GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h" +@import GTMAppAuth; + #ifdef SWIFT_PACKAGE @import AppAuth; @import GoogleUtilities_MethodSwizzler; @@ -47,7 +49,6 @@ #import #import #import -#import #import #endif @@ -216,7 +217,7 @@ - (void)testFetcherAuthorizer { #pragma clang diagnostic ignored "-Wdeprecated-declarations" id fetcherAuthorizer = user.fetcherAuthorizer; #pragma clang diagnostic pop - XCTAssertTrue([fetcherAuthorizer isKindOfClass:[GTMAppAuthFetcherAuthorization class]]); + XCTAssertTrue([fetcherAuthorizer isKindOfClass:[GTMAuthSession class]]); XCTAssertTrue([fetcherAuthorizer canAuthorize]); } diff --git a/GoogleSignIn/Tests/Unit/GIDSignInTest.m b/GoogleSignIn/Tests/Unit/GIDSignInTest.m index b778501c..59e3ac75 100644 --- a/GoogleSignIn/Tests/Unit/GIDSignInTest.m +++ b/GoogleSignIn/Tests/Unit/GIDSignInTest.m @@ -26,6 +26,8 @@ // Test module imports @import GoogleSignIn; +@import GTMAppAuth; + #import "GoogleSignIn/Sources/GIDEMMSupport.h" #import "GoogleSignIn/Sources/GIDGoogleUser_Private.h" #import "GoogleSignIn/Sources/GIDSignIn_Private.h" @@ -43,7 +45,6 @@ #ifdef SWIFT_PACKAGE @import AppAuth; -@import GTMAppAuth; @import GTMSessionFetcherCore; @import OCMock; #else @@ -63,8 +64,6 @@ #import #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST -#import -#import #import #import #endif @@ -190,9 +189,12 @@ @interface GIDSignInTest : XCTestCase { // Mock |OIDTokenRequest|. id _tokenRequest; - // Mock |GTMAppAuthFetcherAuthorization|. + // Mock |GTMAuthSession|. id _authorization; + // Mock |GTMKeychainStore|. + id _keychainStore; + #if TARGET_OS_IOS || TARGET_OS_MACCATALYST // Mock |UIViewController|. id _presentingViewController; @@ -289,23 +291,19 @@ - (void)setUp { OCMStub([_authState initWithAuthorizationResponse:OCMOCK_ANY]).andReturn(_authState); _tokenResponse = OCMStrictClassMock([OIDTokenResponse class]); _tokenRequest = OCMStrictClassMock([OIDTokenRequest class]); - _authorization = OCMStrictClassMock([GTMAppAuthFetcherAuthorization class]); - OCMStub([_authorization authorizationFromKeychainForName:OCMOCK_ANY - useDataProtectionKeychain:YES]).andReturn(_authorization); + _authorization = OCMStrictClassMock([GTMAuthSession class]); + _keychainStore = OCMStrictClassMock([GTMKeychainStore class]); + OCMStub( + [_keychainStore retrieveAuthSessionWithItemName:OCMOCK_ANY error:OCMArg.anyObjectRef] + ).andReturn(_authorization); + OCMStub([_keychainStore retrieveAuthSessionWithError:nil]).andReturn(_authorization); OCMStub([_authorization alloc]).andReturn(_authorization); OCMStub([_authorization initWithAuthState:OCMOCK_ANY]).andReturn(_authorization); - OCMStub([_authorization saveAuthorization:OCMOCK_ANY - toKeychainForName:OCMOCK_ANY - useDataProtectionKeychain:YES]) - .andDo(^(NSInvocation *invocation) { - self->_keychainSaved = self->_saveAuthorizationReturnValue; - [invocation setReturnValue:&self->_saveAuthorizationReturnValue]; - }); - OCMStub([_authorization removeAuthorizationFromKeychainForName:OCMOCK_ANY - useDataProtectionKeychain:YES]) - .andDo(^(NSInvocation *invocation) { - self->_keychainRemoved = YES; - }); + OCMStub( + [_keychainStore removeAuthSessionWithError:OCMArg.anyObjectRef] + ).andDo(^(NSInvocation *invocation) { + self->_keychainRemoved = YES; + }); _user = OCMStrictClassMock([GIDGoogleUser class]); _oidAuthorizationService = OCMStrictClassMock([OIDAuthorizationService class]); OCMStub([_oidAuthorizationService @@ -330,7 +328,7 @@ - (void)setUp { [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kAppHasRunBeforeKey]; - _signIn = [[GIDSignIn alloc] initPrivate]; + _signIn = [[GIDSignIn alloc] initWithKeychainStore:_keychainStore]; _hint = nil; __weak GIDSignInTest *weakSelf = self; @@ -414,7 +412,9 @@ - (void)testInitPrivate_invalidConfig { - (void)testRestorePreviousSignInNoRefresh_hasPreviousUser { [[[_authorization stub] andReturn:_authState] authState]; - [[_authorization expect] setTokenRefreshDelegate:OCMOCK_ANY]; +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + [[_authorization expect] setDelegate:OCMOCK_ANY]; +#endif // TARGET_OS_IOS || !TARGET_OS_MACCATALYST OCMStub([_authState lastTokenResponse]).andReturn(_tokenResponse); OCMStub([_authState refreshToken]).andReturn(kRefreshToken); [[_authState expect] setStateChangeDelegate:OCMOCK_ANY]; @@ -438,10 +438,6 @@ - (void)testRestorePreviousSignInNoRefresh_hasPreviousUser { [_signIn restorePreviousSignInNoRefresh]; - [_authorization verify]; - [_authState verify]; - [_tokenResponse verify]; - [_tokenRequest verify]; [idTokenDecoded verify]; XCTAssertEqual(_signIn.currentUser.userID, kFakeGaiaID); @@ -514,6 +510,7 @@ - (void)testNotRestorePreviousSignInWhenSignedOutAndCompletionIsNil { - (void)testRestorePreviousSignInWhenCompletionIsNil { [[[_authorization expect] andReturn:_authState] authState]; + [[_keychainStore expect] saveAuthSession:OCMOCK_ANY error:[OCMArg anyObjectRef]]; [[[_authState expect] andReturnValue:[NSNumber numberWithBool:YES]] isAuthorized]; OIDTokenResponse *tokenResponse = @@ -537,6 +534,12 @@ - (void)testRestorePreviousSignInWhenCompletionIsNil { } - (void)testOAuthLogin { + OCMStub( + [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef] + ).andDo(^(NSInvocation *invocation) { + self->_keychainSaved = self->_saveAuthorizationReturnValue; + }); + [self OAuthLoginWithAddScopesFlow:NO authError:nil tokenError:nil @@ -548,6 +551,12 @@ - (void)testOAuthLogin { } - (void)testOAuthLogin_RestoredSignIn { + OCMStub( + [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef] + ).andDo(^(NSInvocation *invocation) { + self->_keychainSaved = self->_saveAuthorizationReturnValue; + }); + [self OAuthLoginWithAddScopesFlow:NO authError:nil tokenError:nil @@ -559,6 +568,12 @@ - (void)testOAuthLogin_RestoredSignIn { } - (void)testOAuthLogin_RestoredSignInOldAccessToken { + OCMStub( + [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef] + ).andDo(^(NSInvocation *invocation) { + self->_keychainSaved = self->_saveAuthorizationReturnValue; + }); + [self OAuthLoginWithAddScopesFlow:NO authError:nil tokenError:nil @@ -571,7 +586,13 @@ - (void)testOAuthLogin_RestoredSignInOldAccessToken { - (void)testOAuthLogin_AdditionalScopes { NSString *expectedScopeString; - + + OCMStub( + [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef] + ).andDo(^(NSInvocation *invocation) { + self->_keychainSaved = self->_saveAuthorizationReturnValue; + }); + [self OAuthLoginWithAddScopesFlow:NO authError:nil tokenError:nil @@ -617,6 +638,11 @@ - (void)testOAuthLogin_AdditionalScopes { - (void)testAddScopes { // Restore the previous sign-in account. This is the preparation for adding scopes. + OCMStub( + [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef] + ).andDo(^(NSInvocation *invocation) { + self->_keychainSaved = self->_saveAuthorizationReturnValue; + }); [self OAuthLoginWithAddScopesFlow:NO authError:nil tokenError:nil @@ -677,6 +703,12 @@ - (void)testOpenIDRealm { hostedDomain:nil openIDRealm:kOpenIDRealm]; + OCMStub( + [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef] + ).andDo(^(NSInvocation *invocation) { + self->_keychainSaved = self->_saveAuthorizationReturnValue; + }); + [self OAuthLoginWithAddScopesFlow:NO authError:nil tokenError:nil @@ -693,6 +725,12 @@ - (void)testOpenIDRealm { - (void)testOAuthLogin_LoginHint { _hint = kUserEmail; + OCMStub( + [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef] + ).andDo(^(NSInvocation *invocation) { + self->_keychainSaved = self->_saveAuthorizationReturnValue; + }); + [self OAuthLoginWithAddScopesFlow:NO authError:nil tokenError:nil @@ -712,6 +750,12 @@ - (void)testOAuthLogin_HostedDomain { hostedDomain:kHostedDomain openIDRealm:nil]; + OCMStub( + [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef] + ).andDo(^(NSInvocation *invocation) { + self->_keychainSaved = self->_saveAuthorizationReturnValue; + }); + [self OAuthLoginWithAddScopesFlow:NO authError:nil tokenError:nil @@ -754,6 +798,16 @@ - (void)testOAuthLogin_ModalCanceled { } - (void)testOAuthLogin_KeychainError { + // This error is going be overidden by `-[GIDSignIn errorWithString:code:]` + // We just need to fill in the error so that happens. + NSError *keychainError = [NSError errorWithDomain:@"com.googleSignIn.throwAway" + code:1 + userInfo:nil]; + OCMStub( + [_keychainStore saveAuthSession:OCMOCK_ANY error:[OCMArg setTo:keychainError]] + ).andDo(^(NSInvocation *invocation) { + self->_keychainSaved = self->_saveAuthorizationReturnValue; + }); [self OAuthLoginWithAddScopesFlow:NO authError:nil tokenError:nil @@ -770,6 +824,16 @@ - (void)testOAuthLogin_KeychainError { } - (void)testSignOut { +#if TARGET_OS_IOS || !TARGET_OS_MACCATALYST +// OCMStub([_authorization authState]).andReturn(_authState); +#endif // TARGET_OS_IOS || !TARGET_OS_MACCATALYST + OCMStub([_authorization fetcherService]).andReturn(_fetcherService); + OCMStub( + [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef] + ).andDo(^(NSInvocation *invocation) { + self->_keychainSaved = self->_saveAuthorizationReturnValue; + }); + // Sign in a user so that we can then sign them out. [self OAuthLoginWithAddScopesFlow:NO authError:nil @@ -786,8 +850,7 @@ - (void)testSignOut { XCTAssertNil(_signIn.currentUser, @"should not have a current user"); XCTAssertTrue(_keychainRemoved, @"should remove keychain"); - OCMVerify([_authorization removeAuthorizationFromKeychainForName:kKeychainName - useDataProtectionKeychain:YES]); + OCMVerify([_keychainStore removeAuthSessionWithError:OCMArg.anyObjectRef]); } - (void)testNotHandleWrongScheme { @@ -811,14 +874,16 @@ - (void)testDisconnect_accessToken { [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse]; [[[_tokenResponse expect] andReturn:kAccessToken] accessToken]; [[[_authorization expect] andReturn:_fetcherService] fetcherService]; - XCTestExpectation *expectation = + XCTestExpectation *accessTokenExpectation = [self expectationWithDescription:@"Callback called with nil error"]; [_signIn disconnectWithCompletion:^(NSError * _Nullable error) { if (error == nil) { - [expectation fulfill]; + [accessTokenExpectation fulfill]; } }]; - [self verifyAndRevokeToken:kAccessToken hasCallback:YES]; + [self verifyAndRevokeToken:kAccessToken + hasCallback:YES + waitingForExpectations:@[accessTokenExpectation]]; [_authorization verify]; [_authState verify]; [_tokenResponse verify]; @@ -831,7 +896,7 @@ - (void)testDisconnectNoCallback_accessToken { [[[_tokenResponse expect] andReturn:kAccessToken] accessToken]; [[[_authorization expect] andReturn:_fetcherService] fetcherService]; [_signIn disconnectWithCompletion:nil]; - [self verifyAndRevokeToken:kAccessToken hasCallback:NO]; + [self verifyAndRevokeToken:kAccessToken hasCallback:NO waitingForExpectations:@[]]; [_authorization verify]; [_authState verify]; [_tokenResponse verify]; @@ -845,14 +910,16 @@ - (void)testDisconnect_refreshToken { [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse]; [[[_tokenResponse expect] andReturn:kRefreshToken] refreshToken]; [[[_authorization expect] andReturn:_fetcherService] fetcherService]; - XCTestExpectation *expectation = + XCTestExpectation *refreshTokenExpectation = [self expectationWithDescription:@"Callback called with nil error"]; [_signIn disconnectWithCompletion:^(NSError * _Nullable error) { if (error == nil) { - [expectation fulfill]; + [refreshTokenExpectation fulfill]; } }]; - [self verifyAndRevokeToken:kRefreshToken hasCallback:YES]; + [self verifyAndRevokeToken:kRefreshToken + hasCallback:YES + waitingForExpectations:@[refreshTokenExpectation]]; [_authorization verify]; [_authState verify]; [_tokenResponse verify]; @@ -864,18 +931,18 @@ - (void)testDisconnect_errors { [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse]; [[[_tokenResponse expect] andReturn:kAccessToken] accessToken]; [[[_authorization expect] andReturn:_fetcherService] fetcherService]; - XCTestExpectation *expectation = + XCTestExpectation *errorExpectation = [self expectationWithDescription:@"Callback called with an error"]; [_signIn disconnectWithCompletion:^(NSError * _Nullable error) { if (error != nil) { - [expectation fulfill]; + [errorExpectation fulfill]; } }]; XCTAssertTrue([self isFetcherStarted], @"should start fetching"); // Emulate result back from server. NSError *error = [self error]; [self didFetch:nil error:error]; - [self waitForExpectationsWithTimeout:1 handler:nil]; + [self waitForExpectations:@[errorExpectation] timeout:1]; [_authorization verify]; [_authState verify]; [_tokenResponse verify]; @@ -905,14 +972,14 @@ - (void)testDisconnect_noTokens { [[[_tokenResponse expect] andReturn:nil] accessToken]; [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse]; [[[_tokenResponse expect] andReturn:nil] refreshToken]; - XCTestExpectation *expectation = + XCTestExpectation *noTokensExpectation = [self expectationWithDescription:@"Callback called with nil error"]; [_signIn disconnectWithCompletion:^(NSError * _Nullable error) { if (error == nil) { - [expectation fulfill]; + [noTokensExpectation fulfill]; } }]; - [self waitForExpectationsWithTimeout:1 handler:nil]; + [self waitForExpectations:@[noTokensExpectation] timeout:1]; XCTAssertFalse([self isFetcherStarted], @"should not fetch"); XCTAssertTrue(_keychainRemoved, @"keychain should be removed"); [_authorization verify]; @@ -1008,6 +1075,12 @@ - (void)testRequiringPendingSignIn { #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - (void)testEmmSupportRequestParameters { + OCMStub( + [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef] + ).andDo(^(NSInvocation *invocation) { + self->_keychainSaved = self->_saveAuthorizationReturnValue; + }); + [self OAuthLoginWithAddScopesFlow:NO authError:nil tokenError:nil @@ -1053,6 +1126,12 @@ - (void)testEmmSupportRequestParameters { } - (void)testEmmPasscodeInfo { + OCMStub( + [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef] + ).andDo(^(NSInvocation *invocation) { + self->_keychainSaved = self->_saveAuthorizationReturnValue; + }); + [self OAuthLoginWithAddScopesFlow:NO authError:nil tokenError:nil @@ -1181,7 +1260,9 @@ - (NSError *)error { } // Verifies a fetcher has started for revoking token and emulates a server response. -- (void)verifyAndRevokeToken:(NSString *)token hasCallback:(BOOL)hasCallback { +- (void)verifyAndRevokeToken:(NSString *)token + hasCallback:(BOOL)hasCallback + waitingForExpectations:(NSArray *)expectations { XCTAssertTrue([self isFetcherStarted], @"should start fetching"); NSURL *url = [self fetchedURL]; XCTAssertEqualObjects([url scheme], @"https", @"scheme must match"); @@ -1197,10 +1278,10 @@ - (void)verifyAndRevokeToken:(NSString *)token hasCallback:(BOOL)hasCallback { @"Environment logging parameter should match"); // Emulate result back from server. [self didFetch:nil error:nil]; + XCTAssertTrue(_keychainRemoved, @"should clear saved keychain name"); if (hasCallback) { - [self waitForExpectationsWithTimeout:1 handler:nil]; + [self waitForExpectations:expectations timeout:1]; } - XCTAssertTrue(_keychainRemoved, @"should clear saved keychain name"); } - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow @@ -1281,10 +1362,11 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow tokenRefreshRequestWithAdditionalParameters:[OCMArg any]]; } } else { - XCTestExpectation *expectation = [self expectationWithDescription:@"Callback called"]; + XCTestExpectation *newAccessTokenExpectation = + [self expectationWithDescription:@"Callback called"]; GIDSignInCompletion completion = ^(GIDSignInResult *_Nullable signInResult, NSError * _Nullable error) { - [expectation fulfill]; + [newAccessTokenExpectation fulfill]; if (signInResult) { XCTAssertEqualObjects(signInResult.serverAuthCode, kServerAuthCode); } else { @@ -1367,10 +1449,11 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow } if (restoredSignIn && oldAccessToken) { - XCTestExpectation *expectation = [self expectationWithDescription:@"Callback should be called"]; + XCTestExpectation *callbackShouldBeCalledExpectation = + [self expectationWithDescription:@"Callback should be called"]; [_signIn restorePreviousSignInWithCompletion:^(GIDGoogleUser * _Nullable user, NSError * _Nullable error) { - [expectation fulfill]; + [callbackShouldBeCalledExpectation fulfill]; XCTAssertNil(error, @"should have no error"); }]; } @@ -1423,10 +1506,10 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow } if (restoredSignIn && !oldAccessToken) { - XCTestExpectation *expectation = [self expectationWithDescription:@"Callback should be called"]; + XCTestExpectation *restoredSignInExpectation = [self expectationWithDescription:@"Callback should be called"]; [_signIn restorePreviousSignInWithCompletion:^(GIDGoogleUser * _Nullable user, NSError * _Nullable error) { - [expectation fulfill]; + [restoredSignInExpectation fulfill]; XCTAssertNil(error, @"should have no error"); }]; } else { @@ -1463,11 +1546,12 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow __block GIDGoogleUserCompletion completion; [[_user expect] refreshTokensIfNeededWithCompletion:SAVE_TO_ARG_BLOCK(completion)]; - XCTestExpectation *expectation = [self expectationWithDescription:@"Callback should be called"]; + XCTestExpectation *restorePreviousSignInExpectation = + [self expectationWithDescription:@"Callback should be called"]; [_signIn restorePreviousSignInWithCompletion:^(GIDGoogleUser * _Nullable user, NSError * _Nullable error) { - [expectation fulfill]; + [restorePreviousSignInExpectation fulfill]; XCTAssertNil(error, @"should have no error"); }]; @@ -1478,11 +1562,9 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow XCTAssertFalse(_keychainSaved, @"should not save to keychain again"); if (restoredSignIn) { - OCMVerify([_authorization authorizationFromKeychainForName:kKeychainName - useDataProtectionKeychain:YES]); - OCMVerify([_authorization saveAuthorization:OCMOCK_ANY - toKeychainForName:kKeychainName - useDataProtectionKeychain:YES]); + // Ignore the return value + OCMVerify((void)[_keychainStore retrieveAuthSessionWithError:OCMArg.anyObjectRef]); + OCMVerify([_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]); } } diff --git a/GoogleSignIn/Tests/Unit/OIDAuthState+Testing.m b/GoogleSignIn/Tests/Unit/OIDAuthState+Testing.m index c6c694a4..c49b2abd 100644 --- a/GoogleSignIn/Tests/Unit/OIDAuthState+Testing.m +++ b/GoogleSignIn/Tests/Unit/OIDAuthState+Testing.m @@ -46,4 +46,5 @@ + (instancetype)testInstanceWithIDToken:(NSString *)idToken return [self testInstanceWithTokenResponse:newResponse]; } + @end diff --git a/GoogleSignIn/Tests/Unit/UIAlertAction+Testing.h b/GoogleSignIn/Tests/Unit/UIAlertAction+Testing.h new file mode 100644 index 00000000..18716804 --- /dev/null +++ b/GoogleSignIn/Tests/Unit/UIAlertAction+Testing.h @@ -0,0 +1,34 @@ + +// Copyright 2023 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 + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// Helper category for testing EMM support +@interface UIAlertAction (Testing) + +/// Returns the handler block for this alert action. +- (void (^)(UIAlertAction *))actionHandler; + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST diff --git a/GoogleSignIn/Tests/Unit/UIAlertAction+Testing.m b/GoogleSignIn/Tests/Unit/UIAlertAction+Testing.m new file mode 100644 index 00000000..a6b3df01 --- /dev/null +++ b/GoogleSignIn/Tests/Unit/UIAlertAction+Testing.m @@ -0,0 +1,29 @@ +// Copyright 2023 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 + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +#import "UIAlertAction+Testing.h" + +@implementation UIAlertAction (Testing) + +- (void (^)(UIAlertAction *))actionHandler { + return [self valueForKey:@"handler"]; +} + +@end + +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST diff --git a/GoogleSignInSwiftSupport.podspec b/GoogleSignInSwiftSupport.podspec index d0b1e0c3..75fa4866 100644 --- a/GoogleSignInSwiftSupport.podspec +++ b/GoogleSignInSwiftSupport.podspec @@ -33,6 +33,6 @@ Pod::Spec.new do |s| unit_tests.source_files = [ 'GoogleSignInSwift/Tests/Unit/*.swift', ] - unit_tests.requires_app_host = false + unit_tests.requires_app_host = true end end diff --git a/Package.swift b/Package.swift index a8b794f9..8b498cc2 100644 --- a/Package.swift +++ b/Package.swift @@ -44,11 +44,11 @@ let package = Package( .package( name: "AppAuth", url: "https://github.com/openid/AppAuth-iOS.git", - "1.5.0" ..< "2.0.0"), + "1.6.0" ..< "2.0.0"), .package( name: "GTMAppAuth", url: "https://github.com/google/GTMAppAuth.git", - "1.3.0" ..< "3.0.0"), + from: "4.0.0"), .package( name: "GTMSessionFetcher", url: "https://github.com/google/gtm-session-fetcher.git", diff --git a/Samples/ObjC/SignInSample/Podfile b/Samples/ObjC/SignInSample/Podfile index c7df6fc7..003fefe1 100644 --- a/Samples/ObjC/SignInSample/Podfile +++ b/Samples/ObjC/SignInSample/Podfile @@ -1,4 +1,5 @@ platform :ios, '10.0' +use_frameworks! target 'SampleForPod' do pod 'GoogleSignIn', :path => '../../../', :testspecs => ['unit'] diff --git a/Samples/ObjC/SignInSample/SignInSampleForPod.xcodeproj/project.pbxproj b/Samples/ObjC/SignInSample/SignInSampleForPod.xcodeproj/project.pbxproj index 154116db..417d1c30 100644 --- a/Samples/ObjC/SignInSample/SignInSampleForPod.xcodeproj/project.pbxproj +++ b/Samples/ObjC/SignInSample/SignInSampleForPod.xcodeproj/project.pbxproj @@ -7,7 +7,8 @@ objects = { /* Begin PBXBuildFile section */ - CC887BA946C6A9FDA3DE81B5 /* Pods_SampleForPod.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 550F5407FA6F32CFDE2C3C75 /* Pods_SampleForPod.framework */; }; + 1D721EFBF29E4B5D3EA5BE4D /* Pods_SampleForPod.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2675052AF6D7076DCBF751BB /* Pods_SampleForPod.framework */; }; + 7314214E29E7348800D35433 /* Empty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7314214D29E7348800D35433 /* Empty.swift */; }; D926A5701BC3236100ADECE6 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D926A5381BC3236100ADECE6 /* AppDelegate.m */; }; D926A5711BC3236100ADECE6 /* AuthInspectorViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D926A53A1BC3236100ADECE6 /* AuthInspectorViewController.m */; }; D926A5721BC3236100ADECE6 /* DataPickerState.m in Sources */ = {isa = PBXBuildFile; fileRef = D926A53C1BC3236100ADECE6 /* DataPickerState.m */; }; @@ -24,7 +25,8 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 550F5407FA6F32CFDE2C3C75 /* Pods_SampleForPod.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SampleForPod.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2675052AF6D7076DCBF751BB /* Pods_SampleForPod.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SampleForPod.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7314214D29E7348800D35433 /* Empty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Empty.swift; sourceTree = ""; }; C1B5D38B282AFE460068D12B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; D926A51D1BC31FE000ADECE6 /* SignInSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SignInSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; D926A5371BC3236100ADECE6 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Source/AppDelegate.h; sourceTree = SOURCE_ROOT; }; @@ -90,7 +92,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - CC887BA946C6A9FDA3DE81B5 /* Pods_SampleForPod.framework in Frameworks */, + 1D721EFBF29E4B5D3EA5BE4D /* Pods_SampleForPod.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -109,7 +111,7 @@ BB0E907DD016FB9CB6DC01B5 /* Frameworks */ = { isa = PBXGroup; children = ( - 550F5407FA6F32CFDE2C3C75 /* Pods_SampleForPod.framework */, + 2675052AF6D7076DCBF751BB /* Pods_SampleForPod.framework */, ); name = Frameworks; sourceTree = ""; @@ -130,6 +132,7 @@ D926A56D1BC3236100ADECE6 /* SignInViewController.h */, D926A56E1BC3236100ADECE6 /* SignInViewController.m */, D926A56F1BC3236100ADECE6 /* SignInViewController.xib */, + 7314214D29E7348800D35433 /* Empty.swift */, ); path = Source; sourceTree = ""; @@ -178,7 +181,7 @@ D926A5191BC31FE000ADECE6 /* Sources */, D926A51A1BC31FE000ADECE6 /* Frameworks */, D926A51B1BC31FE000ADECE6 /* Resources */, - 819CCDEFD018D376E185A11E /* [CP] Embed Pods Frameworks */, + CA434359EF3A93E8F17342C4 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -200,6 +203,7 @@ TargetAttributes = { D926A51C1BC31FE000ADECE6 = { CreatedOnToolsVersion = 7.0; + LastSwiftMigration = 1420; }; }; }; @@ -296,7 +300,7 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 819CCDEFD018D376E185A11E /* [CP] Embed Pods Frameworks */ = { + CA434359EF3A93E8F17342C4 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -332,6 +336,7 @@ D926A5721BC3236100ADECE6 /* DataPickerState.m in Sources */, D926A5711BC3236100ADECE6 /* AuthInspectorViewController.m in Sources */, D926A57D1BC3236100ADECE6 /* SignInViewController.m in Sources */, + 7314214E29E7348800D35433 /* Empty.swift in Sources */, D926A5701BC3236100ADECE6 /* AppDelegate.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -496,11 +501,15 @@ baseConfigurationReference = FE1798D23522F57E24875676 /* Pods-SampleForPod.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/SignInSample-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.google.SignInSample; PRODUCT_NAME = SignInSample; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -509,11 +518,14 @@ baseConfigurationReference = DAD21F7A41C90200270A7376 /* Pods-SampleForPod.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/SignInSample-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.google.SignInSample; PRODUCT_NAME = SignInSample; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/Samples/ObjC/SignInSample/Source/Empty.swift b/Samples/ObjC/SignInSample/Source/Empty.swift new file mode 100644 index 00000000..8aa2a7a7 --- /dev/null +++ b/Samples/ObjC/SignInSample/Source/Empty.swift @@ -0,0 +1,18 @@ +/* + * Copyright 2023 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. + */ + + +// This is necessary for `SignInSample`'s `Podfile` to use `use_frameworks! :linkage => :static` diff --git a/Samples/Swift/DaysUntilBirthday/DaysUntilBirthdayForPod.xcodeproj/project.pbxproj b/Samples/Swift/DaysUntilBirthday/DaysUntilBirthdayForPod.xcodeproj/project.pbxproj index 8f76c9ff..87f26e0c 100644 --- a/Samples/Swift/DaysUntilBirthday/DaysUntilBirthdayForPod.xcodeproj/project.pbxproj +++ b/Samples/Swift/DaysUntilBirthday/DaysUntilBirthdayForPod.xcodeproj/project.pbxproj @@ -7,8 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 5965643074F45661539BC2DF /* libPods-DaysUntilBirthdayForPod (iOS).a in Frameworks */ = {isa = PBXBuildFile; fileRef = F163BB5E1E81280A79B4E535 /* libPods-DaysUntilBirthdayForPod (iOS).a */; }; - 7023CA98EF5DD94278069A12 /* libPods-DaysUntilBirthdayForPod (macOS).a in Frameworks */ = {isa = PBXBuildFile; fileRef = AE0FE424C7B3C57CF8808F8A /* libPods-DaysUntilBirthdayForPod (macOS).a */; }; + 4C6A2BA2321434ACBD2AF201 /* Pods_DaysUntilBirthdayForPod__macOS_.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 926A15D393D684C68E09FB0F /* Pods_DaysUntilBirthdayForPod__macOS_.framework */; }; 7345AD032703D9470020AFB1 /* DaysUntilBirthday.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7345AD022703D9470020AFB1 /* DaysUntilBirthday.swift */; }; 7345AD052703D9470020AFB1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7345AD042703D9470020AFB1 /* ContentView.swift */; }; 7345AD072703D9480020AFB1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7345AD062703D9480020AFB1 /* Assets.xcassets */; }; @@ -37,6 +36,7 @@ 73DB41952805FC5F0028B8D3 /* Birthday.swift in Sources */ = {isa = PBXBuildFile; fileRef = 739FCC47270E659A00C92042 /* Birthday.swift */; }; 73DB419628060A9A0028B8D3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7345AD062703D9480020AFB1 /* Assets.xcassets */; }; C1B5D38D282AFE870068D12B /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = C1B5D38C282AFE870068D12B /* README.md */; }; + EEEEE201864437604E97D3B4 /* Pods_DaysUntilBirthdayForPod__iOS_.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F329EC9CE4A0A4AF2280834F /* Pods_DaysUntilBirthdayForPod__iOS_.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -62,10 +62,10 @@ 739FCC45270E467600C92042 /* BirthdayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BirthdayView.swift; sourceTree = ""; }; 739FCC47270E659A00C92042 /* Birthday.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Birthday.swift; sourceTree = ""; }; 7542DB53C46DFA9B71387188 /* Pods-DaysUntilBirthdayForPod (iOS).release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DaysUntilBirthdayForPod (iOS).release.xcconfig"; path = "Target Support Files/Pods-DaysUntilBirthdayForPod (iOS)/Pods-DaysUntilBirthdayForPod (iOS).release.xcconfig"; sourceTree = ""; }; - AE0FE424C7B3C57CF8808F8A /* libPods-DaysUntilBirthdayForPod (macOS).a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-DaysUntilBirthdayForPod (macOS).a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 926A15D393D684C68E09FB0F /* Pods_DaysUntilBirthdayForPod__macOS_.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DaysUntilBirthdayForPod__macOS_.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B7CC09BA1B68EB1881A08E87 /* Pods-DaysUntilBirthdayForPod(macOS).debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DaysUntilBirthdayForPod(macOS).debug.xcconfig"; path = "Target Support Files/Pods-DaysUntilBirthdayForPod(macOS)/Pods-DaysUntilBirthdayForPod(macOS).debug.xcconfig"; sourceTree = ""; }; C1B5D38C282AFE870068D12B /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - F163BB5E1E81280A79B4E535 /* libPods-DaysUntilBirthdayForPod (iOS).a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-DaysUntilBirthdayForPod (iOS).a"; sourceTree = BUILT_PRODUCTS_DIR; }; + F329EC9CE4A0A4AF2280834F /* Pods_DaysUntilBirthdayForPod__iOS_.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DaysUntilBirthdayForPod__iOS_.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F84E43064819F374C9789E49 /* Pods-DaysUntilBirthdayForPod (macOS).debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DaysUntilBirthdayForPod (macOS).debug.xcconfig"; path = "Target Support Files/Pods-DaysUntilBirthdayForPod (macOS)/Pods-DaysUntilBirthdayForPod (macOS).debug.xcconfig"; sourceTree = ""; }; FE2F2ABC2800D9C1005EA17F /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; FE71738027ECFAF400910319 /* DaysUntilBirthdayForPod (macOS).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "DaysUntilBirthdayForPod (macOS).app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -79,7 +79,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5965643074F45661539BC2DF /* libPods-DaysUntilBirthdayForPod (iOS).a in Frameworks */, + EEEEE201864437604E97D3B4 /* Pods_DaysUntilBirthdayForPod__iOS_.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -87,7 +87,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 7023CA98EF5DD94278069A12 /* libPods-DaysUntilBirthdayForPod (macOS).a in Frameworks */, + 4C6A2BA2321434ACBD2AF201 /* Pods_DaysUntilBirthdayForPod__macOS_.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -174,8 +174,8 @@ FE71738F27ECFB3300910319 /* Frameworks */ = { isa = PBXGroup; children = ( - F163BB5E1E81280A79B4E535 /* libPods-DaysUntilBirthdayForPod (iOS).a */, - AE0FE424C7B3C57CF8808F8A /* libPods-DaysUntilBirthdayForPod (macOS).a */, + F329EC9CE4A0A4AF2280834F /* Pods_DaysUntilBirthdayForPod__iOS_.framework */, + 926A15D393D684C68E09FB0F /* Pods_DaysUntilBirthdayForPod__macOS_.framework */, ); name = Frameworks; sourceTree = ""; diff --git a/Samples/Swift/DaysUntilBirthday/Podfile b/Samples/Swift/DaysUntilBirthday/Podfile index 373455a8..f746be79 100644 --- a/Samples/Swift/DaysUntilBirthday/Podfile +++ b/Samples/Swift/DaysUntilBirthday/Podfile @@ -2,6 +2,8 @@ pod 'GoogleSignIn', :path => '../../../', :testspecs => ['unit'] pod 'GoogleSignInSwiftSupport', :path => '../../../', :testspecs => ['unit'] project 'DaysUntilBirthdayForPod.xcodeproj' +use_frameworks! :linkage => :static + target 'DaysUntilBirthdayForPod (iOS)' do platform :ios, '14.0' end