From 30699dff05984dfc4d5f710c6d151f5c3a86a707 Mon Sep 17 00:00:00 2001 From: pinlu Date: Mon, 19 Sep 2022 17:55:05 -0700 Subject: [PATCH 01/26] Add KVO in GIDGoogleUser Send KVO notifications when authState updates tokens. --- GoogleSignIn/Sources/GIDGoogleUser.m | 57 +++++++++++++++------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/GoogleSignIn/Sources/GIDGoogleUser.m b/GoogleSignIn/Sources/GIDGoogleUser.m index b03bd55d..6e0242c4 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser.m +++ b/GoogleSignIn/Sources/GIDGoogleUser.m @@ -102,36 +102,14 @@ - (GIDConfiguration *)configuration { } - (GIDToken *)accessToken { - @synchronized(self) { - if (!_cachedAccessToken) { - _cachedAccessToken = [[GIDToken alloc] initWithTokenString:_authState.lastTokenResponse.accessToken - expirationDate:_authState.lastTokenResponse. - accessTokenExpirationDate]; - } - } return _cachedAccessToken; } - (GIDToken *)refreshToken { - @synchronized(self) { - if (!_cachedRefreshToken) { - _cachedRefreshToken = [[GIDToken alloc] initWithTokenString:_authState.refreshToken - expirationDate:nil]; - } - } return _cachedRefreshToken; } - (nullable GIDToken *)idToken { - @synchronized(self) { - NSString *idTokenString = _authState.lastTokenResponse.idToken; - if (!_cachedIdToken && idTokenString) { - NSDate *idTokenExpirationDate = [[[OIDIDToken alloc] - initWithIDTokenString:idTokenString] expiresAt]; - _cachedIdToken = [[GIDToken alloc] initWithTokenString:idTokenString - expirationDate:idTokenExpirationDate]; - } - } return _cachedIdToken; } @@ -153,10 +131,25 @@ - (void)updateAuthState:(OIDAuthState *)authState _authentication = [[GIDAuthentication alloc] initWithAuthState:authState]; _profile = profileData; - // These three tokens will be generated in the getter and cached . - _cachedAccessToken = nil; - _cachedRefreshToken = nil; - _cachedIdToken = nil; + [self sendKVONotificationsBeforeChanges]; + [self updateTokensWithAuthState:authState]; + [self sendKVONotificationAfterChanges]; + } +} + +- (void)updateTokensWithAuthState:(OIDAuthState *)authState { + _cachedAccessToken = [[GIDToken alloc] initWithTokenString:authState.lastTokenResponse.accessToken + expirationDate:authState.lastTokenResponse. + accessTokenExpirationDate]; + _cachedRefreshToken = [[GIDToken alloc] initWithTokenString:authState.refreshToken + expirationDate:nil]; + _cachedIdToken = nil; + NSString *idTokenString = authState.lastTokenResponse.idToken; + if (idTokenString) { + NSDate *idTokenExpirationDate = [[[OIDIDToken alloc] + initWithIDTokenString:idTokenString] expiresAt]; + _cachedIdToken = [[GIDToken alloc] initWithTokenString:idTokenString + expirationDate:idTokenExpirationDate]; } } @@ -173,6 +166,18 @@ - (nullable NSString *)hostedDomain { return nil; } +- (void)sendKVONotificationsBeforeChanges { + [self willChangeValueForKey:NSStringFromSelector(@selector(accessToken))]; + [self willChangeValueForKey:NSStringFromSelector(@selector(refreshToken))]; + [self willChangeValueForKey:NSStringFromSelector(@selector(idToken))]; +} + +- (void)sendKVONotificationAfterChanges { + [self didChangeValueForKey:NSStringFromSelector(@selector(accessToken))]; + [self didChangeValueForKey:NSStringFromSelector(@selector(refreshToken))]; + [self didChangeValueForKey:NSStringFromSelector(@selector(idToken))]; +} + #pragma mark - NSSecureCoding + (BOOL)supportsSecureCoding { From 9ac54783eb4f3c2daa67b4e3dbe0aeb84ce931f7 Mon Sep 17 00:00:00 2001 From: pinlu Date: Mon, 19 Sep 2022 18:14:36 -0700 Subject: [PATCH 02/26] Observe KVO in GIDGoogleUserTest Uses bit mask to verify KVO notifications are sent. --- GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m | 105 ++++++++++++++++++-- 1 file changed, 98 insertions(+), 7 deletions(-) diff --git a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m index 2ff9e567..9b2e5ddc 100644 --- a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m +++ b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m @@ -41,10 +41,45 @@ // It should be larger than kTimeAccuracy which is used in the method `XCTAssertEqualWithAccuracy`. static NSTimeInterval const kTimeIncrement = 100; +// List of observed properties of the class being tested. +static NSString *const kObservedProperties[] = { + @"accessToken", + @"refreshToken", + @"idToken", +}; +static const NSUInteger kNumberOfObservedProperties = + sizeof(kObservedProperties) / sizeof(*kObservedProperties); + +// Bit position for notification change type bitmask flags. +// Must match the list of observed properties above. +typedef NS_ENUM(NSUInteger, ChangeType) { + kChangeTypeAccessTokenPrior, + kChangeTypeAccessToken, + kChangeTypeRefreshTokenPrior, + kChangeTypeRefreshToken, + kChangeTypeIDTokenPrior, + kChangeTypeIDToken, + kChangeTypeEnd // not a real change type but an end mark for calculating |kChangeAll| +}; + +static const NSUInteger kChangeAll = (1u << kChangeTypeEnd) - 1u; + +#if __has_feature(c_static_assert) || __has_extension(c_static_assert) +_Static_assert(kChangeTypeEnd == (sizeof(kObservedProperties) / sizeof(*kObservedProperties)) * 2, + "List of observed properties must match list of change notification enums"); +#endif + @interface GIDGoogleUserTest : XCTestCase @end -@implementation GIDGoogleUserTest +@implementation GIDGoogleUserTest { + // Bitmask flags for observed changes, as specified in |ChangeType|. + NSUInteger _changesObserved; +} + +- (void)setUp { + _changesObserved = 0; +} #pragma mark - Tests @@ -102,12 +137,8 @@ - (void)testUpdateAuthState { NSTimeInterval accessTokenExpireTime = [NSDate timeIntervalSinceReferenceDate]; NSTimeInterval idTokenExpireTime = accessTokenExpireTime + kTimeIncrement; - NSString *idToken = [self idTokenWithExpireTime:idTokenExpireTime]; - OIDAuthState *authState = [OIDAuthState testInstanceWithIDToken:idToken - accessToken:kAccessToken - accessTokenExpireTime:accessTokenExpireTime]; - - GIDGoogleUser *user = [[GIDGoogleUser alloc] initWithAuthState:authState profileData:nil]; + GIDGoogleUser *user = [self observedGoogleUserWithAccessTokenExpireTime:accessTokenExpireTime + idTokenExpireTime:idTokenExpireTime]; NSTimeInterval updatedAccessTokenExpireTime = idTokenExpireTime + kTimeIncrement; NSTimeInterval updatedIDTokenExpireTime = updatedAccessTokenExpireTime + kTimeIncrement; @@ -126,12 +157,72 @@ - (void)testUpdateAuthState { XCTAssertEqualWithAccuracy([user.idToken.expirationDate timeIntervalSinceReferenceDate], updatedIDTokenExpireTime, kTimeAccuracy); XCTAssertEqual(user.profile, updatedProfileData); + XCTAssertEqual(_changesObserved, kChangeAll); } #pragma mark - Helpers +- (GIDGoogleUser *)observedGoogleUserWithAccessTokenExpireTime:(NSTimeInterval)accessTokenExpireTime + idTokenExpireTime:(NSTimeInterval)idTokenExpireTime { + GIDGoogleUser *user = [self googleUserWithAccessTokenExpireTime:accessTokenExpireTime + idTokenExpireTime:idTokenExpireTime]; + for (unsigned int i = 0; i < kNumberOfObservedProperties; ++i) { + [user addObserver:self + forKeyPath:kObservedProperties[i] + options:NSKeyValueObservingOptionPrior + context:NULL]; + } + return user; +} + +- (GIDGoogleUser *)googleUserWithAccessTokenExpireTime:(NSTimeInterval)accessTokenExpireTime + idTokenExpireTime:(NSTimeInterval)idTokenExpireTime { + + NSString *idToken = [self idTokenWithExpireTime:idTokenExpireTime]; + OIDAuthState *authState = [OIDAuthState testInstanceWithIDToken:idToken + accessToken:kAccessToken + accessTokenExpireTime:accessTokenExpireTime]; + + return [[GIDGoogleUser alloc] initWithAuthState:authState profileData:nil]; +} + - (NSString *)idTokenWithExpireTime:(NSTimeInterval)expireTime { return [OIDTokenResponse idTokenWithSub:kUserID exp:@(expireTime + NSTimeIntervalSince1970)]; } +#pragma mark - NSKeyValueObserving + +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context { + ChangeType changeType; + if ([keyPath isEqualToString:@"accessToken"]) { + if (change[NSKeyValueChangeNotificationIsPriorKey]) { + changeType = kChangeTypeAccessTokenPrior; + } else { + changeType = kChangeTypeAccessToken; + } + } else if ([keyPath isEqualToString:@"refreshToken"]) { + if (change[NSKeyValueChangeNotificationIsPriorKey]) { + changeType = kChangeTypeRefreshTokenPrior; + } else { + changeType = kChangeTypeRefreshToken; + } + } else if ([keyPath isEqualToString:@"idToken"]) { + if (change[NSKeyValueChangeNotificationIsPriorKey]) { + changeType = kChangeTypeIDTokenPrior; + } else { + changeType = kChangeTypeIDToken; + } + } else { + XCTFail(@"unexpected keyPath"); + return; // so compiler knows |changeType| is always assigned + } + + NSInteger changeMask = 1 << changeType; + XCTAssertFalse(_changesObserved & changeMask); // each change type should only fire once + _changesObserved |= changeMask; +} + @end From 26784fe5ac207be94d4f9b71ebdc476f9bb49bad Mon Sep 17 00:00:00 2001 From: pinlu Date: Thu, 22 Sep 2022 11:07:26 -0700 Subject: [PATCH 03/26] Utilize the class extension and automatic KVO notifications --- GoogleSignIn/Sources/GIDGoogleUser.m | 58 ++++++++++------------------ 1 file changed, 20 insertions(+), 38 deletions(-) diff --git a/GoogleSignIn/Sources/GIDGoogleUser.m b/GoogleSignIn/Sources/GIDGoogleUser.m index 6e0242c4..7e394441 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser.m +++ b/GoogleSignIn/Sources/GIDGoogleUser.m @@ -42,12 +42,19 @@ NS_ASSUME_NONNULL_BEGIN +@interface GIDGoogleUser() + +@property(nonatomic, readwrite) GIDToken *accessToken; + +@property(nonatomic, readwrite) GIDToken *refreshToken; + +@property(nonatomic, readwrite, nullable) GIDToken *idToken; + +@end + @implementation GIDGoogleUser { OIDAuthState *_authState; GIDConfiguration *_cachedConfiguration; - GIDToken *_cachedAccessToken; - GIDToken *_cachedRefreshToken; - GIDToken *_cachedIdToken; } - (nullable NSString *)userID { @@ -59,7 +66,6 @@ - (nullable NSString *)userID { return [idTokenDecoded.subject copy]; } } - return nil; } @@ -97,22 +103,9 @@ - (GIDConfiguration *)configuration { openIDRealm:openIDRealm]; }; } - return _cachedConfiguration; } -- (GIDToken *)accessToken { - return _cachedAccessToken; -} - -- (GIDToken *)refreshToken { - return _cachedRefreshToken; -} - -- (nullable GIDToken *)idToken { - return _cachedIdToken; -} - #pragma mark - Private Methods - (instancetype)initWithAuthState:(OIDAuthState *)authState @@ -131,25 +124,26 @@ - (void)updateAuthState:(OIDAuthState *)authState _authentication = [[GIDAuthentication alloc] initWithAuthState:authState]; _profile = profileData; - [self sendKVONotificationsBeforeChanges]; [self updateTokensWithAuthState:authState]; - [self sendKVONotificationAfterChanges]; } } - (void)updateTokensWithAuthState:(OIDAuthState *)authState { - _cachedAccessToken = [[GIDToken alloc] initWithTokenString:authState.lastTokenResponse.accessToken - expirationDate:authState.lastTokenResponse. - accessTokenExpirationDate]; - _cachedRefreshToken = [[GIDToken alloc] initWithTokenString:authState.refreshToken - expirationDate:nil]; - _cachedIdToken = nil; + self.accessToken = [[GIDToken alloc] initWithTokenString:authState.lastTokenResponse.accessToken + expirationDate:authState.lastTokenResponse. + accessTokenExpirationDate]; + + self.refreshToken = [[GIDToken alloc] initWithTokenString:authState.refreshToken + expirationDate:nil]; + NSString *idTokenString = authState.lastTokenResponse.idToken; if (idTokenString) { NSDate *idTokenExpirationDate = [[[OIDIDToken alloc] initWithIDTokenString:idTokenString] expiresAt]; - _cachedIdToken = [[GIDToken alloc] initWithTokenString:idTokenString + self.idToken = [[GIDToken alloc] initWithTokenString:idTokenString expirationDate:idTokenExpirationDate]; + } else { + self.idToken = nil; } } @@ -166,18 +160,6 @@ - (nullable NSString *)hostedDomain { return nil; } -- (void)sendKVONotificationsBeforeChanges { - [self willChangeValueForKey:NSStringFromSelector(@selector(accessToken))]; - [self willChangeValueForKey:NSStringFromSelector(@selector(refreshToken))]; - [self willChangeValueForKey:NSStringFromSelector(@selector(idToken))]; -} - -- (void)sendKVONotificationAfterChanges { - [self didChangeValueForKey:NSStringFromSelector(@selector(accessToken))]; - [self didChangeValueForKey:NSStringFromSelector(@selector(refreshToken))]; - [self didChangeValueForKey:NSStringFromSelector(@selector(idToken))]; -} - #pragma mark - NSSecureCoding + (BOOL)supportsSecureCoding { From 6e5d21adfa5e29158a387aed0b6c31e4fdfb0ae1 Mon Sep 17 00:00:00 2001 From: pinlu Date: Thu, 22 Sep 2022 13:36:03 -0700 Subject: [PATCH 04/26] Only update tokens if necessary --- GoogleSignIn/Sources/GIDGoogleUser.m | 20 ++++++++++++++----- .../Tests/Unit/GIDAuthenticationTest.m | 2 ++ GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m | 9 +++++++-- GoogleSignIn/Tests/Unit/GIDSignInTest.m | 1 + .../Tests/Unit/OIDAuthState+Testing.h | 3 ++- .../Tests/Unit/OIDAuthState+Testing.m | 4 +++- .../Tests/Unit/OIDTokenResponse+Testing.h | 1 + .../Tests/Unit/OIDTokenResponse+Testing.m | 4 +++- 8 files changed, 34 insertions(+), 10 deletions(-) diff --git a/GoogleSignIn/Sources/GIDGoogleUser.m b/GoogleSignIn/Sources/GIDGoogleUser.m index 7e394441..979a164f 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser.m +++ b/GoogleSignIn/Sources/GIDGoogleUser.m @@ -42,7 +42,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface GIDGoogleUser() +@interface GIDGoogleUser () @property(nonatomic, readwrite) GIDToken *accessToken; @@ -129,21 +129,31 @@ - (void)updateAuthState:(OIDAuthState *)authState } - (void)updateTokensWithAuthState:(OIDAuthState *)authState { - self.accessToken = [[GIDToken alloc] initWithTokenString:authState.lastTokenResponse.accessToken + GIDToken *accessToken = [[GIDToken alloc] initWithTokenString:authState.lastTokenResponse.accessToken expirationDate:authState.lastTokenResponse. accessTokenExpirationDate]; + if (![self.accessToken isEqualToToken:accessToken]) { + self.accessToken = accessToken; + } - self.refreshToken = [[GIDToken alloc] initWithTokenString:authState.refreshToken + GIDToken *refreshToken = [[GIDToken alloc] initWithTokenString:authState.refreshToken expirationDate:nil]; + if (![self.refreshToken isEqualToToken:refreshToken]) { + self.refreshToken = refreshToken; + } + GIDToken *idToken; NSString *idTokenString = authState.lastTokenResponse.idToken; if (idTokenString) { NSDate *idTokenExpirationDate = [[[OIDIDToken alloc] initWithIDTokenString:idTokenString] expiresAt]; - self.idToken = [[GIDToken alloc] initWithTokenString:idTokenString + idToken = [[GIDToken alloc] initWithTokenString:idTokenString expirationDate:idTokenExpirationDate]; } else { - self.idToken = nil; + idToken = nil; + } + if ((self.idToken || idToken) && ![self.idToken isEqualToToken:idToken]) { + self.idToken = idToken; } } diff --git a/GoogleSignIn/Tests/Unit/GIDAuthenticationTest.m b/GoogleSignIn/Tests/Unit/GIDAuthenticationTest.m index ad57bead..769d3235 100644 --- a/GoogleSignIn/Tests/Unit/GIDAuthenticationTest.m +++ b/GoogleSignIn/Tests/Unit/GIDAuthenticationTest.m @@ -561,6 +561,7 @@ - (GIDAuthentication *)auth { [OIDTokenResponse testInstanceWithIDToken:idToken accessToken:kAccessToken expiresIn:accessTokenExpiresIn + refreshToken:kRefreshToken tokenRequest:tokenRequest]; return [[GIDAuthentication alloc] initWithAuthState:[OIDAuthState testInstanceWithTokenResponse:tokenResponse]]; @@ -599,6 +600,7 @@ - (OIDTokenResponse *)tokenResponseWithNewTokens { return [OIDTokenResponse testInstanceWithIDToken:(_hasIDToken ? [self idTokenNew] : nil) accessToken:kNewAccessToken expiresIn:expiresIn + refreshToken:kRefreshToken tokenRequest:_tokenRequest ?: nil]; } diff --git a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m index 9b2e5ddc..56e0815b 100644 --- a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m +++ b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m @@ -36,6 +36,8 @@ #endif static NSString *const kNewAccessToken = @"new_access_token"; +static NSString *const kNewRefreshToken = @"new_refresh_token"; + static NSTimeInterval const kTimeAccuracy = 10; // The difference between times. // It should be larger than kTimeAccuracy which is used in the method `XCTAssertEqualWithAccuracy`. @@ -145,7 +147,8 @@ - (void)testUpdateAuthState { NSString *updatedIDToken = [self idTokenWithExpireTime:updatedIDTokenExpireTime]; OIDAuthState *updatedAuthState = [OIDAuthState testInstanceWithIDToken:updatedIDToken accessToken:kNewAccessToken - accessTokenExpireTime:updatedAccessTokenExpireTime]; + accessTokenExpireTime:updatedAccessTokenExpireTime + refreshToken:kNewRefreshToken]; GIDProfileData *updatedProfileData = [GIDProfileData testInstance]; [user updateAuthState:updatedAuthState profileData:updatedProfileData]; @@ -156,6 +159,7 @@ - (void)testUpdateAuthState { XCTAssertEqualObjects(user.idToken.tokenString, updatedIDToken); XCTAssertEqualWithAccuracy([user.idToken.expirationDate timeIntervalSinceReferenceDate], updatedIDTokenExpireTime, kTimeAccuracy); + XCTAssertEqualObjects(user.refreshToken.tokenString, kNewRefreshToken); XCTAssertEqual(user.profile, updatedProfileData); XCTAssertEqual(_changesObserved, kChangeAll); } @@ -181,7 +185,8 @@ - (GIDGoogleUser *)googleUserWithAccessTokenExpireTime:(NSTimeInterval)accessTok NSString *idToken = [self idTokenWithExpireTime:idTokenExpireTime]; OIDAuthState *authState = [OIDAuthState testInstanceWithIDToken:idToken accessToken:kAccessToken - accessTokenExpireTime:accessTokenExpireTime]; + accessTokenExpireTime:accessTokenExpireTime + refreshToken:kRefreshToken]; return [[GIDGoogleUser alloc] initWithAuthState:authState profileData:nil]; } diff --git a/GoogleSignIn/Tests/Unit/GIDSignInTest.m b/GoogleSignIn/Tests/Unit/GIDSignInTest.m index 5d9dac77..fdcd1027 100644 --- a/GoogleSignIn/Tests/Unit/GIDSignInTest.m +++ b/GoogleSignIn/Tests/Unit/GIDSignInTest.m @@ -1202,6 +1202,7 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow [OIDTokenResponse testInstanceWithIDToken:[OIDTokenResponse fatIDToken] accessToken:restoredSignIn ? kAccessToken : nil expiresIn:oldAccessToken ? @(300) : nil + refreshToken:kRefreshToken tokenRequest:nil]; OIDTokenRequest *tokenRequest = [[OIDTokenRequest alloc] diff --git a/GoogleSignIn/Tests/Unit/OIDAuthState+Testing.h b/GoogleSignIn/Tests/Unit/OIDAuthState+Testing.h index b255352f..f660e34e 100644 --- a/GoogleSignIn/Tests/Unit/OIDAuthState+Testing.h +++ b/GoogleSignIn/Tests/Unit/OIDAuthState+Testing.h @@ -30,6 +30,7 @@ + (instancetype)testInstanceWithIDToken:(NSString *)idToken accessToken:(NSString *)accessToken - accessTokenExpireTime:(NSTimeInterval)accessTokenExpireTime; + accessTokenExpireTime:(NSTimeInterval)accessTokenExpireTime + refreshToken:(NSString *)refreshToken; @end diff --git a/GoogleSignIn/Tests/Unit/OIDAuthState+Testing.m b/GoogleSignIn/Tests/Unit/OIDAuthState+Testing.m index 6c019820..a9f7c49c 100644 --- a/GoogleSignIn/Tests/Unit/OIDAuthState+Testing.m +++ b/GoogleSignIn/Tests/Unit/OIDAuthState+Testing.m @@ -35,13 +35,15 @@ + (instancetype)testInstanceWithTokenResponse:(OIDTokenResponse *)tokenResponse + (instancetype)testInstanceWithIDToken:(NSString *)idToken accessToken:(NSString *)accessToken - accessTokenExpireTime:(NSTimeInterval)accessTokenExpireTime { + accessTokenExpireTime:(NSTimeInterval)accessTokenExpireTime + refreshToken:(NSString *)refreshToken { NSNumber *accessTokenExpiresIn = @(accessTokenExpireTime - [[NSDate date] timeIntervalSinceReferenceDate]); OIDTokenResponse *newResponse = [OIDTokenResponse testInstanceWithIDToken:idToken accessToken:accessToken expiresIn:accessTokenExpiresIn + refreshToken:refreshToken tokenRequest:nil]; return [self testInstanceWithTokenResponse:newResponse]; } diff --git a/GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h b/GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h index 990fda0f..bd26a66a 100644 --- a/GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h +++ b/GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h @@ -56,6 +56,7 @@ extern NSString * const kFatPictureURL; + (instancetype)testInstanceWithIDToken:(NSString *)idToken accessToken:(NSString *)accessToken expiresIn:(NSNumber *)expiresIn + refreshToken:(NSString *)refreshToken tokenRequest:(OIDTokenRequest *)tokenRequest; + (NSString *)idToken; diff --git a/GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.m b/GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.m index 1e1b95bf..3f3df182 100644 --- a/GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.m +++ b/GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.m @@ -61,19 +61,21 @@ + (instancetype)testInstanceWithIDToken:(NSString *)idToken { return [OIDTokenResponse testInstanceWithIDToken:idToken accessToken:nil expiresIn:nil + refreshToken:kRefreshToken tokenRequest:nil]; } + (instancetype)testInstanceWithIDToken:(NSString *)idToken accessToken:(NSString *)accessToken expiresIn:(NSNumber *)expiresIn + refreshToken:(NSString *)refreshToken tokenRequest:(OIDTokenRequest *)tokenRequest { NSMutableDictionary *parameters; parameters = [[NSMutableDictionary alloc] initWithDictionary:@{ @"access_token" : accessToken ?: kAccessToken, @"expires_in" : expiresIn ?: @(kAccessTokenExpiresIn), @"token_type" : @"example_token_type", - @"refresh_token" : kRefreshToken, + @"refresh_token" : refreshToken, @"scope" : [OIDScopeUtilities scopesWithArray:@[ OIDAuthorizationRequestTestingScope2 ]], @"server_code" : kServerAuthCode, }]; From d45e398d697f2cffed73d1eb06b2cb4ceef781dc Mon Sep 17 00:00:00 2001 From: pinlu Date: Thu, 22 Sep 2022 14:25:29 -0700 Subject: [PATCH 05/26] Remove the KVO testing Since we use NSObject automatic change notifications we can remove the tests for manual notifications emission. --- GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m | 91 +------------------ .../Tests/Unit/OIDTokenResponse+Testing.m | 4 +- 2 files changed, 5 insertions(+), 90 deletions(-) diff --git a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m index 999d617d..aa326d35 100644 --- a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m +++ b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m @@ -43,45 +43,10 @@ // It should be larger than kTimeAccuracy which is used in the method `XCTAssertEqualWithAccuracy`. static NSTimeInterval const kTimeIncrement = 100; -// List of observed properties of the class being tested. -static NSString *const kObservedProperties[] = { - @"accessToken", - @"refreshToken", - @"idToken", -}; -static const NSUInteger kNumberOfObservedProperties = - sizeof(kObservedProperties) / sizeof(*kObservedProperties); - -// Bit position for notification change type bitmask flags. -// Must match the list of observed properties above. -typedef NS_ENUM(NSUInteger, ChangeType) { - kChangeTypeAccessTokenPrior, - kChangeTypeAccessToken, - kChangeTypeRefreshTokenPrior, - kChangeTypeRefreshToken, - kChangeTypeIDTokenPrior, - kChangeTypeIDToken, - kChangeTypeEnd // not a real change type but an end mark for calculating |kChangeAll| -}; - -static const NSUInteger kChangeAll = (1u << kChangeTypeEnd) - 1u; - -#if __has_feature(c_static_assert) || __has_extension(c_static_assert) -_Static_assert(kChangeTypeEnd == (sizeof(kObservedProperties) / sizeof(*kObservedProperties)) * 2, - "List of observed properties must match list of change notification enums"); -#endif - @interface GIDGoogleUserTest : XCTestCase @end -@implementation GIDGoogleUserTest { - // Bitmask flags for observed changes, as specified in |ChangeType|. - NSUInteger _changesObserved; -} - -- (void)setUp { - _changesObserved = 0; -} +@implementation GIDGoogleUserTest #pragma mark - Tests @@ -139,8 +104,8 @@ - (void)testUpdateAuthState { NSTimeInterval accessTokenExpireTime = [[NSDate date] timeIntervalSince1970]; NSTimeInterval idTokenExpireTime = accessTokenExpireTime + kTimeIncrement; - GIDGoogleUser *user = [self observedGoogleUserWithAccessTokenExpireTime:accessTokenExpireTime - idTokenExpireTime:idTokenExpireTime]; + GIDGoogleUser *user = [self googleUserWithAccessTokenExpireTime:accessTokenExpireTime + idTokenExpireTime:idTokenExpireTime]; NSTimeInterval updatedAccessTokenExpireTime = idTokenExpireTime + kTimeIncrement; NSTimeInterval updatedIDTokenExpireTime = updatedAccessTokenExpireTime + kTimeIncrement; @@ -161,27 +126,12 @@ - (void)testUpdateAuthState { updatedIDTokenExpireTime, kTimeAccuracy); XCTAssertEqualObjects(user.refreshToken.tokenString, kNewRefreshToken); XCTAssertEqual(user.profile, updatedProfileData); - XCTAssertEqual(_changesObserved, kChangeAll); } #pragma mark - Helpers -- (GIDGoogleUser *)observedGoogleUserWithAccessTokenExpireTime:(NSTimeInterval)accessTokenExpireTime - idTokenExpireTime:(NSTimeInterval)idTokenExpireTime { - GIDGoogleUser *user = [self googleUserWithAccessTokenExpireTime:accessTokenExpireTime - idTokenExpireTime:idTokenExpireTime]; - for (unsigned int i = 0; i < kNumberOfObservedProperties; ++i) { - [user addObserver:self - forKeyPath:kObservedProperties[i] - options:NSKeyValueObservingOptionPrior - context:NULL]; - } - return user; -} - - (GIDGoogleUser *)googleUserWithAccessTokenExpireTime:(NSTimeInterval)accessTokenExpireTime idTokenExpireTime:(NSTimeInterval)idTokenExpireTime { - NSString *idToken = [self idTokenWithExpireTime:idTokenExpireTime]; OIDAuthState *authState = [OIDAuthState testInstanceWithIDToken:idToken accessToken:kAccessToken @@ -196,39 +146,4 @@ - (NSString *)idTokenWithExpireTime:(NSTimeInterval)expireTime { return [OIDTokenResponse idTokenWithSub:kUserID exp:@(expireTime)]; } -#pragma mark - NSKeyValueObserving - -- (void)observeValueForKeyPath:(NSString *)keyPath - ofObject:(id)object - change:(NSDictionary *)change - context:(void *)context { - ChangeType changeType; - if ([keyPath isEqualToString:@"accessToken"]) { - if (change[NSKeyValueChangeNotificationIsPriorKey]) { - changeType = kChangeTypeAccessTokenPrior; - } else { - changeType = kChangeTypeAccessToken; - } - } else if ([keyPath isEqualToString:@"refreshToken"]) { - if (change[NSKeyValueChangeNotificationIsPriorKey]) { - changeType = kChangeTypeRefreshTokenPrior; - } else { - changeType = kChangeTypeRefreshToken; - } - } else if ([keyPath isEqualToString:@"idToken"]) { - if (change[NSKeyValueChangeNotificationIsPriorKey]) { - changeType = kChangeTypeIDTokenPrior; - } else { - changeType = kChangeTypeIDToken; - } - } else { - XCTFail(@"unexpected keyPath"); - return; // so compiler knows |changeType| is always assigned - } - - NSInteger changeMask = 1 << changeType; - XCTAssertFalse(_changesObserved & changeMask); // each change type should only fire once - _changesObserved |= changeMask; -} - @end diff --git a/GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.m b/GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.m index 3f3df182..49823be7 100644 --- a/GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.m +++ b/GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.m @@ -61,7 +61,7 @@ + (instancetype)testInstanceWithIDToken:(NSString *)idToken { return [OIDTokenResponse testInstanceWithIDToken:idToken accessToken:nil expiresIn:nil - refreshToken:kRefreshToken + refreshToken:nil tokenRequest:nil]; } @@ -75,7 +75,7 @@ + (instancetype)testInstanceWithIDToken:(NSString *)idToken @"access_token" : accessToken ?: kAccessToken, @"expires_in" : expiresIn ?: @(kAccessTokenExpiresIn), @"token_type" : @"example_token_type", - @"refresh_token" : refreshToken, + @"refresh_token" : refreshToken ?: kRefreshToken, @"scope" : [OIDScopeUtilities scopesWithArray:@[ OIDAuthorizationRequestTestingScope2 ]], @"server_code" : kServerAuthCode, }]; From a56786b6024cdb1edb31a432245d6d15c74a15b5 Mon Sep 17 00:00:00 2001 From: pinlu Date: Fri, 23 Sep 2022 15:10:51 -0700 Subject: [PATCH 06/26] Test tokens unchanged Added a new test case `testUpdateAuthStateWithUnchangedRefreshTokenAndIDToken ` --- GoogleSignIn/Sources/GIDGoogleUser.m | 14 ++++++------ GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m | 25 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/GoogleSignIn/Sources/GIDGoogleUser.m b/GoogleSignIn/Sources/GIDGoogleUser.m index 979a164f..cb215f7f 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser.m +++ b/GoogleSignIn/Sources/GIDGoogleUser.m @@ -129,15 +129,15 @@ - (void)updateAuthState:(OIDAuthState *)authState } - (void)updateTokensWithAuthState:(OIDAuthState *)authState { - GIDToken *accessToken = [[GIDToken alloc] initWithTokenString:authState.lastTokenResponse.accessToken - expirationDate:authState.lastTokenResponse. - accessTokenExpirationDate]; + GIDToken *accessToken = + [[GIDToken alloc] initWithTokenString:authState.lastTokenResponse.accessToken + expirationDate:authState.lastTokenResponse.accessTokenExpirationDate]; if (![self.accessToken isEqualToToken:accessToken]) { self.accessToken = accessToken; } GIDToken *refreshToken = [[GIDToken alloc] initWithTokenString:authState.refreshToken - expirationDate:nil]; + expirationDate:nil]; if (![self.refreshToken isEqualToToken:refreshToken]) { self.refreshToken = refreshToken; } @@ -145,10 +145,10 @@ - (void)updateTokensWithAuthState:(OIDAuthState *)authState { GIDToken *idToken; NSString *idTokenString = authState.lastTokenResponse.idToken; if (idTokenString) { - NSDate *idTokenExpirationDate = [[[OIDIDToken alloc] - initWithIDTokenString:idTokenString] expiresAt]; + NSDate *idTokenExpirationDate = + [[[OIDIDToken alloc] initWithIDTokenString:idTokenString] expiresAt]; idToken = [[GIDToken alloc] initWithTokenString:idTokenString - expirationDate:idTokenExpirationDate]; + expirationDate:idTokenExpirationDate]; } else { idToken = nil; } diff --git a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m index aa326d35..f356057b 100644 --- a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m +++ b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m @@ -128,6 +128,31 @@ - (void)testUpdateAuthState { XCTAssertEqual(user.profile, updatedProfileData); } +- (void)testUpdateAuthStateWithUnchangedRefreshTokenAndIDToken { + NSTimeInterval accessTokenExpireTime = [[NSDate date] timeIntervalSince1970]; + NSTimeInterval idTokenExpireTime = [[NSDate date] timeIntervalSince1970]; + + GIDGoogleUser *user = [self googleUserWithAccessTokenExpireTime:accessTokenExpireTime + idTokenExpireTime:idTokenExpireTime]; + + GIDToken *accessTokenBeforeUpdate = user.accessToken; + GIDToken *refreshTokenBeforeUpdate = user.refreshToken; + GIDToken *idTokenBeforeUpdate = user.idToken; + + NSString *updatedIDToken = [self idTokenWithExpireTime:idTokenExpireTime]; + OIDAuthState *updatedAuthState = [OIDAuthState testInstanceWithIDToken:updatedIDToken + accessToken:kNewAccessToken + accessTokenExpireTime:accessTokenExpireTime + refreshToken:kRefreshToken]; + GIDProfileData *updatedProfileData = [GIDProfileData testInstance]; + + [user updateAuthState:updatedAuthState profileData:updatedProfileData]; + + XCTAssertIdentical(user.idToken, idTokenBeforeUpdate); + XCTAssertIdentical(user.refreshToken, refreshTokenBeforeUpdate); + XCTAssertNotIdentical(user.accessToken, accessTokenBeforeUpdate); +} + #pragma mark - Helpers - (GIDGoogleUser *)googleUserWithAccessTokenExpireTime:(NSTimeInterval)accessTokenExpireTime From 58d2acf457f4ad6454ed034d2c8ad794bc7ed432 Mon Sep 17 00:00:00 2001 From: pinlu Date: Mon, 26 Sep 2022 01:12:34 -0700 Subject: [PATCH 07/26] Move fetcherAuthorizer from GIDAuthentication to GIDGoogleUser --- GoogleSignIn/Sources/GIDGoogleUser.m | 152 +++++++++++++++++- GoogleSignIn/Sources/GIDGoogleUser_Private.h | 15 +- .../Public/GoogleSignIn/GIDGoogleUser.h | 14 ++ 3 files changed, 179 insertions(+), 2 deletions(-) diff --git a/GoogleSignIn/Sources/GIDGoogleUser.m b/GoogleSignIn/Sources/GIDGoogleUser.m index cb215f7f..034a49be 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser.m +++ b/GoogleSignIn/Sources/GIDGoogleUser.m @@ -25,7 +25,14 @@ #ifdef SWIFT_PACKAGE @import AppAuth; #else -#import +#import +#import +#import +#import +#import +#import +#import +#import #endif // The ID Token claim key for the hosted domain value. @@ -40,8 +47,111 @@ static NSString *const kAudienceParameter = @"audience"; static NSString *const kOpenIDRealmParameter = @"openid.realm"; +// Additional parameter names for EMM. +static NSString *const kEMMSupportParameterName = @"emm_support"; + NS_ASSUME_NONNULL_BEGIN +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +// The specialized GTMAppAuthFetcherAuthorization delegate that handles potential EMM error +// responses. +@interface GTMAppAuthFetcherAuthorizationEMMChainedDelegate : 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 GTMAppAuthFetcherAuthorizationEMMChainedDelegate { + // 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. + GTMAppAuthFetcherAuthorizationEMMChainedDelegate *_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 { + [GIDAuthentication 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 + +// A specialized GTMAppAuthFetcherAuthorization subclass with EMM support. +@interface GTMAppAuthFetcherAuthorizationWithEMMSupport : GTMAppAuthFetcherAuthorization +@end + +@implementation GTMAppAuthFetcherAuthorizationWithEMMSupport + +#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 + GTMAppAuthFetcherAuthorizationEMMChainedDelegate *chainedDelegate = + [[GTMAppAuthFetcherAuthorizationEMMChainedDelegate 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) { + [GIDAuthentication handleTokenFetchEMMError:error completion:^(NSError *_Nullable error) { + handler(error); + }]; + }]; +} + +@end + +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST + @interface GIDGoogleUser () @property(nonatomic, readwrite) GIDToken *accessToken; @@ -55,6 +165,10 @@ @interface GIDGoogleUser () @implementation GIDGoogleUser { OIDAuthState *_authState; GIDConfiguration *_cachedConfiguration; + + // A queue for pending authentication handlers so we don't fire multiple requests in parallel. + // Access to this ivar should be synchronized. + NSMutableArray *_authenticationHandlerQueue; } - (nullable NSString *)userID { @@ -106,12 +220,36 @@ - (GIDConfiguration *)configuration { return _cachedConfiguration; } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +- (id)fetcherAuthorizer { +#pragma clang diagnostic pop +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + GTMAppAuthFetcherAuthorization *authorization = self.emmSupport ? + [[GTMAppAuthFetcherAuthorizationWithEMMSupport alloc] initWithAuthState:_authState] : + [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:_authState]; +#elif TARGET_OS_OSX || TARGET_OS_MACCATALYST + GTMAppAuthFetcherAuthorization *authorization = + [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:_authState]; +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST + authorization.tokenRefreshDelegate = self; + return authorization; +} + #pragma mark - Private Methods +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST +- (NSString *)emmSupport { + return + _authState.lastAuthorizationResponse.request.additionalParameters[kEMMSupportParameterName]; +} +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST + - (instancetype)initWithAuthState:(OIDAuthState *)authState profileData:(nullable GIDProfileData *)profileData { self = [super init]; if (self) { + _authenticationHandlerQueue = [[NSMutableArray alloc] init]; [self updateAuthState:authState profileData:profileData]; } return self; @@ -170,6 +308,18 @@ - (nullable NSString *)hostedDomain { return nil; } +#pragma mark - GTMAppAuthFetcherAuthorizationTokenRefreshDelegate + +- (nullable NSDictionary *)additionalRefreshParameters: + (GTMAppAuthFetcherAuthorization *)authorization { +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + return [GIDAuthentication 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 - NSSecureCoding + (BOOL)supportsSecureCoding { diff --git a/GoogleSignIn/Sources/GIDGoogleUser_Private.h b/GoogleSignIn/Sources/GIDGoogleUser_Private.h index c6a1fc84..46b85168 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser_Private.h +++ b/GoogleSignIn/Sources/GIDGoogleUser_Private.h @@ -16,12 +16,25 @@ #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h" +#ifdef SWIFT_PACKAGE +@import AppAuth; +@import GTMAppAuth; +#else +#import +#import +#endif + NS_ASSUME_NONNULL_BEGIN @class OIDAuthState; // Internal methods for the class that are not part of the public API. -@interface GIDGoogleUser () +@interface GIDGoogleUser () + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST +// A string indicating support for Enterprise Mobility Management. +@property(nonatomic, readonly) NSString *emmSupport; +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST // Create a object with an auth state, scopes, and profile data. - (instancetype)initWithAuthState:(OIDAuthState *)authState diff --git a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h index 1d3f09c6..bc7ef324 100644 --- a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h +++ b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h @@ -18,6 +18,14 @@ NS_ASSUME_NONNULL_BEGIN +// We have to import GTMAppAuth because forward declaring the protocol does +// not generate the `fetcherAuthorizer` method below for Swift. +#ifdef SWIFT_PACKAGE +@import GTMAppAuth; +#else +#import +#endif + @class GIDAuthentication; @class GIDConfiguration; @class GIDToken; @@ -53,6 +61,12 @@ NS_ASSUME_NONNULL_BEGIN /// see https://developers.google.com/identity/sign-in/ios/backend-auth. @property(nonatomic, readonly, nullable) GIDToken *idToken; +/// A new authorizer for `GTLService`, `GTMSessionFetcher`, or `GTMHTTPFetcher`. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +@property(nonatomic, readonly) (id)fetcherAuthorizer; +#pragma clang diagnostic pop + @end NS_ASSUME_NONNULL_END From 3d2921240dee8ccfb7d8a9d8792c93e347827571 Mon Sep 17 00:00:00 2001 From: pinlu Date: Mon, 26 Sep 2022 16:30:27 -0700 Subject: [PATCH 08/26] Move `fetcherAuthorizer` into GIDGoogleUser API --- GoogleSignIn/Sources/GIDAuthentication.m | 137 ------------ .../Sources/GIDAuthentication_Private.h | 7 +- GoogleSignIn/Sources/GIDGoogleUser.m | 210 +++++++++--------- .../Public/GoogleSignIn/GIDAuthentication.h | 8 - .../Public/GoogleSignIn/GIDGoogleUser.h | 6 +- .../Tests/Unit/GIDAuthenticationTest.m | 13 -- GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m | 14 ++ 7 files changed, 119 insertions(+), 276 deletions(-) diff --git a/GoogleSignIn/Sources/GIDAuthentication.m b/GoogleSignIn/Sources/GIDAuthentication.m index 7965db1e..927be7a1 100644 --- a/GoogleSignIn/Sources/GIDAuthentication.m +++ b/GoogleSignIn/Sources/GIDAuthentication.m @@ -57,106 +57,6 @@ // New UIDevice system name for iOS. static NSString *const kNewIOSSystemName = @"iOS"; -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - -// The specialized GTMAppAuthFetcherAuthorization delegate that handles potential EMM error -// responses. -@interface GTMAppAuthFetcherAuthorizationEMMChainedDelegate : 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 GTMAppAuthFetcherAuthorizationEMMChainedDelegate { - // 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. - GTMAppAuthFetcherAuthorizationEMMChainedDelegate *_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 { - [GIDAuthentication 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 - -// A specialized GTMAppAuthFetcherAuthorization subclass with EMM support. -@interface GTMAppAuthFetcherAuthorizationWithEMMSupport : GTMAppAuthFetcherAuthorization -@end - -@implementation GTMAppAuthFetcherAuthorizationWithEMMSupport - -#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 - GTMAppAuthFetcherAuthorizationEMMChainedDelegate *chainedDelegate = - [[GTMAppAuthFetcherAuthorizationEMMChainedDelegate 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) { - [GIDAuthentication handleTokenFetchEMMError:error completion:^(NSError *_Nullable error) { - handler(error); - }]; - }]; -} - -@end - -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST - @implementation GIDAuthentication { // A queue for pending authentication handlers so we don't fire multiple requests in parallel. // Access to this ivar should be synchronized. @@ -201,33 +101,8 @@ - (nullable NSDate *)idTokenExpirationDate { return [[[OIDIDToken alloc] initWithIDTokenString:self.idToken] expiresAt]; } -#pragma mark - Private property accessors - -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST -- (NSString *)emmSupport { - return - _authState.lastAuthorizationResponse.request.additionalParameters[kEMMSupportParameterName]; -} -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST - #pragma mark - Public methods -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -- (id)fetcherAuthorizer { -#pragma clang diagnostic pop -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - GTMAppAuthFetcherAuthorization *authorization = self.emmSupport ? - [[GTMAppAuthFetcherAuthorizationWithEMMSupport alloc] initWithAuthState:_authState] : - [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:_authState]; -#elif TARGET_OS_OSX || TARGET_OS_MACCATALYST - GTMAppAuthFetcherAuthorization *authorization = - [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:_authState]; -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST - authorization.tokenRefreshDelegate = self; - return authorization; -} - - (void)doWithFreshTokens:(GIDAuthenticationCompletion)completion { if (!([self.accessTokenExpirationDate timeIntervalSinceNow] < kMinimalTimeToExpire || (self.idToken && [self.idTokenExpirationDate timeIntervalSinceNow] < kMinimalTimeToExpire))) { @@ -360,18 +235,6 @@ + (void)handleTokenFetchEMMError:(nullable NSError *)error #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST -#pragma mark - GTMAppAuthFetcherAuthorizationTokenRefreshDelegate - -- (nullable NSDictionary *)additionalRefreshParameters: - (GTMAppAuthFetcherAuthorization *)authorization { -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - return [GIDAuthentication 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 - NSSecureCoding + (BOOL)supportsSecureCoding { diff --git a/GoogleSignIn/Sources/GIDAuthentication_Private.h b/GoogleSignIn/Sources/GIDAuthentication_Private.h index c967a0da..37245aa4 100644 --- a/GoogleSignIn/Sources/GIDAuthentication_Private.h +++ b/GoogleSignIn/Sources/GIDAuthentication_Private.h @@ -27,16 +27,11 @@ NS_ASSUME_NONNULL_BEGIN // Internal methods for the class that are not part of the public API. -@interface GIDAuthentication () +@interface GIDAuthentication () // A representation of the state of the OAuth session for this instance. @property(nonatomic, readonly) OIDAuthState *authState; -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST -// A string indicating support for Enterprise Mobility Management. -@property(nonatomic, readonly) NSString *emmSupport; -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST - - (instancetype)initWithAuthState:(OIDAuthState *)authState; #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST diff --git a/GoogleSignIn/Sources/GIDGoogleUser.m b/GoogleSignIn/Sources/GIDGoogleUser.m index 034a49be..3bd78c09 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser.m +++ b/GoogleSignIn/Sources/GIDGoogleUser.m @@ -25,14 +25,7 @@ #ifdef SWIFT_PACKAGE @import AppAuth; #else -#import -#import -#import -#import -#import -#import -#import -#import +#import #endif // The ID Token claim key for the hosted domain value. @@ -52,106 +45,10 @@ NS_ASSUME_NONNULL_BEGIN -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - -// The specialized GTMAppAuthFetcherAuthorization delegate that handles potential EMM error -// responses. -@interface GTMAppAuthFetcherAuthorizationEMMChainedDelegate : 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 GTMAppAuthFetcherAuthorizationEMMChainedDelegate { - // 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. - GTMAppAuthFetcherAuthorizationEMMChainedDelegate *_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 { - [GIDAuthentication 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 - // A specialized GTMAppAuthFetcherAuthorization subclass with EMM support. @interface GTMAppAuthFetcherAuthorizationWithEMMSupport : GTMAppAuthFetcherAuthorization @end -@implementation GTMAppAuthFetcherAuthorizationWithEMMSupport - -#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 - GTMAppAuthFetcherAuthorizationEMMChainedDelegate *chainedDelegate = - [[GTMAppAuthFetcherAuthorizationEMMChainedDelegate 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) { - [GIDAuthentication handleTokenFetchEMMError:error completion:^(NSError *_Nullable error) { - handler(error); - }]; - }]; -} - -@end - -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST - @interface GIDGoogleUser () @property(nonatomic, readwrite) GIDToken *accessToken; @@ -165,10 +62,6 @@ @interface GIDGoogleUser () @implementation GIDGoogleUser { OIDAuthState *_authState; GIDConfiguration *_cachedConfiguration; - - // A queue for pending authentication handlers so we don't fire multiple requests in parallel. - // Access to this ivar should be synchronized. - NSMutableArray *_authenticationHandlerQueue; } - (nullable NSString *)userID { @@ -249,7 +142,6 @@ - (instancetype)initWithAuthState:(OIDAuthState *)authState profileData:(nullable GIDProfileData *)profileData { self = [super init]; if (self) { - _authenticationHandlerQueue = [[NSMutableArray alloc] init]; [self updateAuthState:authState profileData:profileData]; } return self; @@ -351,4 +243,104 @@ - (void)encodeWithCoder:(NSCoder *)encoder { @end +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +#pragma mark - GTMAppAuthFetcherAuthorizationEMMChainedDelegate + +// The specialized GTMAppAuthFetcherAuthorization delegate that handles potential EMM error +// responses. +@interface GTMAppAuthFetcherAuthorizationEMMChainedDelegate : 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 GTMAppAuthFetcherAuthorizationEMMChainedDelegate { + // 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. + GTMAppAuthFetcherAuthorizationEMMChainedDelegate *_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 { + [GIDAuthentication 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 + +#pragma mark - GTMAppAuthFetcherAuthorizationWithEMMSupport + +@implementation GTMAppAuthFetcherAuthorizationWithEMMSupport + +#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 + GTMAppAuthFetcherAuthorizationEMMChainedDelegate *chainedDelegate = + [[GTMAppAuthFetcherAuthorizationEMMChainedDelegate 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) { + [GIDAuthentication handleTokenFetchEMMError:error completion:^(NSError *_Nullable error) { + handler(error); + }]; + }]; +} + +@end + +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST + NS_ASSUME_NONNULL_END diff --git a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDAuthentication.h b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDAuthentication.h index 13ae1920..cba4cac6 100644 --- a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDAuthentication.h +++ b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDAuthentication.h @@ -56,14 +56,6 @@ typedef void (^GIDAuthenticationCompletion)(GIDAuthentication *_Nullable authent /// The estimated expiration date of the ID token. @property(nonatomic, readonly, nullable) NSDate *idTokenExpirationDate; -/// Gets a new authorizer for `GTLService`, `GTMSessionFetcher`, or `GTMHTTPFetcher`. -/// -/// @return A new authorizer -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -- (id)fetcherAuthorizer; -#pragma clang diagnostic pop - /// Get a valid access token and a valid ID token, refreshing them first if they have expired or are /// about to expire. /// diff --git a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h index bc7ef324..32a26440 100644 --- a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h +++ b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h @@ -16,8 +16,6 @@ #import -NS_ASSUME_NONNULL_BEGIN - // We have to import GTMAppAuth because forward declaring the protocol does // not generate the `fetcherAuthorizer` method below for Swift. #ifdef SWIFT_PACKAGE @@ -31,6 +29,8 @@ NS_ASSUME_NONNULL_BEGIN @class GIDToken; @class GIDProfileData; +NS_ASSUME_NONNULL_BEGIN + /// This class represents a user account. @interface GIDGoogleUser : NSObject @@ -64,7 +64,7 @@ NS_ASSUME_NONNULL_BEGIN /// A new authorizer for `GTLService`, `GTMSessionFetcher`, or `GTMHTTPFetcher`. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" -@property(nonatomic, readonly) (id)fetcherAuthorizer; +@property(nonatomic, readonly) id fetcherAuthorizer; #pragma clang diagnostic pop @end diff --git a/GoogleSignIn/Tests/Unit/GIDAuthenticationTest.m b/GoogleSignIn/Tests/Unit/GIDAuthenticationTest.m index 970ccdd9..41c094a6 100644 --- a/GoogleSignIn/Tests/Unit/GIDAuthenticationTest.m +++ b/GoogleSignIn/Tests/Unit/GIDAuthenticationTest.m @@ -240,19 +240,6 @@ - (void)testLegacyCoding { } #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST -- (void)testFetcherAuthorizer { - // This is really hard to test without assuming how GTMAppAuthFetcherAuthorization works - // internally, so let's just take the shortcut here by asserting we get a - // GTMAppAuthFetcherAuthorization object. - GIDAuthentication *auth = [self auth]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - id fetcherAuthroizer = auth.fetcherAuthorizer; -#pragma clang diagnostic pop - XCTAssertTrue([fetcherAuthroizer isKindOfClass:[GTMAppAuthFetcherAuthorization class]]); - XCTAssertTrue([fetcherAuthroizer canAuthorize]); -} - - (void)testDoWithFreshTokensWithBothExpired { // Both tokens expired 10 seconds ago. [self setExpireTimeForAccessToken:-10 IDToken:-10]; diff --git a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m index 1199fc48..0c4f1d26 100644 --- a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m +++ b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m @@ -149,6 +149,20 @@ - (void)testUpdateAuthState_tokensAreNotChanged { XCTAssertIdentical(user.refreshToken, refreshTokenBeforeUpdate); } +- (void)testFetcherAuthorizer { + // This is really hard to test without assuming how GTMAppAuthFetcherAuthorization works + // internally, so let's just take the shortcut here by asserting we get a + // GTMAppAuthFetcherAuthorization object. + GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:kAccessTokenExpiresIn + idTokenExpiresIn:kIDTokenExpiresIn]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + id fetcherAuthroizer = user.fetcherAuthorizer; +#pragma clang diagnostic pop + XCTAssertTrue([fetcherAuthroizer isKindOfClass:[GTMAppAuthFetcherAuthorization class]]); + XCTAssertTrue([fetcherAuthroizer canAuthorize]); +} + #pragma mark - Helpers - (GIDGoogleUser *)googleUserWithAccessTokenExpiresIn:(NSTimeInterval)accessTokenExpiresIn From ca97c303ac0f563690573c5da37184e4325a9a89 Mon Sep 17 00:00:00 2001 From: pinlu Date: Wed, 28 Sep 2022 15:38:21 -0700 Subject: [PATCH 09/26] Create GIDAppAuthFetcherAuthorizationWithEMMSupport class --- ...ppAuthFetcherAuthorizationWithEMMSupport.h | 47 +++++ ...ppAuthFetcherAuthorizationWithEMMSupport.m | 192 ++++++++++++++++++ .../Sources/GIDAuthentication_Private.h | 14 -- GoogleSignIn/Sources/GIDGoogleUser.m | 112 +--------- GoogleSignIn/Sources/GIDGoogleUser_Private.h | 2 +- GoogleSignIn/Sources/GIDSignIn.m | 17 +- .../Public/GoogleSignIn/GIDGoogleUser.h | 2 +- GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m | 9 +- GoogleSignIn/Tests/Unit/GIDSignInTest.m | 8 +- .../ViewModels/AuthenticationViewModel.swift | 2 + 10 files changed, 269 insertions(+), 136 deletions(-) create mode 100644 GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h create mode 100644 GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m diff --git a/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h b/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h new file mode 100644 index 00000000..674a072b --- /dev/null +++ b/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h @@ -0,0 +1,47 @@ +/* + * 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. + */ +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +#import + +#ifdef SWIFT_PACKAGE +@import GTMAppAuth; +#else +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +// A specialized GTMAppAuthFetcherAuthorization subclass with EMM support. +@interface GIDAppAuthFetcherAuthorizationWithEMMSupport : GTMAppAuthFetcherAuthorization + +// 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. ++ (NSDictionary *)updatedEMMParametersWithParameters:(NSDictionary *)parameters; + +// 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; + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST diff --git a/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m b/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m new file mode 100644 index 00000000..cab44c66 --- /dev/null +++ b/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m @@ -0,0 +1,192 @@ +// 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. + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +#import "GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h" + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST +#import "GoogleSignIn/Sources/GIDEMMErrorHandler.h" +#import "GoogleSignIn/Sources/GIDMDMPasscodeState.h" +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h" + +#ifdef SWIFT_PACKAGE +@import AppAuth; +@import GTMAppAuth; +#else +#import +#import +#endif + +// Additional parameter names for EMM. +static NSString *const kEMMSupportParameterName = @"emm_support"; +static NSString *const kEMMOSVersionParameterName = @"device_os"; +static NSString *const kEMMPasscodeInfoParameterName = @"emm_passcode_info"; + +// Old UIDevice system name for iOS. +static NSString *const kOldIOSSystemName = @"iPhone OS"; + +// New UIDevice system name for iOS. +static NSString *const kNewIOSSystemName = @"iOS"; + +NS_ASSUME_NONNULL_BEGIN + +// The specialized GTMAppAuthFetcherAuthorization delegate that handles potential EMM error +// responses. +@interface GTMAppAuthFetcherAuthorizationEMMChainedDelegate : 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 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 + GTMAppAuthFetcherAuthorizationEMMChainedDelegate *chainedDelegate = + [[GTMAppAuthFetcherAuthorizationEMMChainedDelegate 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) { + [[self class] handleTokenFetchEMMError:error completion:^(NSError *_Nullable error) { + handler(error); + }]; + }]; +} + ++ (void)handleTokenFetchEMMError:(nullable NSError *)error + completion:(void (^)(NSError *_Nullable))completion { + NSDictionary *errorJSON = error.userInfo[OIDOAuthErrorResponseErrorKey]; + if (errorJSON) { + __block BOOL handled = NO; + handled = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:errorJSON + completion:^() { + if (handled) { + completion([NSError errorWithDomain:kGIDSignInErrorDomain + code:kGIDSignInErrorCodeEMM + userInfo:error.userInfo]); + } else { + completion(error); + } + }]; + } else { + completion(error); + } +} + ++ (NSDictionary *)updatedEMMParametersWithParameters:(NSDictionary *)parameters { + return [[self class] parametersWithParameters:parameters + emmSupport:parameters[kEMMSupportParameterName] + isPasscodeInfoRequired:parameters[kEMMPasscodeInfoParameterName] != nil]; +} + + ++ (NSDictionary *)parametersWithParameters:(NSDictionary *)parameters + emmSupport:(nullable NSString *)emmSupport + isPasscodeInfoRequired:(BOOL)isPasscodeInfoRequired { + if (!emmSupport) { + return parameters; + } + NSMutableDictionary *allParameters = [(parameters ?: @{}) mutableCopy]; + allParameters[kEMMSupportParameterName] = emmSupport; + UIDevice *device = [UIDevice currentDevice]; + NSString *systemName = device.systemName; + if ([systemName isEqualToString:kOldIOSSystemName]) { + systemName = kNewIOSSystemName; + } + allParameters[kEMMOSVersionParameterName] = + [NSString stringWithFormat:@"%@ %@", systemName, device.systemVersion]; + if (isPasscodeInfoRequired) { + allParameters[kEMMPasscodeInfoParameterName] = [GIDMDMPasscodeState passcodeState].info; + } + return allParameters; +} + +@end + +#pragma mark - GTMAppAuthFetcherAuthorizationEMMChainedDelegate + +@implementation GTMAppAuthFetcherAuthorizationEMMChainedDelegate { + // 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. + GTMAppAuthFetcherAuthorizationEMMChainedDelegate *_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 { + [GIDAppAuthFetcherAuthorizationWithEMMSupport 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 + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST diff --git a/GoogleSignIn/Sources/GIDAuthentication_Private.h b/GoogleSignIn/Sources/GIDAuthentication_Private.h index 37245aa4..7bdb46c1 100644 --- a/GoogleSignIn/Sources/GIDAuthentication_Private.h +++ b/GoogleSignIn/Sources/GIDAuthentication_Private.h @@ -34,20 +34,6 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithAuthState:(OIDAuthState *)authState; -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST -// 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; - -// Gets a new set of URL parameters that contains updated EMM-related URL parameters if needed. -+ (NSDictionary *)updatedEMMParametersWithParameters:(NSDictionary *)parameters; - -// Handles potential EMM error from token fetch response. -+ (void)handleTokenFetchEMMError:(nullable NSError *)error - completion:(void (^)(NSError *_Nullable))completion; -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST - @end NS_ASSUME_NONNULL_END diff --git a/GoogleSignIn/Sources/GIDGoogleUser.m b/GoogleSignIn/Sources/GIDGoogleUser.m index 3bd78c09..50d0d59a 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser.m +++ b/GoogleSignIn/Sources/GIDGoogleUser.m @@ -18,6 +18,7 @@ #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDConfiguration.h" +#import "GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h" #import "GoogleSignIn/Sources/GIDAuthentication_Private.h" #import "GoogleSignIn/Sources/GIDProfileData_Private.h" #import "GoogleSignIn/Sources/GIDToken_Private.h" @@ -45,10 +46,6 @@ NS_ASSUME_NONNULL_BEGIN -// A specialized GTMAppAuthFetcherAuthorization subclass with EMM support. -@interface GTMAppAuthFetcherAuthorizationWithEMMSupport : GTMAppAuthFetcherAuthorization -@end - @interface GIDGoogleUser () @property(nonatomic, readwrite) GIDToken *accessToken; @@ -113,13 +110,14 @@ - (GIDConfiguration *)configuration { return _cachedConfiguration; } +// TODO(pinlu): Create fetcherAuthorizer at initialization and observe authState changes. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - (id)fetcherAuthorizer { #pragma clang diagnostic pop #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST GTMAppAuthFetcherAuthorization *authorization = self.emmSupport ? - [[GTMAppAuthFetcherAuthorizationWithEMMSupport alloc] initWithAuthState:_authState] : + [[GIDAppAuthFetcherAuthorizationWithEMMSupport alloc] initWithAuthState:_authState] : [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:_authState]; #elif TARGET_OS_OSX || TARGET_OS_MACCATALYST GTMAppAuthFetcherAuthorization *authorization = @@ -132,7 +130,7 @@ - (GIDConfiguration *)configuration { #pragma mark - Private Methods #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST -- (NSString *)emmSupport { +- (nullable NSString *)emmSupport { return _authState.lastAuthorizationResponse.request.additionalParameters[kEMMSupportParameterName]; } @@ -205,7 +203,7 @@ - (nullable NSString *)hostedDomain { - (nullable NSDictionary *)additionalRefreshParameters: (GTMAppAuthFetcherAuthorization *)authorization { #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - return [GIDAuthentication updatedEMMParametersWithParameters: + return [GIDAppAuthFetcherAuthorizationWithEMMSupport updatedEMMParametersWithParameters: authorization.authState.lastTokenResponse.request.additionalParameters]; #elif TARGET_OS_OSX || TARGET_OS_MACCATALYST return authorization.authState.lastTokenResponse.request.additionalParameters; @@ -243,104 +241,4 @@ - (void)encodeWithCoder:(NSCoder *)encoder { @end -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - -#pragma mark - GTMAppAuthFetcherAuthorizationEMMChainedDelegate - -// The specialized GTMAppAuthFetcherAuthorization delegate that handles potential EMM error -// responses. -@interface GTMAppAuthFetcherAuthorizationEMMChainedDelegate : 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 GTMAppAuthFetcherAuthorizationEMMChainedDelegate { - // 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. - GTMAppAuthFetcherAuthorizationEMMChainedDelegate *_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 { - [GIDAuthentication 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 - -#pragma mark - GTMAppAuthFetcherAuthorizationWithEMMSupport - -@implementation GTMAppAuthFetcherAuthorizationWithEMMSupport - -#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 - GTMAppAuthFetcherAuthorizationEMMChainedDelegate *chainedDelegate = - [[GTMAppAuthFetcherAuthorizationEMMChainedDelegate 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) { - [GIDAuthentication handleTokenFetchEMMError:error completion:^(NSError *_Nullable error) { - handler(error); - }]; - }]; -} - -@end - -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST - NS_ASSUME_NONNULL_END diff --git a/GoogleSignIn/Sources/GIDGoogleUser_Private.h b/GoogleSignIn/Sources/GIDGoogleUser_Private.h index 46b85168..a56996c9 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser_Private.h +++ b/GoogleSignIn/Sources/GIDGoogleUser_Private.h @@ -33,7 +33,7 @@ NS_ASSUME_NONNULL_BEGIN #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST // A string indicating support for Enterprise Mobility Management. -@property(nonatomic, readonly) NSString *emmSupport; +@property(nonatomic, readonly, nullable) NSString *emmSupport; #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST // Create a object with an auth state, scopes, and profile data. diff --git a/GoogleSignIn/Sources/GIDSignIn.m b/GoogleSignIn/Sources/GIDSignIn.m index 682c1995..66fac4ef 100644 --- a/GoogleSignIn/Sources/GIDSignIn.m +++ b/GoogleSignIn/Sources/GIDSignIn.m @@ -22,6 +22,7 @@ #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDProfileData.h" #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDUserAuth.h" +#import "GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h" #import "GoogleSignIn/Sources/GIDSignInInternalOptions.h" #import "GoogleSignIn/Sources/GIDSignInPreferences.h" #import "GoogleSignIn/Sources/GIDCallbackQueue.h" @@ -583,9 +584,9 @@ - (void)authenticateInteractivelyWithOptions:(GIDSignInInternalOptions *)options #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST [additionalParameters addEntriesFromDictionary: - [GIDAuthentication parametersWithParameters:options.extraParams - emmSupport:emmSupport - isPasscodeInfoRequired:NO]]; + [GIDAppAuthFetcherAuthorizationWithEMMSupport parametersWithParameters:options.extraParams + emmSupport:emmSupport + isPasscodeInfoRequired:NO]]; #elif TARGET_OS_OSX || TARGET_OS_MACCATALYST [additionalParameters addEntriesFromDictionary:options.extraParams]; #endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST @@ -732,9 +733,10 @@ - (void)maybeFetchToken:(GIDAuthFlow *)authFlow { authState.lastAuthorizationResponse.additionalParameters; NSString *passcodeInfoRequired = (NSString *)params[kEMMPasscodeInfoRequiredKeyName]; [additionalParameters addEntriesFromDictionary: - [GIDAuthentication parametersWithParameters:@{} - emmSupport:authFlow.emmSupport - isPasscodeInfoRequired:passcodeInfoRequired.length > 0]]; + [GIDAppAuthFetcherAuthorizationWithEMMSupport + parametersWithParameters:@{} + emmSupport:authFlow.emmSupport + isPasscodeInfoRequired:passcodeInfoRequired.length > 0]]; #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST additionalParameters[kSDKVersionLoggingParameter] = GIDVersion(); additionalParameters[kEnvironmentLoggingParameter] = GIDEnvironment(); @@ -760,7 +762,8 @@ - (void)maybeFetchToken:(GIDAuthFlow *)authFlow { #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST if (authFlow.emmSupport) { - [GIDAuthentication handleTokenFetchEMMError:error completion:^(NSError *error) { + [GIDAppAuthFetcherAuthorizationWithEMMSupport + handleTokenFetchEMMError:error completion:^(NSError *error) { authFlow.error = error; [authFlow next]; }]; diff --git a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h index 32a26440..ae8408a8 100644 --- a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h +++ b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h @@ -17,7 +17,7 @@ #import // We have to import GTMAppAuth because forward declaring the protocol does -// not generate the `fetcherAuthorizer` method below for Swift. +// not generate the `fetcherAuthorizer` property below for Swift. #ifdef SWIFT_PACKAGE @import GTMAppAuth; #else diff --git a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m index 0c4f1d26..b033c785 100644 --- a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m +++ b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m @@ -157,12 +157,15 @@ - (void)testFetcherAuthorizer { idTokenExpiresIn:kIDTokenExpiresIn]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - id fetcherAuthroizer = user.fetcherAuthorizer; + id fetcherAuthorizer = user.fetcherAuthorizer; #pragma clang diagnostic pop - XCTAssertTrue([fetcherAuthroizer isKindOfClass:[GTMAppAuthFetcherAuthorization class]]); - XCTAssertTrue([fetcherAuthroizer canAuthorize]); + XCTAssertTrue([fetcherAuthorizer isKindOfClass:[GTMAppAuthFetcherAuthorization class]]); + XCTAssertTrue([fetcherAuthorizer canAuthorize]); } +// TODO(pinlu): Add a test that the property `fetcherAuthorizer` returns the same instance +// after authState is updated. + #pragma mark - Helpers - (GIDGoogleUser *)googleUserWithAccessTokenExpiresIn:(NSTimeInterval)accessTokenExpiresIn diff --git a/GoogleSignIn/Tests/Unit/GIDSignInTest.m b/GoogleSignIn/Tests/Unit/GIDSignInTest.m index fdcd1027..373265ef 100644 --- a/GoogleSignIn/Tests/Unit/GIDSignInTest.m +++ b/GoogleSignIn/Tests/Unit/GIDSignInTest.m @@ -26,6 +26,7 @@ // Test module imports @import GoogleSignIn; +#import "GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h" #import "GoogleSignIn/Sources/GIDGoogleUser_Private.h" #import "GoogleSignIn/Sources/GIDSignIn_Private.h" #import "GoogleSignIn/Sources/GIDSignInPreferences.h" @@ -1068,9 +1069,10 @@ - (void)testTokenEndpointEMMError { NSError *emmError = [NSError errorWithDomain:@"anydomain" code:12345 userInfo:@{ OIDOAuthErrorFieldError : errorJSON }]; - [[_authentication expect] handleTokenFetchEMMError:emmError - completion:SAVE_TO_ARG_BLOCK(completion)]; - + id appAuthFetcherAuthorization = + OCMStrictClassMock([GIDAppAuthFetcherAuthorizationWithEMMSupport class]); + [[appAuthFetcherAuthorization expect] handleTokenFetchEMMError:emmError + completion:SAVE_TO_ARG_BLOCK(completion)]; [self OAuthLoginWithAddScopesFlow:NO authError:nil diff --git a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift index 3ad14289..7f92ee71 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift @@ -40,6 +40,8 @@ final class AuthenticationViewModel: ObservableObject { init() { if let user = GIDSignIn.sharedInstance.currentUser { self.state = .signedIn(user) + + let authorization = user.fetcherAuthorizer } else { self.state = .signedOut } From ad40f6c795afa9bb86cf1c3d0b313710aa07f55f Mon Sep 17 00:00:00 2001 From: pinlu Date: Wed, 28 Sep 2022 18:37:40 -0700 Subject: [PATCH 10/26] Minor improvements. --- .../GIDAppAuthFetcherAuthorizationWithEMMSupport.h | 4 ++-- .../GIDAppAuthFetcherAuthorizationWithEMMSupport.m | 5 ++--- GoogleSignIn/Sources/GIDAuthentication_Private.h | 8 -------- GoogleSignIn/Tests/Unit/GIDSignInTest.m | 2 +- .../Shared/ViewModels/AuthenticationViewModel.swift | 2 -- 5 files changed, 5 insertions(+), 16 deletions(-) diff --git a/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h b/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h index 674a072b..5594670e 100644 --- a/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h +++ b/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - #import +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + #ifdef SWIFT_PACKAGE @import GTMAppAuth; #else diff --git a/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m b/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m index cab44c66..cb831cf3 100644 --- a/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m +++ b/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m @@ -12,15 +12,14 @@ // 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" -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST #import "GoogleSignIn/Sources/GIDEMMErrorHandler.h" #import "GoogleSignIn/Sources/GIDMDMPasscodeState.h" -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST - #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h" #ifdef SWIFT_PACKAGE diff --git a/GoogleSignIn/Sources/GIDAuthentication_Private.h b/GoogleSignIn/Sources/GIDAuthentication_Private.h index 7bdb46c1..113728bf 100644 --- a/GoogleSignIn/Sources/GIDAuthentication_Private.h +++ b/GoogleSignIn/Sources/GIDAuthentication_Private.h @@ -16,14 +16,6 @@ #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDAuthentication.h" -#ifdef SWIFT_PACKAGE -@import AppAuth; -@import GTMAppAuth; -#else -#import -#import -#endif - NS_ASSUME_NONNULL_BEGIN // Internal methods for the class that are not part of the public API. diff --git a/GoogleSignIn/Tests/Unit/GIDSignInTest.m b/GoogleSignIn/Tests/Unit/GIDSignInTest.m index 373265ef..5ff4f5b6 100644 --- a/GoogleSignIn/Tests/Unit/GIDSignInTest.m +++ b/GoogleSignIn/Tests/Unit/GIDSignInTest.m @@ -1091,7 +1091,7 @@ - (void)testTokenEndpointEMMError { [self waitForExpectationsWithTimeout:1 handler:nil]; - [_authentication verify]; + [appAuthFetcherAuthorization verify]; XCTAssertFalse(_keychainSaved, @"should not save to keychain"); XCTAssertTrue(_completionCalled, @"should call delegate"); XCTAssertNotNil(_authError, @"should have error"); diff --git a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift index 7f92ee71..3ad14289 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/ViewModels/AuthenticationViewModel.swift @@ -40,8 +40,6 @@ final class AuthenticationViewModel: ObservableObject { init() { if let user = GIDSignIn.sharedInstance.currentUser { self.state = .signedIn(user) - - let authorization = user.fetcherAuthorizer } else { self.state = .signedOut } From bd3257d7b4ed0acfa408338c1ce948cf16db2e95 Mon Sep 17 00:00:00 2001 From: pinlu Date: Thu, 29 Sep 2022 11:19:31 -0700 Subject: [PATCH 11/26] Update GIDAppAuthFetcherAuthorizationWithEMMSupport.m --- ...ppAuthFetcherAuthorizationWithEMMSupport.m | 100 +++++++++--------- 1 file changed, 49 insertions(+), 51 deletions(-) diff --git a/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m b/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m index cb831cf3..96a8b579 100644 --- a/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m +++ b/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m @@ -57,6 +57,55 @@ - (void)authentication:(GTMAppAuthFetcherAuthorization *)auth @end +@implementation GTMAppAuthFetcherAuthorizationEMMChainedDelegate { + // 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. + GTMAppAuthFetcherAuthorizationEMMChainedDelegate *_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 { + [GIDAppAuthFetcherAuthorizationWithEMMSupport 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 @@ -135,57 +184,6 @@ + (NSDictionary *)parametersWithParameters:(NSDictionary *)parameters @end -#pragma mark - GTMAppAuthFetcherAuthorizationEMMChainedDelegate - -@implementation GTMAppAuthFetcherAuthorizationEMMChainedDelegate { - // 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. - GTMAppAuthFetcherAuthorizationEMMChainedDelegate *_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 { - [GIDAppAuthFetcherAuthorizationWithEMMSupport 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 - NS_ASSUME_NONNULL_END #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST From f87f063cf83cf56a3b3983d7855c86a147c64b9a Mon Sep 17 00:00:00 2001 From: pinlu Date: Thu, 29 Sep 2022 15:15:04 -0700 Subject: [PATCH 12/26] Create fetcherAuthorizer at initialization --- GoogleSignIn/Sources/GIDGoogleUser.m | 30 +++++++++------------ GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m | 11 ++++++-- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/GoogleSignIn/Sources/GIDGoogleUser.m b/GoogleSignIn/Sources/GIDGoogleUser.m index 50d0d59a..82d8e159 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser.m +++ b/GoogleSignIn/Sources/GIDGoogleUser.m @@ -54,6 +54,8 @@ @interface GIDGoogleUser () @property(nonatomic, readwrite, nullable) GIDToken *idToken; +@property(nonatomic, readwrite) id fetcherAuthorizer; + @end @implementation GIDGoogleUser { @@ -110,23 +112,6 @@ - (GIDConfiguration *)configuration { return _cachedConfiguration; } -// TODO(pinlu): Create fetcherAuthorizer at initialization and observe authState changes. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -- (id)fetcherAuthorizer { -#pragma clang diagnostic pop -#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]; -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST - authorization.tokenRefreshDelegate = self; - return authorization; -} - #pragma mark - Private Methods #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST @@ -140,6 +125,17 @@ - (instancetype)initWithAuthState:(OIDAuthState *)authState profileData:(nullable GIDProfileData *)profileData { self = [super init]; if (self) { +#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]; +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST + authorization.tokenRefreshDelegate = self; + self.fetcherAuthorizer = authorization; + [self updateAuthState:authState profileData:profileData]; } return self; diff --git a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m index b033c785..da29db8e 100644 --- a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m +++ b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m @@ -163,8 +163,15 @@ - (void)testFetcherAuthorizer { XCTAssertTrue([fetcherAuthorizer canAuthorize]); } -// TODO(pinlu): Add a test that the property `fetcherAuthorizer` returns the same instance -// after authState is updated. +- (void)testFetcherAuthorizer_returnTheSameInstance { + GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:kAccessTokenExpiresIn + idTokenExpiresIn:kIDTokenExpiresIn]; + + id fetcherAuthorizer = user.fetcherAuthorizer; + id fetcherAuthorizer2 = user.fetcherAuthorizer; + + XCTAssertIdentical(fetcherAuthorizer, fetcherAuthorizer2); +} #pragma mark - Helpers From 9f9f85ab547dd0407ae63551ccbfc25dd9436af1 Mon Sep 17 00:00:00 2001 From: pinlu Date: Thu, 29 Sep 2022 15:50:56 -0700 Subject: [PATCH 13/26] Fix test failures --- GoogleSignIn/Sources/GIDGoogleUser.m | 22 +++++++++++----------- GoogleSignIn/Tests/Unit/GIDSignInTest.m | 1 + 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/GoogleSignIn/Sources/GIDGoogleUser.m b/GoogleSignIn/Sources/GIDGoogleUser.m index 82d8e159..e73f9273 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser.m +++ b/GoogleSignIn/Sources/GIDGoogleUser.m @@ -125,17 +125,6 @@ - (instancetype)initWithAuthState:(OIDAuthState *)authState profileData:(nullable GIDProfileData *)profileData { self = [super init]; if (self) { -#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]; -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST - authorization.tokenRefreshDelegate = self; - self.fetcherAuthorizer = authorization; - [self updateAuthState:authState profileData:profileData]; } return self; @@ -148,6 +137,17 @@ - (void)updateAuthState:(OIDAuthState *)authState _authentication = [[GIDAuthentication alloc] initWithAuthState:authState]; _profile = profileData; +#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]; +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST + authorization.tokenRefreshDelegate = self; + self.fetcherAuthorizer = authorization; + [self updateTokensWithAuthState:authState]; } } diff --git a/GoogleSignIn/Tests/Unit/GIDSignInTest.m b/GoogleSignIn/Tests/Unit/GIDSignInTest.m index 5ff4f5b6..8fa0fbb7 100644 --- a/GoogleSignIn/Tests/Unit/GIDSignInTest.m +++ b/GoogleSignIn/Tests/Unit/GIDSignInTest.m @@ -397,6 +397,7 @@ - (void)testShareInstance { - (void)testRestorePreviousSignInNoRefresh_hasPreviousUser { [[[_authorization expect] andReturn:_authState] authState]; + [[_authorization expect] setTokenRefreshDelegate:OCMOCK_ANY]; OCMStub([_authState lastTokenResponse]).andReturn(_tokenResponse); OCMStub([_authState refreshToken]).andReturn(kRefreshToken); From c622c9733d2d518ffcdbc7f1497fe04ae48213b5 Mon Sep 17 00:00:00 2001 From: pinlu Date: Thu, 29 Sep 2022 16:09:51 -0700 Subject: [PATCH 14/26] Suppress the deprecation warnings. --- GoogleSignIn/Sources/GIDGoogleUser.m | 3 +++ GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h | 2 +- GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m | 5 ++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/GoogleSignIn/Sources/GIDGoogleUser.m b/GoogleSignIn/Sources/GIDGoogleUser.m index e73f9273..9a0fa89a 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser.m +++ b/GoogleSignIn/Sources/GIDGoogleUser.m @@ -54,7 +54,10 @@ @interface GIDGoogleUser () @property(nonatomic, readwrite, nullable) GIDToken *idToken; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" @property(nonatomic, readwrite) id fetcherAuthorizer; +#pragma clang diagnostic pop @end diff --git a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h index ae8408a8..39176735 100644 --- a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h +++ b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h @@ -61,7 +61,7 @@ NS_ASSUME_NONNULL_BEGIN /// see https://developers.google.com/identity/sign-in/ios/backend-auth. @property(nonatomic, readonly, nullable) GIDToken *idToken; -/// A new authorizer for `GTLService`, `GTMSessionFetcher`, or `GTMHTTPFetcher`. +/// The authorizer for `GTLService`, `GTMSessionFetcher`, or `GTMHTTPFetcher`. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @property(nonatomic, readonly) id fetcherAuthorizer; diff --git a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m index da29db8e..ae2e561d 100644 --- a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m +++ b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m @@ -167,9 +167,12 @@ - (void)testFetcherAuthorizer_returnTheSameInstance { GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:kAccessTokenExpiresIn idTokenExpiresIn:kIDTokenExpiresIn]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" id fetcherAuthorizer = user.fetcherAuthorizer; id fetcherAuthorizer2 = user.fetcherAuthorizer; - +#pragma clang diagnostic pop + XCTAssertIdentical(fetcherAuthorizer, fetcherAuthorizer2); } From 51bb4246bb65697615f75d44807a6b1480425bc8 Mon Sep 17 00:00:00 2001 From: pinlu Date: Sat, 1 Oct 2022 12:45:45 -0700 Subject: [PATCH 15/26] Added Utility class GIDEMMSupport to handle EMM related issues. --- ...ppAuthFetcherAuthorizationWithEMMSupport.h | 13 +-- ...ppAuthFetcherAuthorizationWithEMMSupport.m | 78 ++------------ GoogleSignIn/Sources/GIDEMMSupport.h | 44 ++++++++ GoogleSignIn/Sources/GIDEMMSupport.m | 101 ++++++++++++++++++ GoogleSignIn/Sources/GIDGoogleUser.m | 22 +--- GoogleSignIn/Sources/GIDGoogleUser_Private.h | 11 ++ GoogleSignIn/Sources/GIDSignIn.m | 18 ++-- GoogleSignIn/Tests/Unit/GIDSignInTest.m | 11 +- 8 files changed, 182 insertions(+), 116 deletions(-) create mode 100644 GoogleSignIn/Sources/GIDEMMSupport.h create mode 100644 GoogleSignIn/Sources/GIDEMMSupport.m diff --git a/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h b/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h index 5594670e..baffe3c1 100644 --- a/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h +++ b/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + #import #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST @@ -28,18 +29,6 @@ NS_ASSUME_NONNULL_BEGIN // A specialized GTMAppAuthFetcherAuthorization subclass with EMM support. @interface GIDAppAuthFetcherAuthorizationWithEMMSupport : GTMAppAuthFetcherAuthorization -// 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. -+ (NSDictionary *)updatedEMMParametersWithParameters:(NSDictionary *)parameters; - -// 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; - @end NS_ASSUME_NONNULL_END diff --git a/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m b/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m index 96a8b579..2fa68e3f 100644 --- a/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m +++ b/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m @@ -18,9 +18,7 @@ #import "GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h" -#import "GoogleSignIn/Sources/GIDEMMErrorHandler.h" -#import "GoogleSignIn/Sources/GIDMDMPasscodeState.h" -#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h" +#import "GoogleSignIn/Sources/GIDEMMSupport.h" #ifdef SWIFT_PACKAGE @import AppAuth; @@ -30,22 +28,11 @@ #import #endif -// Additional parameter names for EMM. -static NSString *const kEMMSupportParameterName = @"emm_support"; -static NSString *const kEMMOSVersionParameterName = @"device_os"; -static NSString *const kEMMPasscodeInfoParameterName = @"emm_passcode_info"; - -// Old UIDevice system name for iOS. -static NSString *const kOldIOSSystemName = @"iPhone OS"; - -// New UIDevice system name for iOS. -static NSString *const kNewIOSSystemName = @"iOS"; - NS_ASSUME_NONNULL_BEGIN // The specialized GTMAppAuthFetcherAuthorization delegate that handles potential EMM error // responses. -@interface GTMAppAuthFetcherAuthorizationEMMChainedDelegate : NSObject +@interface GIDAppAuthFetcherAuthorizationEMMChainedDelegate : NSObject // Initializes with chained delegate and selector. - (instancetype)initWithDelegate:(id)delegate selector:(SEL)selector; @@ -57,13 +44,13 @@ - (void)authentication:(GTMAppAuthFetcherAuthorization *)auth @end -@implementation GTMAppAuthFetcherAuthorizationEMMChainedDelegate { +@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. - GTMAppAuthFetcherAuthorizationEMMChainedDelegate *_retained_self; + GIDAppAuthFetcherAuthorizationEMMChainedDelegate *_retained_self; } - (instancetype)initWithDelegate:(id)delegate selector:(SEL)selector { @@ -79,8 +66,7 @@ - (instancetype)initWithDelegate:(id)delegate selector:(SEL)selector { - (void)authentication:(GTMAppAuthFetcherAuthorization *)auth request:(NSMutableURLRequest *)request finishedWithError:(nullable NSError *)error { - [GIDAppAuthFetcherAuthorizationWithEMMSupport handleTokenFetchEMMError:error - completion:^(NSError *_Nullable error) { + [GIDEMMSupport handleTokenFetchEMMError:error completion:^(NSError *_Nullable error) { if (!self->_delegate || !self->_selector) { return; } @@ -114,8 +100,8 @@ - (void)authorizeRequest:(nullable NSMutableURLRequest *)request delegate:(id)delegate didFinishSelector:(SEL)sel { #pragma clang diagnostic pop - GTMAppAuthFetcherAuthorizationEMMChainedDelegate *chainedDelegate = - [[GTMAppAuthFetcherAuthorizationEMMChainedDelegate alloc] initWithDelegate:delegate + GIDAppAuthFetcherAuthorizationEMMChainedDelegate *chainedDelegate = + [[GIDAppAuthFetcherAuthorizationEMMChainedDelegate alloc] initWithDelegate:delegate selector:sel]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -128,60 +114,12 @@ - (void)authorizeRequest:(nullable NSMutableURLRequest *)request - (void)authorizeRequest:(nullable NSMutableURLRequest *)request completionHandler:(GTMAppAuthFetcherAuthorizationCompletion)handler { [super authorizeRequest:request completionHandler:^(NSError *_Nullable error) { - [[self class] handleTokenFetchEMMError:error completion:^(NSError *_Nullable error) { + [GIDEMMSupport handleTokenFetchEMMError:error completion:^(NSError *_Nullable error) { handler(error); }]; }]; } -+ (void)handleTokenFetchEMMError:(nullable NSError *)error - completion:(void (^)(NSError *_Nullable))completion { - NSDictionary *errorJSON = error.userInfo[OIDOAuthErrorResponseErrorKey]; - if (errorJSON) { - __block BOOL handled = NO; - handled = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:errorJSON - completion:^() { - if (handled) { - completion([NSError errorWithDomain:kGIDSignInErrorDomain - code:kGIDSignInErrorCodeEMM - userInfo:error.userInfo]); - } else { - completion(error); - } - }]; - } else { - completion(error); - } -} - -+ (NSDictionary *)updatedEMMParametersWithParameters:(NSDictionary *)parameters { - return [[self class] parametersWithParameters:parameters - emmSupport:parameters[kEMMSupportParameterName] - isPasscodeInfoRequired:parameters[kEMMPasscodeInfoParameterName] != nil]; -} - - -+ (NSDictionary *)parametersWithParameters:(NSDictionary *)parameters - emmSupport:(nullable NSString *)emmSupport - isPasscodeInfoRequired:(BOOL)isPasscodeInfoRequired { - if (!emmSupport) { - return parameters; - } - NSMutableDictionary *allParameters = [(parameters ?: @{}) mutableCopy]; - allParameters[kEMMSupportParameterName] = emmSupport; - UIDevice *device = [UIDevice currentDevice]; - NSString *systemName = device.systemName; - if ([systemName isEqualToString:kOldIOSSystemName]) { - systemName = kNewIOSSystemName; - } - allParameters[kEMMOSVersionParameterName] = - [NSString stringWithFormat:@"%@ %@", systemName, device.systemVersion]; - if (isPasscodeInfoRequired) { - allParameters[kEMMPasscodeInfoParameterName] = [GIDMDMPasscodeState passcodeState].info; - } - return allParameters; -} - @end NS_ASSUME_NONNULL_END diff --git a/GoogleSignIn/Sources/GIDEMMSupport.h b/GoogleSignIn/Sources/GIDEMMSupport.h new file mode 100644 index 00000000..d6f4e92e --- /dev/null +++ b/GoogleSignIn/Sources/GIDEMMSupport.h @@ -0,0 +1,44 @@ +/* + * 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 + +NS_ASSUME_NONNULL_BEGIN + +// A class to support EMM (Enterprise Mobility Management). +@interface GIDEMMSupport : NSObject + +// 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. ++ (NSDictionary *)updatedEMMParametersWithParameters:(NSDictionary *)parameters; + +// 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; + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST diff --git a/GoogleSignIn/Sources/GIDEMMSupport.m b/GoogleSignIn/Sources/GIDEMMSupport.m new file mode 100644 index 00000000..654aaeb3 --- /dev/null +++ b/GoogleSignIn/Sources/GIDEMMSupport.m @@ -0,0 +1,101 @@ +/* + * 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/GIDEMMSupport.h" + +#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h" + +#import "GoogleSignIn/Sources/GIDEMMErrorHandler.h" +#import "GoogleSignIn/Sources/GIDMDMPasscodeState.h" + +#ifdef SWIFT_PACKAGE +@import AppAuth; +#else +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +// Additional parameter names for EMM. +static NSString *const kEMMSupportParameterName = @"emm_support"; +static NSString *const kEMMOSVersionParameterName = @"device_os"; +static NSString *const kEMMPasscodeInfoParameterName = @"emm_passcode_info"; + +// Old UIDevice system name for iOS. +static NSString *const kOldIOSSystemName = @"iPhone OS"; + +// New UIDevice system name for iOS. +static NSString *const kNewIOSSystemName = @"iOS"; + +@implementation GIDEMMSupport + ++ (void)handleTokenFetchEMMError:(nullable NSError *)error + completion:(void (^)(NSError *_Nullable))completion { + NSDictionary *errorJSON = error.userInfo[OIDOAuthErrorResponseErrorKey]; + if (errorJSON) { + __block BOOL handled = NO; + handled = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:errorJSON + completion:^() { + if (handled) { + completion([NSError errorWithDomain:kGIDSignInErrorDomain + code:kGIDSignInErrorCodeEMM + userInfo:error.userInfo]); + } else { + completion(error); + } + }]; + } else { + completion(error); + } +} + ++ (NSDictionary *)updatedEMMParametersWithParameters:(NSDictionary *)parameters { + return [[self class] parametersWithParameters:parameters + emmSupport:parameters[kEMMSupportParameterName] + isPasscodeInfoRequired:parameters[kEMMPasscodeInfoParameterName] != nil]; +} + + ++ (NSDictionary *)parametersWithParameters:(NSDictionary *)parameters + emmSupport:(nullable NSString *)emmSupport + isPasscodeInfoRequired:(BOOL)isPasscodeInfoRequired { + if (!emmSupport) { + return parameters; + } + NSMutableDictionary *allParameters = [(parameters ?: @{}) mutableCopy]; + allParameters[kEMMSupportParameterName] = emmSupport; + UIDevice *device = [UIDevice currentDevice]; + NSString *systemName = device.systemName; + if ([systemName isEqualToString:kOldIOSSystemName]) { + systemName = kNewIOSSystemName; + } + allParameters[kEMMOSVersionParameterName] = + [NSString stringWithFormat:@"%@ %@", systemName, device.systemVersion]; + if (isPasscodeInfoRequired) { + allParameters[kEMMPasscodeInfoParameterName] = [GIDMDMPasscodeState passcodeState].info; + } + return allParameters; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST diff --git a/GoogleSignIn/Sources/GIDGoogleUser.m b/GoogleSignIn/Sources/GIDGoogleUser.m index 9a0fa89a..241798c2 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser.m +++ b/GoogleSignIn/Sources/GIDGoogleUser.m @@ -20,6 +20,7 @@ #import "GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h" #import "GoogleSignIn/Sources/GIDAuthentication_Private.h" +#import "GoogleSignIn/Sources/GIDEMMSupport.h" #import "GoogleSignIn/Sources/GIDProfileData_Private.h" #import "GoogleSignIn/Sources/GIDToken_Private.h" @@ -29,6 +30,8 @@ #import #endif +NS_ASSUME_NONNULL_BEGIN + // The ID Token claim key for the hosted domain value. static NSString *const kHostedDomainIDTokenClaimKey = @"hd"; @@ -44,23 +47,6 @@ // Additional parameter names for EMM. static NSString *const kEMMSupportParameterName = @"emm_support"; -NS_ASSUME_NONNULL_BEGIN - -@interface GIDGoogleUser () - -@property(nonatomic, readwrite) GIDToken *accessToken; - -@property(nonatomic, readwrite) GIDToken *refreshToken; - -@property(nonatomic, readwrite, nullable) GIDToken *idToken; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -@property(nonatomic, readwrite) id fetcherAuthorizer; -#pragma clang diagnostic pop - -@end - @implementation GIDGoogleUser { OIDAuthState *_authState; GIDConfiguration *_cachedConfiguration; @@ -202,7 +188,7 @@ - (nullable NSString *)hostedDomain { - (nullable NSDictionary *)additionalRefreshParameters: (GTMAppAuthFetcherAuthorization *)authorization { #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - return [GIDAppAuthFetcherAuthorizationWithEMMSupport updatedEMMParametersWithParameters: + return [GIDEMMSupport updatedEMMParametersWithParameters: authorization.authState.lastTokenResponse.request.additionalParameters]; #elif TARGET_OS_OSX || TARGET_OS_MACCATALYST return authorization.authState.lastTokenResponse.request.additionalParameters; diff --git a/GoogleSignIn/Sources/GIDGoogleUser_Private.h b/GoogleSignIn/Sources/GIDGoogleUser_Private.h index a56996c9..3687bfa0 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser_Private.h +++ b/GoogleSignIn/Sources/GIDGoogleUser_Private.h @@ -31,6 +31,17 @@ NS_ASSUME_NONNULL_BEGIN // Internal methods for the class that are not part of the public API. @interface GIDGoogleUser () +@property(nonatomic, readwrite) GIDToken *accessToken; + +@property(nonatomic, readwrite) GIDToken *refreshToken; + +@property(nonatomic, readwrite, nullable) GIDToken *idToken; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +@property(nonatomic, readwrite) id fetcherAuthorizer; +#pragma clang diagnostic pop + #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST // A string indicating support for Enterprise Mobility Management. @property(nonatomic, readonly, nullable) NSString *emmSupport; diff --git a/GoogleSignIn/Sources/GIDSignIn.m b/GoogleSignIn/Sources/GIDSignIn.m index 66fac4ef..e84682ee 100644 --- a/GoogleSignIn/Sources/GIDSignIn.m +++ b/GoogleSignIn/Sources/GIDSignIn.m @@ -22,7 +22,7 @@ #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDProfileData.h" #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDUserAuth.h" -#import "GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h" +#import "GoogleSignIn/Sources/GIDEMMSupport.h" #import "GoogleSignIn/Sources/GIDSignInInternalOptions.h" #import "GoogleSignIn/Sources/GIDSignInPreferences.h" #import "GoogleSignIn/Sources/GIDCallbackQueue.h" @@ -584,9 +584,9 @@ - (void)authenticateInteractivelyWithOptions:(GIDSignInInternalOptions *)options #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST [additionalParameters addEntriesFromDictionary: - [GIDAppAuthFetcherAuthorizationWithEMMSupport parametersWithParameters:options.extraParams - emmSupport:emmSupport - isPasscodeInfoRequired:NO]]; + [GIDEMMSupport parametersWithParameters:options.extraParams + emmSupport:emmSupport + isPasscodeInfoRequired:NO]]; #elif TARGET_OS_OSX || TARGET_OS_MACCATALYST [additionalParameters addEntriesFromDictionary:options.extraParams]; #endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST @@ -733,10 +733,9 @@ - (void)maybeFetchToken:(GIDAuthFlow *)authFlow { authState.lastAuthorizationResponse.additionalParameters; NSString *passcodeInfoRequired = (NSString *)params[kEMMPasscodeInfoRequiredKeyName]; [additionalParameters addEntriesFromDictionary: - [GIDAppAuthFetcherAuthorizationWithEMMSupport - parametersWithParameters:@{} - emmSupport:authFlow.emmSupport - isPasscodeInfoRequired:passcodeInfoRequired.length > 0]]; + [GIDEMMSupport parametersWithParameters:@{} + emmSupport:authFlow.emmSupport + isPasscodeInfoRequired:passcodeInfoRequired.length > 0]]; #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST additionalParameters[kSDKVersionLoggingParameter] = GIDVersion(); additionalParameters[kEnvironmentLoggingParameter] = GIDEnvironment(); @@ -762,8 +761,7 @@ - (void)maybeFetchToken:(GIDAuthFlow *)authFlow { #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST if (authFlow.emmSupport) { - [GIDAppAuthFetcherAuthorizationWithEMMSupport - handleTokenFetchEMMError:error completion:^(NSError *error) { + [GIDEMMSupport handleTokenFetchEMMError:error completion:^(NSError *error) { authFlow.error = error; [authFlow next]; }]; diff --git a/GoogleSignIn/Tests/Unit/GIDSignInTest.m b/GoogleSignIn/Tests/Unit/GIDSignInTest.m index 8fa0fbb7..a5f0c21f 100644 --- a/GoogleSignIn/Tests/Unit/GIDSignInTest.m +++ b/GoogleSignIn/Tests/Unit/GIDSignInTest.m @@ -26,7 +26,7 @@ // Test module imports @import GoogleSignIn; -#import "GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h" +#import "GoogleSignIn/Sources/GIDEMMSupport.h" #import "GoogleSignIn/Sources/GIDGoogleUser_Private.h" #import "GoogleSignIn/Sources/GIDSignIn_Private.h" #import "GoogleSignIn/Sources/GIDSignInPreferences.h" @@ -1070,10 +1070,9 @@ - (void)testTokenEndpointEMMError { NSError *emmError = [NSError errorWithDomain:@"anydomain" code:12345 userInfo:@{ OIDOAuthErrorFieldError : errorJSON }]; - id appAuthFetcherAuthorization = - OCMStrictClassMock([GIDAppAuthFetcherAuthorizationWithEMMSupport class]); - [[appAuthFetcherAuthorization expect] handleTokenFetchEMMError:emmError - completion:SAVE_TO_ARG_BLOCK(completion)]; + id emmSupport = OCMStrictClassMock([GIDEMMSupport class]); + [[emmSupport expect] handleTokenFetchEMMError:emmError + completion:SAVE_TO_ARG_BLOCK(completion)]; [self OAuthLoginWithAddScopesFlow:NO authError:nil @@ -1092,7 +1091,7 @@ - (void)testTokenEndpointEMMError { [self waitForExpectationsWithTimeout:1 handler:nil]; - [appAuthFetcherAuthorization verify]; + [emmSupport verify]; XCTAssertFalse(_keychainSaved, @"should not save to keychain"); XCTAssertTrue(_completionCalled, @"should call delegate"); XCTAssertNotNil(_authError, @"should have error"); From befe97774ee7de5cab1e7acbed77d77f6c667651 Mon Sep 17 00:00:00 2001 From: pinlu Date: Sun, 2 Oct 2022 21:40:10 -0700 Subject: [PATCH 16/26] Minor improvement. --- GoogleSignIn/Sources/GIDEMMSupport.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/GoogleSignIn/Sources/GIDEMMSupport.m b/GoogleSignIn/Sources/GIDEMMSupport.m index 654aaeb3..a796f5fb 100644 --- a/GoogleSignIn/Sources/GIDEMMSupport.m +++ b/GoogleSignIn/Sources/GIDEMMSupport.m @@ -67,9 +67,9 @@ + (void)handleTokenFetchEMMError:(nullable NSError *)error } + (NSDictionary *)updatedEMMParametersWithParameters:(NSDictionary *)parameters { - return [[self class] parametersWithParameters:parameters - emmSupport:parameters[kEMMSupportParameterName] - isPasscodeInfoRequired:parameters[kEMMPasscodeInfoParameterName] != nil]; + return [self parametersWithParameters:parameters + emmSupport:parameters[kEMMSupportParameterName] + isPasscodeInfoRequired:parameters[kEMMPasscodeInfoParameterName] != nil]; } From 33705b481ff534f7bbd8e952b5e8b56af1ce1e0b Mon Sep 17 00:00:00 2001 From: pinlu Date: Mon, 3 Oct 2022 14:02:52 -0700 Subject: [PATCH 17/26] Move `doWithFreshTokens:` into GIDGoogleUser API --- ...ppAuthFetcherAuthorizationWithEMMSupport.m | 28 +- GoogleSignIn/Sources/GIDAuthentication.m | 259 ---- .../Sources/GIDAuthentication_Private.h | 31 - GoogleSignIn/Sources/GIDGoogleUser.m | 136 +- GoogleSignIn/Sources/GIDGoogleUser_Private.h | 10 +- GoogleSignIn/Sources/GIDSignIn.m | 15 +- .../Public/GoogleSignIn/GIDAuthentication.h | 70 - .../Public/GoogleSignIn/GIDGoogleUser.h | 14 +- .../Public/GoogleSignIn/GoogleSignIn.h | 1 - .../Tests/Unit/GIDAuthentication+Testing.h | 25 - .../Tests/Unit/GIDAuthentication+Testing.m | 46 - .../Tests/Unit/GIDAuthenticationTest.m | 1346 ++++++++--------- .../Tests/Unit/GIDGoogleUser+Testing.m | 9 +- GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m | 13 +- GoogleSignIn/Tests/Unit/GIDSignInTest.m | 29 +- .../Shared/Services/BirthdayLoader.swift | 9 +- 16 files changed, 852 insertions(+), 1189 deletions(-) delete mode 100644 GoogleSignIn/Sources/GIDAuthentication.m delete mode 100644 GoogleSignIn/Sources/GIDAuthentication_Private.h delete mode 100644 GoogleSignIn/Sources/Public/GoogleSignIn/GIDAuthentication.h delete mode 100644 GoogleSignIn/Tests/Unit/GIDAuthentication+Testing.h delete mode 100644 GoogleSignIn/Tests/Unit/GIDAuthentication+Testing.m diff --git a/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m b/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m index 2fa68e3f..814a73fc 100644 --- a/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m +++ b/GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.m @@ -1,16 +1,18 @@ -// 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. +/* + * 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 diff --git a/GoogleSignIn/Sources/GIDAuthentication.m b/GoogleSignIn/Sources/GIDAuthentication.m deleted file mode 100644 index 927be7a1..00000000 --- a/GoogleSignIn/Sources/GIDAuthentication.m +++ /dev/null @@ -1,259 +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/GIDAuthentication.h" - -#import "GoogleSignIn/Sources/GIDAuthentication_Private.h" - -#import "GoogleSignIn/Sources/GIDSignInPreferences.h" - -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST -#import "GoogleSignIn/Sources/GIDEMMErrorHandler.h" -#import "GoogleSignIn/Sources/GIDMDMPasscodeState.h" -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST - -#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h" - -#ifdef SWIFT_PACKAGE -@import AppAuth; -#else -#import -#import -#import -#import -#import -#import -#import -#import -#endif - -NS_ASSUME_NONNULL_BEGIN - -// Minimal time interval before expiration for the access token or it needs to be refreshed. -NSTimeInterval kMinimalTimeToExpire = 60.0; - -// Key constants used for encode and decode. -static NSString *const kAuthStateKey = @"authState"; - -// Additional parameter names for EMM. -static NSString *const kEMMSupportParameterName = @"emm_support"; -static NSString *const kEMMOSVersionParameterName = @"device_os"; -static NSString *const kEMMPasscodeInfoParameterName = @"emm_passcode_info"; - -// Old UIDevice system name for iOS. -static NSString *const kOldIOSSystemName = @"iPhone OS"; - -// New UIDevice system name for iOS. -static NSString *const kNewIOSSystemName = @"iOS"; - -@implementation GIDAuthentication { - // A queue for pending authentication handlers so we don't fire multiple requests in parallel. - // Access to this ivar should be synchronized. - NSMutableArray *_authenticationHandlerQueue; -} - -- (instancetype)initWithAuthState:(OIDAuthState *)authState { - if (!authState) { - return nil; - } - self = [super init]; - if (self) { - _authenticationHandlerQueue = [[NSMutableArray alloc] init]; - _authState = authState; - } - return self; -} - -#pragma mark - Public property accessors - -- (NSString *)clientID { - return _authState.lastAuthorizationResponse.request.clientID; -} - -- (NSString *)accessToken { - return _authState.lastTokenResponse.accessToken; -} - -- (NSDate *)accessTokenExpirationDate { - return _authState.lastTokenResponse.accessTokenExpirationDate; -} - -- (NSString *)refreshToken { - return _authState.refreshToken; -} - -- (nullable NSString *)idToken { - return _authState.lastTokenResponse.idToken; -} - -- (nullable NSDate *)idTokenExpirationDate { - return [[[OIDIDToken alloc] initWithIDTokenString:self.idToken] expiresAt]; -} - -#pragma mark - Public methods - -- (void)doWithFreshTokens:(GIDAuthenticationCompletion)completion { - if (!([self.accessTokenExpirationDate timeIntervalSinceNow] < kMinimalTimeToExpire || - (self.idToken && [self.idTokenExpirationDate timeIntervalSinceNow] < kMinimalTimeToExpire))) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(self, nil); - }); - return; - } - @synchronized (_authenticationHandlerQueue) { - // Push the handler into the callback queue. - [_authenticationHandlerQueue addObject:[completion copy]]; - if (_authenticationHandlerQueue.count > 1) { - // This is not the first handler in the queue, no fetch is needed. - return; - } - } - // This is the first handler in the queue, a fetch is needed. - NSMutableDictionary *additionalParameters = [@{} mutableCopy]; -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - [additionalParameters addEntriesFromDictionary: - [GIDAuthentication updatedEMMParametersWithParameters: - _authState.lastTokenResponse.request.additionalParameters]]; -#elif TARGET_OS_OSX || TARGET_OS_MACCATALYST - [additionalParameters addEntriesFromDictionary: - _authState.lastTokenResponse.request.additionalParameters]; -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST - additionalParameters[kSDKVersionLoggingParameter] = GIDVersion(); - additionalParameters[kEnvironmentLoggingParameter] = GIDEnvironment(); - - OIDTokenRequest *tokenRefreshRequest = - [_authState tokenRefreshRequestWithAdditionalParameters:additionalParameters]; - [OIDAuthorizationService performTokenRequest:tokenRefreshRequest - originalAuthorizationResponse:_authState.lastAuthorizationResponse - callback:^(OIDTokenResponse *_Nullable tokenResponse, - NSError *_Nullable error) { - if (tokenResponse) { - [self willChangeValueForKey:NSStringFromSelector(@selector(accessToken))]; - [self willChangeValueForKey:NSStringFromSelector(@selector(accessTokenExpirationDate))]; - [self willChangeValueForKey:NSStringFromSelector(@selector(idToken))]; - [self willChangeValueForKey:NSStringFromSelector(@selector(idTokenExpirationDate))]; - [self->_authState updateWithTokenResponse:tokenResponse error:nil]; - [self didChangeValueForKey:NSStringFromSelector(@selector(accessToken))]; - [self didChangeValueForKey:NSStringFromSelector(@selector(accessTokenExpirationDate))]; - [self didChangeValueForKey:NSStringFromSelector(@selector(idToken))]; - [self didChangeValueForKey:NSStringFromSelector(@selector(idTokenExpirationDate))]; - } else { - if (error.domain == OIDOAuthTokenErrorDomain) { - [self->_authState updateWithAuthorizationError:error]; - } - } -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - [GIDAuthentication handleTokenFetchEMMError:error completion:^(NSError *_Nullable error) { - // Process the handler queue to call back. - NSArray *authenticationHandlerQueue; - @synchronized(self->_authenticationHandlerQueue) { - authenticationHandlerQueue = [self->_authenticationHandlerQueue copy]; - [self->_authenticationHandlerQueue removeAllObjects]; - } - for (GIDAuthenticationCompletion completion in authenticationHandlerQueue) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(error ? nil : self, error); - }); - } - }]; -#elif TARGET_OS_OSX || TARGET_OS_MACCATALYST - NSArray *authenticationHandlerQueue; - @synchronized(self->_authenticationHandlerQueue) { - authenticationHandlerQueue = [self->_authenticationHandlerQueue copy]; - [self->_authenticationHandlerQueue removeAllObjects]; - } - for (GIDAuthenticationCompletion completion in authenticationHandlerQueue) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(error ? nil : self, error); - }); - } -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST - }]; -} - -#pragma mark - Private methods - -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - -+ (NSDictionary *)parametersWithParameters:(NSDictionary *)parameters - emmSupport:(nullable NSString *)emmSupport - isPasscodeInfoRequired:(BOOL)isPasscodeInfoRequired { - if (!emmSupport) { - return parameters; - } - NSMutableDictionary *allParameters = [(parameters ?: @{}) mutableCopy]; - allParameters[kEMMSupportParameterName] = emmSupport; - UIDevice *device = [UIDevice currentDevice]; - NSString *systemName = device.systemName; - if ([systemName isEqualToString:kOldIOSSystemName]) { - systemName = kNewIOSSystemName; - } - allParameters[kEMMOSVersionParameterName] = - [NSString stringWithFormat:@"%@ %@", systemName, device.systemVersion]; - if (isPasscodeInfoRequired) { - allParameters[kEMMPasscodeInfoParameterName] = [GIDMDMPasscodeState passcodeState].info; - } - return allParameters; -} - -+ (NSDictionary *)updatedEMMParametersWithParameters:(NSDictionary *)parameters { - return [self parametersWithParameters:parameters - emmSupport:parameters[kEMMSupportParameterName] - isPasscodeInfoRequired:parameters[kEMMPasscodeInfoParameterName] != nil]; -} - -+ (void)handleTokenFetchEMMError:(nullable NSError *)error - completion:(void (^)(NSError *_Nullable))completion { - NSDictionary *errorJSON = error.userInfo[OIDOAuthErrorResponseErrorKey]; - if (errorJSON) { - __block BOOL handled = NO; - handled = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:errorJSON - completion:^() { - if (handled) { - completion([NSError errorWithDomain:kGIDSignInErrorDomain - code:kGIDSignInErrorCodeEMM - userInfo:error.userInfo]); - } else { - completion(error); - } - }]; - } else { - completion(error); - } -} - -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST - -#pragma mark - NSSecureCoding - -+ (BOOL)supportsSecureCoding { - return YES; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)decoder { - self = [super init]; - if (self) { - _authenticationHandlerQueue = [[NSMutableArray alloc] init]; - _authState = [decoder decodeObjectOfClass:[OIDAuthState class] forKey:kAuthStateKey]; - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)encoder { - [encoder encodeObject:_authState forKey:kAuthStateKey]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleSignIn/Sources/GIDAuthentication_Private.h b/GoogleSignIn/Sources/GIDAuthentication_Private.h deleted file mode 100644 index 113728bf..00000000 --- a/GoogleSignIn/Sources/GIDAuthentication_Private.h +++ /dev/null @@ -1,31 +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/GIDAuthentication.h" - -NS_ASSUME_NONNULL_BEGIN - -// Internal methods for the class that are not part of the public API. -@interface GIDAuthentication () - -// A representation of the state of the OAuth session for this instance. -@property(nonatomic, readonly) OIDAuthState *authState; - -- (instancetype)initWithAuthState:(OIDAuthState *)authState; - -@end - -NS_ASSUME_NONNULL_END diff --git a/GoogleSignIn/Sources/GIDGoogleUser.m b/GoogleSignIn/Sources/GIDGoogleUser.m index 241798c2..ee3823e0 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser.m +++ b/GoogleSignIn/Sources/GIDGoogleUser.m @@ -19,9 +19,9 @@ #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDConfiguration.h" #import "GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h" -#import "GoogleSignIn/Sources/GIDAuthentication_Private.h" #import "GoogleSignIn/Sources/GIDEMMSupport.h" #import "GoogleSignIn/Sources/GIDProfileData_Private.h" +#import "GoogleSignIn/Sources/GIDSignInPreferences.h" #import "GoogleSignIn/Sources/GIDToken_Private.h" #ifdef SWIFT_PACKAGE @@ -47,9 +47,16 @@ // Additional parameter names for EMM. static NSString *const kEMMSupportParameterName = @"emm_support"; +// Minimal time interval before expiration for the access token or it needs to be refreshed. +NSTimeInterval kMinimalTimeToExpire = 60.0; + @implementation GIDGoogleUser { OIDAuthState *_authState; GIDConfiguration *_cachedConfiguration; + + // A queue for pending refrsh token handlers so we don't fire multiple requests in parallel. + // Access to this ivar should be synchronized. + NSMutableArray *_refreshTokensHandlerQueue; } - (nullable NSString *)userID { @@ -101,6 +108,77 @@ - (GIDConfiguration *)configuration { return _cachedConfiguration; } +- (void)doWithFreshTokens:(GIDGoogleUserCompletion)completion { + if (!([self.accessToken.expirationDate timeIntervalSinceNow] < kMinimalTimeToExpire || + (self.idToken && [self.idToken.expirationDate timeIntervalSinceNow] < kMinimalTimeToExpire))) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(self, nil); + }); + return; + } + @synchronized (_refreshTokensHandlerQueue) { + // Push the handler into the callback queue. + [_refreshTokensHandlerQueue addObject:[completion copy]]; + if (_refreshTokensHandlerQueue.count > 1) { + // This is not the first handler in the queue, no fetch is needed. + return; + } + } + // This is the first handler in the queue, a fetch is needed. + NSMutableDictionary *additionalParameters = [@{} mutableCopy]; +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + [additionalParameters addEntriesFromDictionary: + [GIDEMMSupport updatedEMMParametersWithParameters: + _authState.lastTokenResponse.request.additionalParameters]]; +#elif TARGET_OS_OSX || TARGET_OS_MACCATALYST + [additionalParameters addEntriesFromDictionary: + _authState.lastTokenResponse.request.additionalParameters]; +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST + additionalParameters[kSDKVersionLoggingParameter] = GIDVersion(); + additionalParameters[kEnvironmentLoggingParameter] = GIDEnvironment(); + + OIDTokenRequest *tokenRefreshRequest = + [_authState tokenRefreshRequestWithAdditionalParameters:additionalParameters]; + [OIDAuthorizationService performTokenRequest:tokenRefreshRequest + originalAuthorizationResponse:_authState.lastAuthorizationResponse + callback:^(OIDTokenResponse *_Nullable tokenResponse, + NSError *_Nullable error) { + if (tokenResponse) { + [self->_authState updateWithTokenResponse:tokenResponse error:nil]; + } else { + if (error.domain == OIDOAuthTokenErrorDomain) { + [self->_authState updateWithAuthorizationError:error]; + } + } +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + [GIDEMMSupport handleTokenFetchEMMError:error completion:^(NSError *_Nullable error) { + // Process the handler queue to call back. + NSArray *refreshTokensHandlerQueue; + @synchronized(self->_refreshTokensHandlerQueue) { + refreshTokensHandlerQueue = [self->_refreshTokensHandlerQueue copy]; + [self->_refreshTokensHandlerQueue removeAllObjects]; + } + for (GIDGoogleUserCompletion completion in refreshTokensHandlerQueue) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(error ? nil : self, error); + }); + } + }]; +#elif TARGET_OS_OSX || TARGET_OS_MACCATALYST + NSArray *refreshTokensHandlerQueue; + @synchronized(self->_refreshTokensHandlerQueue) { + refreshTokensHandlerQueue = [self->_refreshTokensHandlerQueue copy]; + [self->_refreshTokensHandlerQueue removeAllObjects]; + } + for (GIDAuthenticationCompletion completion in refreshTokensHandlerQueue) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(error ? nil : self, error); + }); + } +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST + }]; +} + #pragma mark - Private Methods #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST @@ -114,16 +192,9 @@ - (instancetype)initWithAuthState:(OIDAuthState *)authState profileData:(nullable GIDProfileData *)profileData { self = [super init]; if (self) { - [self updateAuthState:authState profileData:profileData]; - } - return self; -} - -- (void)updateAuthState:(OIDAuthState *)authState - profileData:(nullable GIDProfileData *)profileData { - @synchronized(self) { + _refreshTokensHandlerQueue = [[NSMutableArray alloc] init]; _authState = authState; - _authentication = [[GIDAuthentication alloc] initWithAuthState:authState]; + _authState.stateChangeDelegate = self; _profile = profileData; #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST @@ -139,6 +210,17 @@ - (void)updateAuthState:(OIDAuthState *)authState [self updateTokensWithAuthState:authState]; } + return self; +} + +- (void)updateWithTokenResponse:(nullable OIDTokenResponse *)tokenResponse + profileData:(nullable GIDProfileData *)profileData { + @synchronized(self) { + _profile = profileData; + if (tokenResponse) { + [_authState updateWithTokenResponse:tokenResponse error:nil]; + } + } } - (void)updateTokensWithAuthState:(OIDAuthState *)authState { @@ -195,6 +277,12 @@ - (nullable NSDictionary *)additionalRefreshParameters: #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST } +#pragma mark - OIDAuthStateChangeDelegate + +- (void)didChangeState:(OIDAuthState *)state { + [self updateTokensWithAuthState:state]; +} + #pragma mark - NSSecureCoding + (BOOL)supportsSecureCoding { @@ -204,17 +292,23 @@ + (BOOL)supportsSecureCoding { - (nullable instancetype)initWithCoder:(NSCoder *)decoder { self = [super init]; if (self) { - GIDProfileData *profileData = - [decoder decodeObjectOfClass:[GIDProfileData class] forKey:kProfileDataKey]; - OIDAuthState *authState; - if ([decoder containsValueForKey:kAuthState]) { // Current encoding - authState = [decoder decodeObjectOfClass:[OIDAuthState class] forKey:kAuthState]; - } else { // Old encoding - GIDAuthentication *authentication = [decoder decodeObjectOfClass:[GIDAuthentication class] - forKey:kAuthenticationKey]; - authState = authentication.authState; - } - [self updateAuthState:authState profileData:profileData]; + _refreshTokensHandlerQueue = [[NSMutableArray alloc] init]; + _profile = [decoder decodeObjectOfClass:[GIDProfileData class] forKey:kProfileDataKey]; + _authState = [decoder decodeObjectOfClass:[OIDAuthState class] forKey:kAuthState]; + _authState.stateChangeDelegate = self; + +#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]; +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST + authorization.tokenRefreshDelegate = self; + self.fetcherAuthorizer = authorization; + + [self updateTokensWithAuthState:_authState]; } return self; } diff --git a/GoogleSignIn/Sources/GIDGoogleUser_Private.h b/GoogleSignIn/Sources/GIDGoogleUser_Private.h index 3687bfa0..b53e99df 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser_Private.h +++ b/GoogleSignIn/Sources/GIDGoogleUser_Private.h @@ -29,7 +29,8 @@ NS_ASSUME_NONNULL_BEGIN @class OIDAuthState; // Internal methods for the class that are not part of the public API. -@interface GIDGoogleUser () +@interface GIDGoogleUser () @property(nonatomic, readwrite) GIDToken *accessToken; @@ -37,6 +38,9 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, readwrite, nullable) GIDToken *idToken; +// A representation of the state of the OAuth session for this instance. +@property(nonatomic, readonly) OIDAuthState *authState; + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @property(nonatomic, readwrite) id fetcherAuthorizer; @@ -52,8 +56,8 @@ NS_ASSUME_NONNULL_BEGIN profileData:(nullable GIDProfileData *)profileData; // Update the auth state and profile data. -- (void)updateAuthState:(OIDAuthState *)authState - profileData:(nullable GIDProfileData *)profileData; +- (void)updateWithTokenResponse:(nullable OIDTokenResponse *)tokenResponse + profileData:(nullable GIDProfileData *)profileData; @end diff --git a/GoogleSignIn/Sources/GIDSignIn.m b/GoogleSignIn/Sources/GIDSignIn.m index e84682ee..f51d06d3 100644 --- a/GoogleSignIn/Sources/GIDSignIn.m +++ b/GoogleSignIn/Sources/GIDSignIn.m @@ -16,7 +16,6 @@ #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" @@ -33,7 +32,6 @@ #import "GoogleSignIn/Sources/GIDEMMErrorHandler.h" #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST -#import "GoogleSignIn/Sources/GIDAuthentication_Private.h" #import "GoogleSignIn/Sources/GIDGoogleUser_Private.h" #import "GoogleSignIn/Sources/GIDProfileData_Private.h" #import "GoogleSignIn/Sources/GIDUserAuth_Private.h" @@ -183,7 +181,7 @@ - (BOOL)handleURL:(NSURL *)url { } - (BOOL)hasPreviousSignIn { - if ([_currentUser.authentication.authState isAuthorized]) { + if ([_currentUser.authState isAuthorized]) { return YES; } OIDAuthState *authState = [self loadAuthState]; @@ -415,8 +413,7 @@ - (void)signOut { } - (void)disconnectWithCompletion:(nullable GIDDisconnectCompletion)completion { - GIDGoogleUser *user = _currentUser; - OIDAuthState *authState = user.authentication.authState; + OIDAuthState *authState = _currentUser.authState; if (!authState) { // Even the user is not signed in right now, we still need to remove any token saved in the // keychain. @@ -536,8 +533,8 @@ - (void)signInWithOptions:(GIDSignInInternalOptions *)options { } // If this is a non-interactive flow, use cached authentication if possible. - if (!options.interactive && _currentUser.authentication) { - [_currentUser.authentication doWithFreshTokens:^(GIDAuthentication *unused, NSError *error) { + if (!options.interactive && _currentUser) { + [_currentUser doWithFreshTokens:^(GIDGoogleUser *unused, NSError *error) { if (error) { [self authenticateWithOptions:options]; } else { @@ -788,8 +785,8 @@ - (void)addSaveAuthCallback:(GIDAuthFlow *)authFlow { } if (self->_currentOptions.addScopesFlow) { - [self->_currentUser updateAuthState:authState - profileData:handlerAuthFlow.profileData]; + [self->_currentUser updateWithTokenResponse:authState.lastTokenResponse + profileData:handlerAuthFlow.profileData]; } else { GIDGoogleUser *user = [[GIDGoogleUser alloc] initWithAuthState:authState profileData:handlerAuthFlow.profileData]; diff --git a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDAuthentication.h b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDAuthentication.h deleted file mode 100644 index cba4cac6..00000000 --- a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDAuthentication.h +++ /dev/null @@ -1,70 +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 - -// We have to import GTMAppAuth because forward declaring the protocol does -// not generate the `fetcherAuthorizer` method below for Swift. -#ifdef SWIFT_PACKAGE -@import GTMAppAuth; -#else -#import -#endif - -@class GIDAuthentication; - -NS_ASSUME_NONNULL_BEGIN - -/// A completion block that takes a `GIDAuthentication` or an error if the attempt to refresh tokens -/// was unsuccessful. -typedef void (^GIDAuthenticationCompletion)(GIDAuthentication *_Nullable authentication, - NSError *_Nullable error); - -/// This class represents the OAuth 2.0 entities needed for sign-in. -@interface GIDAuthentication : NSObject - -/// The client ID associated with the authentication. -@property(nonatomic, readonly) NSString *clientID; - -/// The OAuth2 access token to access Google services. -@property(nonatomic, readonly) NSString *accessToken; - -/// The estimated expiration date of the access token. -@property(nonatomic, readonly) NSDate *accessTokenExpirationDate; - -/// The OAuth2 refresh token to exchange for new access tokens. -@property(nonatomic, readonly) NSString *refreshToken; - -/// An OpenID Connect ID token that identifies the user. Send this token to your server to -/// authenticate the user there. For more information on this topic, see -/// https://developers.google.com/identity/sign-in/ios/backend-auth -@property(nonatomic, readonly, nullable) NSString *idToken; - -/// The estimated expiration date of the ID token. -@property(nonatomic, readonly, nullable) NSDate *idTokenExpirationDate; - -/// Get a valid access token and a valid ID token, refreshing them first if they have expired or are -/// about to expire. -/// -/// @param completion A completion block that takes a `GIDAuthentication` or an error if the attempt -/// to refresh tokens was unsuccessful. The block will be called asynchronously on the main -/// queue. -- (void)doWithFreshTokens:(GIDAuthenticationCompletion)completion; - -@end - - -NS_ASSUME_NONNULL_END diff --git a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h index 39176735..6d2dac5f 100644 --- a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h +++ b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h @@ -24,13 +24,16 @@ #import #endif -@class GIDAuthentication; @class GIDConfiguration; +@class GIDGoogleUser; @class GIDToken; @class GIDProfileData; 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); + /// This class represents a user account. @interface GIDGoogleUser : NSObject @@ -40,9 +43,6 @@ NS_ASSUME_NONNULL_BEGIN /// Representation of basic profile data for the user. @property(nonatomic, readonly, nullable) GIDProfileData *profile; -/// The authentication object for the user. -@property(nonatomic, readonly) GIDAuthentication *authentication; - /// The API scopes granted to the app in an array of `NSString`. @property(nonatomic, readonly, nullable) NSArray *grantedScopes; @@ -67,6 +67,12 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, readonly) id fetcherAuthorizer; #pragma clang diagnostic pop +/// Get a valid access token and a valid ID token, refreshing them first if they have expired or are about to expire. +/// +/// @param completion A completion block that takes a `GIDGoogleUser` or an error if the attempt to refresh tokens was +/// unsuccessful. The block will be called asynchronously on the main queue. +- (void)doWithFreshTokens:(GIDGoogleUserCompletion)completion; + @end NS_ASSUME_NONNULL_END diff --git a/GoogleSignIn/Sources/Public/GoogleSignIn/GoogleSignIn.h b/GoogleSignIn/Sources/Public/GoogleSignIn/GoogleSignIn.h index d1b7afda..1b9d1042 100644 --- a/GoogleSignIn/Sources/Public/GoogleSignIn/GoogleSignIn.h +++ b/GoogleSignIn/Sources/Public/GoogleSignIn/GoogleSignIn.h @@ -15,7 +15,6 @@ */ #import -#import "GIDAuthentication.h" #import "GIDConfiguration.h" #import "GIDGoogleUser.h" #import "GIDProfileData.h" diff --git a/GoogleSignIn/Tests/Unit/GIDAuthentication+Testing.h b/GoogleSignIn/Tests/Unit/GIDAuthentication+Testing.h deleted file mode 100644 index 30672902..00000000 --- a/GoogleSignIn/Tests/Unit/GIDAuthentication+Testing.h +++ /dev/null @@ -1,25 +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/GIDAuthentication.h" - -@interface GIDAuthentication (Testing) - -- (BOOL)isEqual:(id)object; -- (BOOL)isEqualToAuthentication:(GIDAuthentication *)other; -- (NSUInteger)hash; - -@end diff --git a/GoogleSignIn/Tests/Unit/GIDAuthentication+Testing.m b/GoogleSignIn/Tests/Unit/GIDAuthentication+Testing.m deleted file mode 100644 index feae4cc1..00000000 --- a/GoogleSignIn/Tests/Unit/GIDAuthentication+Testing.m +++ /dev/null @@ -1,46 +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/Tests/Unit/GIDAuthentication+Testing.h" - -@implementation GIDAuthentication (Testing) - -- (BOOL)isEqual:(id)object { - if (self == object) { - return YES; - } - if (![object isKindOfClass:[GIDAuthentication class]]) { - return NO; - } - return [self isEqualToAuthentication:(GIDAuthentication *)object]; -} - -- (BOOL)isEqualToAuthentication:(GIDAuthentication *)other { - return [self.clientID isEqual:other.clientID] && - [self.accessToken isEqual:other.accessToken] && - [self.accessTokenExpirationDate isEqual:other.accessTokenExpirationDate] && - [self.refreshToken isEqual:other.refreshToken] && - (self.idToken == other.idToken || [self.idToken isEqual:other.idToken]) && - (self.idTokenExpirationDate == other.idTokenExpirationDate || - [self.idTokenExpirationDate isEqual:other.idTokenExpirationDate]); -} - -// Not the hash implemention you want to use on prod, but just to match |isEqual:| here. -- (NSUInteger)hash { - return [self.clientID hash] ^ [self.accessToken hash] ^ [self.accessTokenExpirationDate hash] ^ - [self.refreshToken hash] ^ [self.idToken hash] ^ [self.idTokenExpirationDate hash]; -} - -@end - diff --git a/GoogleSignIn/Tests/Unit/GIDAuthenticationTest.m b/GoogleSignIn/Tests/Unit/GIDAuthenticationTest.m index 41c094a6..9a898bac 100644 --- a/GoogleSignIn/Tests/Unit/GIDAuthenticationTest.m +++ b/GoogleSignIn/Tests/Unit/GIDAuthenticationTest.m @@ -1,673 +1,673 @@ -// 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 - -#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDAuthentication.h" -#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h" - -#import "GoogleSignIn/Sources/GIDAuthentication_Private.h" -#import "GoogleSignIn/Sources/GIDEMMErrorHandler.h" -#import "GoogleSignIn/Sources/GIDMDMPasscodeState.h" -#import "GoogleSignIn/Sources/GIDSignInPreferences.h" -#import "GoogleSignIn/Tests/Unit/OIDAuthState+Testing.h" -#import "GoogleSignIn/Tests/Unit/OIDTokenRequest+Testing.h" -#import "GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h" - -#ifdef SWIFT_PACKAGE -@import AppAuth; -@import GoogleUtilities_MethodSwizzler; -@import GoogleUtilities_SwizzlerTestHelpers; -@import GTMAppAuth; -@import GTMSessionFetcherCore; -@import OCMock; -#else -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#endif - -static NSString *const kClientID = @"87654321.googleusercontent.com"; -static NSString *const kNewAccessToken = @"new_access_token"; -static NSString *const kUserEmail = @"foo@gmail.com"; -static NSTimeInterval const kExpireTime = 442886117; -static NSTimeInterval const kNewExpireTime = 442886123; -static NSTimeInterval const kNewExpireTime2 = 442886124; - -static NSTimeInterval const kTimeAccuracy = 10; - -// The system name in old iOS versions. -static NSString *const kOldIOSName = @"iPhone OS"; - -// The system name in new iOS versions. -static NSString *const kNewIOSName = @"iOS"; - -// List of observed properties of the class being tested. -static NSString *const kObservedProperties[] = { - @"accessToken", - @"accessTokenExpirationDate", - @"idToken", - @"idTokenExpirationDate" -}; -static const NSUInteger kNumberOfObservedProperties = - sizeof(kObservedProperties) / sizeof(*kObservedProperties); - -// Bit position for notification change type bitmask flags. -// Must match the list of observed properties above. -typedef NS_ENUM(NSUInteger, ChangeType) { - kChangeTypeAccessTokenPrior, - kChangeTypeAccessToken, - kChangeTypeAccessTokenExpirationDatePrior, - kChangeTypeAccessTokenExpirationDate, - kChangeTypeIDTokenPrior, - kChangeTypeIDToken, - kChangeTypeIDTokenExpirationDatePrior, - kChangeTypeIDTokenExpirationDate, - kChangeTypeEnd // not a real change type but an end mark for calculating |kChangeAll| -}; - -static const NSUInteger kChangeNone = 0u; -static const NSUInteger kChangeAll = (1u << kChangeTypeEnd) - 1u; - -#if __has_feature(c_static_assert) || __has_extension(c_static_assert) -_Static_assert(kChangeTypeEnd == (sizeof(kObservedProperties) / sizeof(*kObservedProperties)) * 2, - "List of observed properties must match list of change notification enums"); -#endif - -@interface GIDAuthenticationTest : XCTestCase -@end - -@implementation GIDAuthenticationTest { - // Whether the auth object has ID token or not. - BOOL _hasIDToken; - - // Fake data used to generate the expiration date of the access token. - NSTimeInterval _accessTokenExpireTime; - - // Fake data used to generate the expiration date of the ID token. - NSTimeInterval _idTokenExpireTime; - - // Fake data used to generate the additional token request parameters. - NSDictionary *_additionalTokenRequestParameters; - - // The saved token fetch handler. - OIDTokenCallback _tokenFetchHandler; - - // The saved token request. - OIDTokenRequest *_tokenRequest; - - // All GIDAuthentication objects that are observed. - NSMutableArray *_observedAuths; - - // Bitmask flags for observed changes, as specified in |ChangeType|. - NSUInteger _changesObserved; - - // The fake system name used for testing. - NSString *_fakeSystemName; -} - -- (void)setUp { - _hasIDToken = YES; - _accessTokenExpireTime = kAccessTokenExpiresIn; - _idTokenExpireTime = kExpireTime; - _additionalTokenRequestParameters = nil; - _tokenFetchHandler = nil; - _tokenRequest = nil; - [GULSwizzler swizzleClass:[OIDAuthorizationService class] - selector:@selector(performTokenRequest:originalAuthorizationResponse:callback:) - isClassSelector:YES - withBlock:^(id sender, - OIDTokenRequest *request, - OIDAuthorizationResponse *authorizationResponse, - OIDTokenCallback callback) { - XCTAssertNotNil(authorizationResponse.request.clientID); - XCTAssertNotNil(authorizationResponse.request.configuration.tokenEndpoint); - XCTAssertNil(self->_tokenFetchHandler); // only one on-going fetch allowed - self->_tokenFetchHandler = [callback copy]; - self->_tokenRequest = [request copy]; - return nil; - }]; - _observedAuths = [[NSMutableArray alloc] init]; - _changesObserved = 0; - _fakeSystemName = kNewIOSName; -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - [GULSwizzler swizzleClass:[UIDevice class] - selector:@selector(systemName) - isClassSelector:NO - withBlock:^(id sender) { return self->_fakeSystemName; }]; -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST -} - -- (void)tearDown { - [GULSwizzler unswizzleClass:[OIDAuthorizationService class] - selector:@selector(performTokenRequest:originalAuthorizationResponse:callback:) - isClassSelector:YES]; -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - [GULSwizzler unswizzleClass:[UIDevice class] - selector:@selector(systemName) - isClassSelector:NO]; -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST - for (GIDAuthentication *auth in _observedAuths) { - for (unsigned int i = 0; i < kNumberOfObservedProperties; ++i) { - [auth removeObserver:self forKeyPath:kObservedProperties[i]]; - } - } - _observedAuths = nil; -} - -#pragma mark - Tests - -- (void)testInitWithAuthState { - OIDAuthState *authState = [OIDAuthState testInstance]; - GIDAuthentication *auth = [[GIDAuthentication alloc] initWithAuthState:authState]; - - XCTAssertEqualObjects(auth.clientID, authState.lastAuthorizationResponse.request.clientID); - XCTAssertEqualObjects(auth.accessToken, authState.lastTokenResponse.accessToken); - XCTAssertEqualObjects(auth.accessTokenExpirationDate, - authState.lastTokenResponse.accessTokenExpirationDate); - XCTAssertEqualObjects(auth.refreshToken, authState.refreshToken); - XCTAssertEqualObjects(auth.idToken, authState.lastTokenResponse.idToken); - OIDIDToken *idToken = [[OIDIDToken alloc] - initWithIDTokenString:authState.lastTokenResponse.idToken]; - XCTAssertEqualObjects(auth.idTokenExpirationDate, [idToken expiresAt]); -} - -- (void)testInitWithAuthStateNoIDToken { - OIDAuthState *authState = [OIDAuthState testInstanceWithIDToken:nil]; - GIDAuthentication *auth = [[GIDAuthentication alloc] initWithAuthState:authState]; - - XCTAssertEqualObjects(auth.clientID, authState.lastAuthorizationResponse.request.clientID); - XCTAssertEqualObjects(auth.accessToken, authState.lastTokenResponse.accessToken); - XCTAssertEqualObjects(auth.accessTokenExpirationDate, - authState.lastTokenResponse.accessTokenExpirationDate); - XCTAssertEqualObjects(auth.refreshToken, authState.refreshToken); - XCTAssertNil(auth.idToken); - XCTAssertNil(auth.idTokenExpirationDate); -} - -- (void)testAuthState { - OIDAuthState *authState = [OIDAuthState testInstance]; - GIDAuthentication *auth = [[GIDAuthentication alloc] initWithAuthState:authState]; - OIDAuthState *authStateReturned = auth.authState; - - XCTAssertEqual(authState, authStateReturned); -} - -- (void)testCoding { - if (@available(iOS 11, macOS 10.13, *)) { - GIDAuthentication *auth = [self auth]; - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:auth requiringSecureCoding:YES error:nil]; - GIDAuthentication *newAuth = [NSKeyedUnarchiver unarchivedObjectOfClass:[GIDAuthentication class] - fromData:data - error:nil]; - XCTAssertEqualObjects(auth, newAuth); - XCTAssertTrue([GIDAuthentication supportsSecureCoding]); - } else { - XCTSkip(@"Required API is not available for this test."); - } -} - -#if TARGET_OS_IOS || TARGET_OS_MACCATALYST -// Deprecated in iOS 13 and macOS 10.14 -- (void)testLegacyCoding { - GIDAuthentication *auth = [self auth]; - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:auth]; - GIDAuthentication *newAuth = [NSKeyedUnarchiver unarchiveObjectWithData:data]; - XCTAssertEqualObjects(auth, newAuth); - XCTAssertTrue([GIDAuthentication supportsSecureCoding]); -} -#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST - -- (void)testDoWithFreshTokensWithBothExpired { - // Both tokens expired 10 seconds ago. - [self setExpireTimeForAccessToken:-10 IDToken:-10]; - [self verifyTokensRefreshedWithMethod:@selector(doWithFreshTokens:)]; -} - -- (void)testDoWithFreshTokensWithAccessTokenExpired { - // Access token expired 10 seconds ago while ID token to expire in 10 minutes. - [self setExpireTimeForAccessToken:-10 IDToken:10 * 60]; - [self verifyTokensRefreshedWithMethod:@selector(doWithFreshTokens:)]; -} - -- (void)testDoWithFreshTokensWithIDTokenToExpire { - // Access token to expire in 10 minutes while ID token to expire in 10 seconds. - [self setExpireTimeForAccessToken:10 * 60 IDToken:10]; - [self verifyTokensRefreshedWithMethod:@selector(doWithFreshTokens:)]; -} - -- (void)testDoWithFreshTokensWithBothFresh { - // Both tokens to expire in 10 minutes. - [self setExpireTimeForAccessToken:10 * 60 IDToken:10 * 60]; - [self verifyTokensNotRefreshedWithMethod:@selector(doWithFreshTokens:)]; -} - -- (void)testDoWithFreshTokensWithAccessTokenExpiredAndNoIDToken { - _hasIDToken = NO; - [self setExpireTimeForAccessToken:-10 IDToken:10 * 60]; // access token expired 10 seconds ago - [self verifyTokensRefreshedWithMethod:@selector(doWithFreshTokens:)]; -} - -- (void)testDoWithFreshTokensWithAccessTokenFreshAndNoIDToken { - _hasIDToken = NO; - [self setExpireTimeForAccessToken:10 * 60 IDToken:-10]; // access token to expire in 10 minutes - [self verifyTokensNotRefreshedWithMethod:@selector(doWithFreshTokens:)]; -} - -- (void)testDoWithFreshTokensError { - [self setTokensExpireTime:-10]; // expired 10 seconds ago - GIDAuthentication *auth = [self observedAuth]; - XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"]; - [auth doWithFreshTokens:^(GIDAuthentication *authentication, NSError *error) { - [expectation fulfill]; - XCTAssertNil(authentication); - XCTAssertNotNil(error); - }]; - _tokenFetchHandler(nil, [self fakeError]); - [self waitForExpectationsWithTimeout:1 handler:nil]; - [self assertOldTokensInAuth:auth]; -} - -- (void)testDoWithFreshTokensQueue { - GIDAuthentication *auth = [self observedAuth]; - XCTestExpectation *firstExpectation = - [self expectationWithDescription:@"First callback is called"]; - [auth doWithFreshTokens:^(GIDAuthentication *authentication, NSError *error) { - [firstExpectation fulfill]; - [self assertNewTokensInAuth:authentication]; - XCTAssertNil(error); - }]; - XCTestExpectation *secondExpectation = - [self expectationWithDescription:@"Second callback is called"]; - [auth doWithFreshTokens:^(GIDAuthentication *authentication, NSError *error) { - [secondExpectation fulfill]; - [self assertNewTokensInAuth:authentication]; - XCTAssertNil(error); - }]; - _tokenFetchHandler([self tokenResponseWithNewTokens], nil); - [self waitForExpectationsWithTimeout:1 handler:nil]; - [self assertNewTokensInAuth:auth]; -} - -#pragma mark - EMM Support - -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - -- (void)testEMMSupport { - _additionalTokenRequestParameters = @{ - @"emm_support" : @"xyz", - }; - GIDAuthentication *auth = [self auth]; - [auth doWithFreshTokens:^(GIDAuthentication * _Nonnull authentication, - NSError * _Nullable error) {}]; - _tokenFetchHandler([self tokenResponseWithNewTokens], nil); - NSDictionary *expectedParameters = @{ - @"emm_support" : @"xyz", - @"device_os" : [NSString stringWithFormat:@"%@ %@", - _fakeSystemName, [UIDevice currentDevice].systemVersion], - kSDKVersionLoggingParameter : GIDVersion(), - kEnvironmentLoggingParameter : GIDEnvironment(), - }; - XCTAssertEqualObjects(auth.authState.lastTokenResponse.request.additionalParameters, - expectedParameters); -} - -- (void)testSystemNameNormalization { - _fakeSystemName = kOldIOSName; - _additionalTokenRequestParameters = @{ - @"emm_support" : @"xyz", - }; - GIDAuthentication *auth = [self auth]; - [auth doWithFreshTokens:^(GIDAuthentication * _Nonnull authentication, - NSError * _Nullable error) {}]; - _tokenFetchHandler([self tokenResponseWithNewTokens], nil); - NSDictionary *expectedParameters = @{ - @"emm_support" : @"xyz", - @"device_os" : [NSString stringWithFormat:@"%@ %@", - kNewIOSName, [UIDevice currentDevice].systemVersion], - kSDKVersionLoggingParameter : GIDVersion(), - kEnvironmentLoggingParameter : GIDEnvironment(), - }; - XCTAssertEqualObjects(auth.authState.lastTokenResponse.request.additionalParameters, - expectedParameters); -} - -- (void)testEMMPasscodeInfo { - _additionalTokenRequestParameters = @{ - @"emm_support" : @"xyz", - @"device_os" : @"old one", - @"emm_passcode_info" : @"something", - }; - GIDAuthentication *auth = [self auth]; - [auth doWithFreshTokens:^(GIDAuthentication * _Nonnull authentication, - NSError * _Nullable error) {}]; - _tokenFetchHandler([self tokenResponseWithNewTokens], nil); - NSDictionary *expectedParameters = @{ - @"emm_support" : @"xyz", - @"device_os" : [NSString stringWithFormat:@"%@ %@", - _fakeSystemName, [UIDevice currentDevice].systemVersion], - @"emm_passcode_info" : [GIDMDMPasscodeState passcodeState].info, - kSDKVersionLoggingParameter : GIDVersion(), - kEnvironmentLoggingParameter : GIDEnvironment(), - }; - XCTAssertEqualObjects(auth.authState.lastTokenResponse.request.additionalParameters, - expectedParameters); -} - -- (void)testEMMError { - // Set expectations. - NSDictionary *errorJSON = @{ @"error" : @"EMM Specific Error" }; - NSError *emmError = [NSError errorWithDomain:@"anydomain" - code:12345 - userInfo:@{ OIDOAuthErrorResponseErrorKey : errorJSON }]; - id mockEMMErrorHandler = OCMStrictClassMock([GIDEMMErrorHandler class]); - [[[mockEMMErrorHandler stub] andReturn:mockEMMErrorHandler] sharedInstance]; - __block void (^completion)(void); - [[[mockEMMErrorHandler expect] andReturnValue:@YES] - handleErrorFromResponse:errorJSON completion:[OCMArg checkWithBlock:^(id arg) { - completion = arg; - return YES; - }]]; - - // Start testing. - _additionalTokenRequestParameters = @{ - @"emm_support" : @"xyz", - }; - GIDAuthentication *auth = [self auth]; - XCTestExpectation *notCalled = [self expectationWithDescription:@"Callback is not called"]; - notCalled.inverted = YES; - XCTestExpectation *called = [self expectationWithDescription:@"Callback is called"]; - [auth doWithFreshTokens:^(GIDAuthentication *authentication, NSError *error) { - [notCalled fulfill]; - [called fulfill]; - XCTAssertNil(authentication); - XCTAssertEqualObjects(error.domain, kGIDSignInErrorDomain); - XCTAssertEqual(error.code, kGIDSignInErrorCodeEMM); - }]; - _tokenFetchHandler(nil, emmError); - - // Verify and clean up. - [mockEMMErrorHandler verify]; - [mockEMMErrorHandler stopMocking]; - [self waitForExpectations:@[ notCalled ] timeout:1]; - completion(); - [self waitForExpectations:@[ called ] timeout:1]; - [self assertOldTokensInAuth:auth]; -} - -- (void)testNonEMMError { - // Set expectations. - NSDictionary *errorJSON = @{ @"error" : @"Not EMM Specific Error" }; - NSError *emmError = [NSError errorWithDomain:@"anydomain" - code:12345 - userInfo:@{ OIDOAuthErrorResponseErrorKey : errorJSON }]; - id mockEMMErrorHandler = OCMStrictClassMock([GIDEMMErrorHandler class]); - [[[mockEMMErrorHandler stub] andReturn:mockEMMErrorHandler] sharedInstance]; - __block void (^completion)(void); - [[[mockEMMErrorHandler expect] andReturnValue:@NO] - handleErrorFromResponse:errorJSON completion:[OCMArg checkWithBlock:^(id arg) { - completion = arg; - return YES; - }]]; - - // Start testing. - _additionalTokenRequestParameters = @{ - @"emm_support" : @"xyz", - }; - GIDAuthentication *auth = [self auth]; - XCTestExpectation *notCalled = [self expectationWithDescription:@"Callback is not called"]; - notCalled.inverted = YES; - XCTestExpectation *called = [self expectationWithDescription:@"Callback is called"]; - [auth doWithFreshTokens:^(GIDAuthentication *authentication, NSError *error) { - [notCalled fulfill]; - [called fulfill]; - XCTAssertNil(authentication); - XCTAssertEqualObjects(error.domain, @"anydomain"); - XCTAssertEqual(error.code, 12345); - }]; - _tokenFetchHandler(nil, emmError); - - // Verify and clean up. - [mockEMMErrorHandler verify]; - [mockEMMErrorHandler stopMocking]; - [self waitForExpectations:@[ notCalled ] timeout:1]; - completion(); - [self waitForExpectations:@[ called ] timeout:1]; - [self assertOldTokensInAuth:auth]; -} - -- (void)testCodingPreserveEMMParameters { - _additionalTokenRequestParameters = @{ - @"emm_support" : @"xyz", - @"device_os" : @"old one", - @"emm_passcode_info" : @"something", - }; - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:[self auth]]; - GIDAuthentication *auth = [NSKeyedUnarchiver unarchiveObjectWithData:data]; - [auth doWithFreshTokens:^(GIDAuthentication * _Nonnull authentication, - NSError * _Nullable error) {}]; - _tokenFetchHandler([self tokenResponseWithNewTokens], nil); - NSDictionary *expectedParameters = @{ - @"emm_support" : @"xyz", - @"device_os" : [NSString stringWithFormat:@"%@ %@", - [UIDevice currentDevice].systemName, [UIDevice currentDevice].systemVersion], - @"emm_passcode_info" : [GIDMDMPasscodeState passcodeState].info, - kSDKVersionLoggingParameter : GIDVersion(), - kEnvironmentLoggingParameter : GIDEnvironment(), - }; - XCTAssertEqualObjects(auth.authState.lastTokenResponse.request.additionalParameters, - expectedParameters); -} - -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST - -#pragma mark - NSKeyValueObserving - -- (void)observeValueForKeyPath:(NSString *)keyPath - ofObject:(id)object - change:(NSDictionary *)change - context:(void *)context { - GIDAuthentication *auth = (GIDAuthentication *)object; - ChangeType changeType; - if ([keyPath isEqualToString:@"accessToken"]) { - if (change[NSKeyValueChangeNotificationIsPriorKey]) { - XCTAssertEqualObjects(auth.accessToken, kAccessToken); - changeType = kChangeTypeAccessTokenPrior; - } else { - XCTAssertEqualObjects(auth.accessToken, kNewAccessToken); - changeType = kChangeTypeAccessToken; - } - } else if ([keyPath isEqualToString:@"accessTokenExpirationDate"]) { - if (change[NSKeyValueChangeNotificationIsPriorKey]) { - [self assertDate:auth.accessTokenExpirationDate equalTime:_accessTokenExpireTime]; - changeType = kChangeTypeAccessTokenExpirationDatePrior; - } else { - [self assertDate:auth.accessTokenExpirationDate equalTime:kNewExpireTime]; - changeType = kChangeTypeAccessTokenExpirationDate; - } - } else if ([keyPath isEqualToString:@"idToken"]) { - if (change[NSKeyValueChangeNotificationIsPriorKey]) { - XCTAssertEqualObjects(auth.idToken, [self idToken]); - changeType = kChangeTypeIDTokenPrior; - } else { - XCTAssertEqualObjects(auth.idToken, [self idTokenNew]); - changeType = kChangeTypeIDToken; - } - } else if ([keyPath isEqualToString:@"idTokenExpirationDate"]) { - if (change[NSKeyValueChangeNotificationIsPriorKey]) { - if (_hasIDToken) { - [self assertDate:auth.idTokenExpirationDate equalTime:_idTokenExpireTime]; - } - changeType = kChangeTypeIDTokenExpirationDatePrior; - } else { - if (_hasIDToken) { - [self assertDate:auth.idTokenExpirationDate equalTime:kNewExpireTime2]; - } - changeType = kChangeTypeIDTokenExpirationDate; - } - } else { - XCTFail(@"unexpected keyPath"); - return; // so compiler knows |changeType| is always assigned - } - NSUInteger changeMask = 1u << changeType; - XCTAssertFalse(_changesObserved & changeMask); // each change type should only fire once - _changesObserved |= changeMask; -} - -#pragma mark - Helpers - -- (GIDAuthentication *)auth { - NSString *idToken = [self idToken]; - NSNumber *accessTokenExpiresIn = - @(_accessTokenExpireTime - [[NSDate date] timeIntervalSince1970]); - OIDTokenRequest *tokenRequest = - [OIDTokenRequest testInstanceWithAdditionalParameters:_additionalTokenRequestParameters]; - OIDTokenResponse *tokenResponse = - [OIDTokenResponse testInstanceWithIDToken:idToken - accessToken:kAccessToken - expiresIn:accessTokenExpiresIn - refreshToken:kRefreshToken - tokenRequest:tokenRequest]; - return [[GIDAuthentication alloc] - initWithAuthState:[OIDAuthState testInstanceWithTokenResponse:tokenResponse]]; -} - -- (NSString *)idTokenWithExpireTime:(NSTimeInterval)expireTime { - if (!_hasIDToken) { - return nil; - } - return [OIDTokenResponse idTokenWithSub:kUserID exp:@(expireTime)]; -} - -- (NSString *)idToken { - return [self idTokenWithExpireTime:_idTokenExpireTime]; -} - -- (NSString *)idTokenNew { - return [self idTokenWithExpireTime:kNewExpireTime2]; -} - -// Return the auth object that has certain property changes observed. -- (GIDAuthentication *)observedAuth { - GIDAuthentication *auth = [self auth]; - for (unsigned int i = 0; i < kNumberOfObservedProperties; ++i) { - [auth addObserver:self - forKeyPath:kObservedProperties[i] - options:NSKeyValueObservingOptionPrior - context:NULL]; - } - [_observedAuths addObject:auth]; - return auth; -} - -- (OIDTokenResponse *)tokenResponseWithNewTokens { - NSNumber *expiresIn = @(kNewExpireTime - [[NSDate date] timeIntervalSince1970]); - return [OIDTokenResponse testInstanceWithIDToken:(_hasIDToken ? [self idTokenNew] : nil) - accessToken:kNewAccessToken - expiresIn:expiresIn - refreshToken:kRefreshToken - tokenRequest:_tokenRequest ?: nil]; -} - -- (NSError *)fakeError { - return [NSError errorWithDomain:@"fake.domain" code:-1 userInfo:nil]; -} - -- (void)assertDate:(NSDate *)date equalTime:(NSTimeInterval)time { - XCTAssertEqualWithAccuracy([date timeIntervalSince1970], time, kTimeAccuracy); -} - -- (void)assertOldAccessTokenInAuth:(GIDAuthentication *)auth { - XCTAssertEqualObjects(auth.accessToken, kAccessToken); - [self assertDate:auth.accessTokenExpirationDate equalTime:_accessTokenExpireTime]; - XCTAssertEqual(_changesObserved, kChangeNone); -} - -- (void)assertNewAccessTokenInAuth:(GIDAuthentication *)auth { - XCTAssertEqualObjects(auth.accessToken, kNewAccessToken); - [self assertDate:auth.accessTokenExpirationDate equalTime:kNewExpireTime]; - XCTAssertEqual(_changesObserved, kChangeAll); -} - -- (void)assertOldTokensInAuth:(GIDAuthentication *)auth { - [self assertOldAccessTokenInAuth:auth]; - XCTAssertEqualObjects(auth.idToken, [self idToken]); - if (_hasIDToken) { - [self assertDate:auth.idTokenExpirationDate equalTime:_idTokenExpireTime]; - } -} - -- (void)assertNewTokensInAuth:(GIDAuthentication *)auth { - [self assertNewAccessTokenInAuth:auth]; - XCTAssertEqualObjects(auth.idToken, [self idTokenNew]); - if (_hasIDToken) { - [self assertDate:auth.idTokenExpirationDate equalTime:kNewExpireTime2]; - } -} - -- (void)setTokensExpireTime:(NSTimeInterval)fromNow { - [self setExpireTimeForAccessToken:fromNow IDToken:fromNow]; -} - -- (void)setExpireTimeForAccessToken:(NSTimeInterval)accessExpire IDToken:(NSTimeInterval)idExpire { - _accessTokenExpireTime = [[NSDate date] timeIntervalSince1970] + accessExpire; - _idTokenExpireTime = [[NSDate date] timeIntervalSince1970] + idExpire; -} - -- (void)verifyTokensRefreshedWithMethod:(SEL)sel { - GIDAuthentication *auth = [self observedAuth]; - XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - // We know the method doesn't return anything, so there is no risk of leaking. - [auth performSelector:sel withObject:^(GIDAuthentication *authentication, NSError *error) { -#pragma clang diagnostic pop - [expectation fulfill]; - [self assertNewTokensInAuth:authentication]; - XCTAssertNil(error); - }]; - _tokenFetchHandler([self tokenResponseWithNewTokens], nil); - [self waitForExpectationsWithTimeout:1 handler:nil]; - [self assertNewTokensInAuth:auth]; -} - -- (void)verifyTokensNotRefreshedWithMethod:(SEL)sel { - GIDAuthentication *auth = [self observedAuth]; - XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - // We know the method doesn't return anything, so there is no risk of leaking. - [auth performSelector:sel withObject:^(GIDAuthentication *authentication, NSError *error) { -#pragma clang diagnostic pop - [expectation fulfill]; - [self assertOldTokensInAuth:authentication]; - XCTAssertNil(error); - }]; - XCTAssertNil(_tokenFetchHandler); - [self waitForExpectationsWithTimeout:1 handler:nil]; - [self assertOldTokensInAuth:auth]; -} - -@end +//// 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 +// +//#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDAuthentication.h" +//#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h" +// +//#import "GoogleSignIn/Sources/GIDAuthentication_Private.h" +//#import "GoogleSignIn/Sources/GIDEMMErrorHandler.h" +//#import "GoogleSignIn/Sources/GIDMDMPasscodeState.h" +//#import "GoogleSignIn/Sources/GIDSignInPreferences.h" +//#import "GoogleSignIn/Tests/Unit/OIDAuthState+Testing.h" +//#import "GoogleSignIn/Tests/Unit/OIDTokenRequest+Testing.h" +//#import "GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h" +// +//#ifdef SWIFT_PACKAGE +//@import AppAuth; +//@import GoogleUtilities_MethodSwizzler; +//@import GoogleUtilities_SwizzlerTestHelpers; +//@import GTMAppAuth; +//@import GTMSessionFetcherCore; +//@import OCMock; +//#else +//#import +//#import +//#import +//#import +//#import +//#import +//#import +//#import +//#import +//#import +//#import +//#import +//#import +//#import +//#endif +// +//static NSString *const kClientID = @"87654321.googleusercontent.com"; +//static NSString *const kNewAccessToken = @"new_access_token"; +//static NSString *const kUserEmail = @"foo@gmail.com"; +//static NSTimeInterval const kExpireTime = 442886117; +//static NSTimeInterval const kNewExpireTime = 442886123; +//static NSTimeInterval const kNewExpireTime2 = 442886124; +// +//static NSTimeInterval const kTimeAccuracy = 10; +// +//// The system name in old iOS versions. +//static NSString *const kOldIOSName = @"iPhone OS"; +// +//// The system name in new iOS versions. +//static NSString *const kNewIOSName = @"iOS"; +// +//// List of observed properties of the class being tested. +//static NSString *const kObservedProperties[] = { +// @"accessToken", +// @"accessTokenExpirationDate", +// @"idToken", +// @"idTokenExpirationDate" +//}; +//static const NSUInteger kNumberOfObservedProperties = +// sizeof(kObservedProperties) / sizeof(*kObservedProperties); +// +//// Bit position for notification change type bitmask flags. +//// Must match the list of observed properties above. +//typedef NS_ENUM(NSUInteger, ChangeType) { +// kChangeTypeAccessTokenPrior, +// kChangeTypeAccessToken, +// kChangeTypeAccessTokenExpirationDatePrior, +// kChangeTypeAccessTokenExpirationDate, +// kChangeTypeIDTokenPrior, +// kChangeTypeIDToken, +// kChangeTypeIDTokenExpirationDatePrior, +// kChangeTypeIDTokenExpirationDate, +// kChangeTypeEnd // not a real change type but an end mark for calculating |kChangeAll| +//}; +// +//static const NSUInteger kChangeNone = 0u; +//static const NSUInteger kChangeAll = (1u << kChangeTypeEnd) - 1u; +// +//#if __has_feature(c_static_assert) || __has_extension(c_static_assert) +//_Static_assert(kChangeTypeEnd == (sizeof(kObservedProperties) / sizeof(*kObservedProperties)) * 2, +// "List of observed properties must match list of change notification enums"); +//#endif +// +//@interface GIDAuthenticationTest : XCTestCase +//@end +// +//@implementation GIDAuthenticationTest { +// // Whether the auth object has ID token or not. +// BOOL _hasIDToken; +// +// // Fake data used to generate the expiration date of the access token. +// NSTimeInterval _accessTokenExpireTime; +// +// // Fake data used to generate the expiration date of the ID token. +// NSTimeInterval _idTokenExpireTime; +// +// // Fake data used to generate the additional token request parameters. +// NSDictionary *_additionalTokenRequestParameters; +// +// // The saved token fetch handler. +// OIDTokenCallback _tokenFetchHandler; +// +// // The saved token request. +// OIDTokenRequest *_tokenRequest; +// +// // All GIDAuthentication objects that are observed. +// NSMutableArray *_observedAuths; +// +// // Bitmask flags for observed changes, as specified in |ChangeType|. +// NSUInteger _changesObserved; +// +// // The fake system name used for testing. +// NSString *_fakeSystemName; +//} +// +//- (void)setUp { +// _hasIDToken = YES; +// _accessTokenExpireTime = kAccessTokenExpiresIn; +// _idTokenExpireTime = kExpireTime; +// _additionalTokenRequestParameters = nil; +// _tokenFetchHandler = nil; +// _tokenRequest = nil; +// [GULSwizzler swizzleClass:[OIDAuthorizationService class] +// selector:@selector(performTokenRequest:originalAuthorizationResponse:callback:) +// isClassSelector:YES +// withBlock:^(id sender, +// OIDTokenRequest *request, +// OIDAuthorizationResponse *authorizationResponse, +// OIDTokenCallback callback) { +// XCTAssertNotNil(authorizationResponse.request.clientID); +// XCTAssertNotNil(authorizationResponse.request.configuration.tokenEndpoint); +// XCTAssertNil(self->_tokenFetchHandler); // only one on-going fetch allowed +// self->_tokenFetchHandler = [callback copy]; +// self->_tokenRequest = [request copy]; +// return nil; +// }]; +// _observedAuths = [[NSMutableArray alloc] init]; +// _changesObserved = 0; +// _fakeSystemName = kNewIOSName; +//#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST +// [GULSwizzler swizzleClass:[UIDevice class] +// selector:@selector(systemName) +// isClassSelector:NO +// withBlock:^(id sender) { return self->_fakeSystemName; }]; +//#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST +//} +// +//- (void)tearDown { +// [GULSwizzler unswizzleClass:[OIDAuthorizationService class] +// selector:@selector(performTokenRequest:originalAuthorizationResponse:callback:) +// isClassSelector:YES]; +//#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST +// [GULSwizzler unswizzleClass:[UIDevice class] +// selector:@selector(systemName) +// isClassSelector:NO]; +//#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST +// for (GIDAuthentication *auth in _observedAuths) { +// for (unsigned int i = 0; i < kNumberOfObservedProperties; ++i) { +// [auth removeObserver:self forKeyPath:kObservedProperties[i]]; +// } +// } +// _observedAuths = nil; +//} +// +//#pragma mark - Tests +// +//- (void)testInitWithAuthState { +// OIDAuthState *authState = [OIDAuthState testInstance]; +// GIDAuthentication *auth = [[GIDAuthentication alloc] initWithAuthState:authState]; +// +// XCTAssertEqualObjects(auth.clientID, authState.lastAuthorizationResponse.request.clientID); +// XCTAssertEqualObjects(auth.accessToken, authState.lastTokenResponse.accessToken); +// XCTAssertEqualObjects(auth.accessTokenExpirationDate, +// authState.lastTokenResponse.accessTokenExpirationDate); +// XCTAssertEqualObjects(auth.refreshToken, authState.refreshToken); +// XCTAssertEqualObjects(auth.idToken, authState.lastTokenResponse.idToken); +// OIDIDToken *idToken = [[OIDIDToken alloc] +// initWithIDTokenString:authState.lastTokenResponse.idToken]; +// XCTAssertEqualObjects(auth.idTokenExpirationDate, [idToken expiresAt]); +//} +// +//- (void)testInitWithAuthStateNoIDToken { +// OIDAuthState *authState = [OIDAuthState testInstanceWithIDToken:nil]; +// GIDAuthentication *auth = [[GIDAuthentication alloc] initWithAuthState:authState]; +// +// XCTAssertEqualObjects(auth.clientID, authState.lastAuthorizationResponse.request.clientID); +// XCTAssertEqualObjects(auth.accessToken, authState.lastTokenResponse.accessToken); +// XCTAssertEqualObjects(auth.accessTokenExpirationDate, +// authState.lastTokenResponse.accessTokenExpirationDate); +// XCTAssertEqualObjects(auth.refreshToken, authState.refreshToken); +// XCTAssertNil(auth.idToken); +// XCTAssertNil(auth.idTokenExpirationDate); +//} +// +//- (void)testAuthState { +// OIDAuthState *authState = [OIDAuthState testInstance]; +// GIDAuthentication *auth = [[GIDAuthentication alloc] initWithAuthState:authState]; +// OIDAuthState *authStateReturned = auth.authState; +// +// XCTAssertEqual(authState, authStateReturned); +//} +// +//- (void)testCoding { +// if (@available(iOS 11, macOS 10.13, *)) { +// GIDAuthentication *auth = [self auth]; +// NSData *data = [NSKeyedArchiver archivedDataWithRootObject:auth requiringSecureCoding:YES error:nil]; +// GIDAuthentication *newAuth = [NSKeyedUnarchiver unarchivedObjectOfClass:[GIDAuthentication class] +// fromData:data +// error:nil]; +// XCTAssertEqualObjects(auth, newAuth); +// XCTAssertTrue([GIDAuthentication supportsSecureCoding]); +// } else { +// XCTSkip(@"Required API is not available for this test."); +// } +//} +// +//#if TARGET_OS_IOS || TARGET_OS_MACCATALYST +//// Deprecated in iOS 13 and macOS 10.14 +//- (void)testLegacyCoding { +// GIDAuthentication *auth = [self auth]; +// NSData *data = [NSKeyedArchiver archivedDataWithRootObject:auth]; +// GIDAuthentication *newAuth = [NSKeyedUnarchiver unarchiveObjectWithData:data]; +// XCTAssertEqualObjects(auth, newAuth); +// XCTAssertTrue([GIDAuthentication supportsSecureCoding]); +//} +//#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST +// +//- (void)testDoWithFreshTokensWithBothExpired { +// // Both tokens expired 10 seconds ago. +// [self setExpireTimeForAccessToken:-10 IDToken:-10]; +// [self verifyTokensRefreshedWithMethod:@selector(doWithFreshTokens:)]; +//} +// +//- (void)testDoWithFreshTokensWithAccessTokenExpired { +// // Access token expired 10 seconds ago while ID token to expire in 10 minutes. +// [self setExpireTimeForAccessToken:-10 IDToken:10 * 60]; +// [self verifyTokensRefreshedWithMethod:@selector(doWithFreshTokens:)]; +//} +// +//- (void)testDoWithFreshTokensWithIDTokenToExpire { +// // Access token to expire in 10 minutes while ID token to expire in 10 seconds. +// [self setExpireTimeForAccessToken:10 * 60 IDToken:10]; +// [self verifyTokensRefreshedWithMethod:@selector(doWithFreshTokens:)]; +//} +// +//- (void)testDoWithFreshTokensWithBothFresh { +// // Both tokens to expire in 10 minutes. +// [self setExpireTimeForAccessToken:10 * 60 IDToken:10 * 60]; +// [self verifyTokensNotRefreshedWithMethod:@selector(doWithFreshTokens:)]; +//} +// +//- (void)testDoWithFreshTokensWithAccessTokenExpiredAndNoIDToken { +// _hasIDToken = NO; +// [self setExpireTimeForAccessToken:-10 IDToken:10 * 60]; // access token expired 10 seconds ago +// [self verifyTokensRefreshedWithMethod:@selector(doWithFreshTokens:)]; +//} +// +//- (void)testDoWithFreshTokensWithAccessTokenFreshAndNoIDToken { +// _hasIDToken = NO; +// [self setExpireTimeForAccessToken:10 * 60 IDToken:-10]; // access token to expire in 10 minutes +// [self verifyTokensNotRefreshedWithMethod:@selector(doWithFreshTokens:)]; +//} +// +//- (void)testDoWithFreshTokensError { +// [self setTokensExpireTime:-10]; // expired 10 seconds ago +// GIDAuthentication *auth = [self observedAuth]; +// XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"]; +// [auth doWithFreshTokens:^(GIDAuthentication *authentication, NSError *error) { +// [expectation fulfill]; +// XCTAssertNil(authentication); +// XCTAssertNotNil(error); +// }]; +// _tokenFetchHandler(nil, [self fakeError]); +// [self waitForExpectationsWithTimeout:1 handler:nil]; +// [self assertOldTokensInAuth:auth]; +//} +// +//- (void)testDoWithFreshTokensQueue { +// GIDAuthentication *auth = [self observedAuth]; +// XCTestExpectation *firstExpectation = +// [self expectationWithDescription:@"First callback is called"]; +// [auth doWithFreshTokens:^(GIDAuthentication *authentication, NSError *error) { +// [firstExpectation fulfill]; +// [self assertNewTokensInAuth:authentication]; +// XCTAssertNil(error); +// }]; +// XCTestExpectation *secondExpectation = +// [self expectationWithDescription:@"Second callback is called"]; +// [auth doWithFreshTokens:^(GIDAuthentication *authentication, NSError *error) { +// [secondExpectation fulfill]; +// [self assertNewTokensInAuth:authentication]; +// XCTAssertNil(error); +// }]; +// _tokenFetchHandler([self tokenResponseWithNewTokens], nil); +// [self waitForExpectationsWithTimeout:1 handler:nil]; +// [self assertNewTokensInAuth:auth]; +//} +// +//#pragma mark - EMM Support +// +//#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST +// +//- (void)testEMMSupport { +// _additionalTokenRequestParameters = @{ +// @"emm_support" : @"xyz", +// }; +// GIDAuthentication *auth = [self auth]; +// [auth doWithFreshTokens:^(GIDAuthentication * _Nonnull authentication, +// NSError * _Nullable error) {}]; +// _tokenFetchHandler([self tokenResponseWithNewTokens], nil); +// NSDictionary *expectedParameters = @{ +// @"emm_support" : @"xyz", +// @"device_os" : [NSString stringWithFormat:@"%@ %@", +// _fakeSystemName, [UIDevice currentDevice].systemVersion], +// kSDKVersionLoggingParameter : GIDVersion(), +// kEnvironmentLoggingParameter : GIDEnvironment(), +// }; +// XCTAssertEqualObjects(auth.authState.lastTokenResponse.request.additionalParameters, +// expectedParameters); +//} +// +//- (void)testSystemNameNormalization { +// _fakeSystemName = kOldIOSName; +// _additionalTokenRequestParameters = @{ +// @"emm_support" : @"xyz", +// }; +// GIDAuthentication *auth = [self auth]; +// [auth doWithFreshTokens:^(GIDAuthentication * _Nonnull authentication, +// NSError * _Nullable error) {}]; +// _tokenFetchHandler([self tokenResponseWithNewTokens], nil); +// NSDictionary *expectedParameters = @{ +// @"emm_support" : @"xyz", +// @"device_os" : [NSString stringWithFormat:@"%@ %@", +// kNewIOSName, [UIDevice currentDevice].systemVersion], +// kSDKVersionLoggingParameter : GIDVersion(), +// kEnvironmentLoggingParameter : GIDEnvironment(), +// }; +// XCTAssertEqualObjects(auth.authState.lastTokenResponse.request.additionalParameters, +// expectedParameters); +//} +// +//- (void)testEMMPasscodeInfo { +// _additionalTokenRequestParameters = @{ +// @"emm_support" : @"xyz", +// @"device_os" : @"old one", +// @"emm_passcode_info" : @"something", +// }; +// GIDAuthentication *auth = [self auth]; +// [auth doWithFreshTokens:^(GIDAuthentication * _Nonnull authentication, +// NSError * _Nullable error) {}]; +// _tokenFetchHandler([self tokenResponseWithNewTokens], nil); +// NSDictionary *expectedParameters = @{ +// @"emm_support" : @"xyz", +// @"device_os" : [NSString stringWithFormat:@"%@ %@", +// _fakeSystemName, [UIDevice currentDevice].systemVersion], +// @"emm_passcode_info" : [GIDMDMPasscodeState passcodeState].info, +// kSDKVersionLoggingParameter : GIDVersion(), +// kEnvironmentLoggingParameter : GIDEnvironment(), +// }; +// XCTAssertEqualObjects(auth.authState.lastTokenResponse.request.additionalParameters, +// expectedParameters); +//} +// +//- (void)testEMMError { +// // Set expectations. +// NSDictionary *errorJSON = @{ @"error" : @"EMM Specific Error" }; +// NSError *emmError = [NSError errorWithDomain:@"anydomain" +// code:12345 +// userInfo:@{ OIDOAuthErrorResponseErrorKey : errorJSON }]; +// id mockEMMErrorHandler = OCMStrictClassMock([GIDEMMErrorHandler class]); +// [[[mockEMMErrorHandler stub] andReturn:mockEMMErrorHandler] sharedInstance]; +// __block void (^completion)(void); +// [[[mockEMMErrorHandler expect] andReturnValue:@YES] +// handleErrorFromResponse:errorJSON completion:[OCMArg checkWithBlock:^(id arg) { +// completion = arg; +// return YES; +// }]]; +// +// // Start testing. +// _additionalTokenRequestParameters = @{ +// @"emm_support" : @"xyz", +// }; +// GIDAuthentication *auth = [self auth]; +// XCTestExpectation *notCalled = [self expectationWithDescription:@"Callback is not called"]; +// notCalled.inverted = YES; +// XCTestExpectation *called = [self expectationWithDescription:@"Callback is called"]; +// [auth doWithFreshTokens:^(GIDAuthentication *authentication, NSError *error) { +// [notCalled fulfill]; +// [called fulfill]; +// XCTAssertNil(authentication); +// XCTAssertEqualObjects(error.domain, kGIDSignInErrorDomain); +// XCTAssertEqual(error.code, kGIDSignInErrorCodeEMM); +// }]; +// _tokenFetchHandler(nil, emmError); +// +// // Verify and clean up. +// [mockEMMErrorHandler verify]; +// [mockEMMErrorHandler stopMocking]; +// [self waitForExpectations:@[ notCalled ] timeout:1]; +// completion(); +// [self waitForExpectations:@[ called ] timeout:1]; +// [self assertOldTokensInAuth:auth]; +//} +// +//- (void)testNonEMMError { +// // Set expectations. +// NSDictionary *errorJSON = @{ @"error" : @"Not EMM Specific Error" }; +// NSError *emmError = [NSError errorWithDomain:@"anydomain" +// code:12345 +// userInfo:@{ OIDOAuthErrorResponseErrorKey : errorJSON }]; +// id mockEMMErrorHandler = OCMStrictClassMock([GIDEMMErrorHandler class]); +// [[[mockEMMErrorHandler stub] andReturn:mockEMMErrorHandler] sharedInstance]; +// __block void (^completion)(void); +// [[[mockEMMErrorHandler expect] andReturnValue:@NO] +// handleErrorFromResponse:errorJSON completion:[OCMArg checkWithBlock:^(id arg) { +// completion = arg; +// return YES; +// }]]; +// +// // Start testing. +// _additionalTokenRequestParameters = @{ +// @"emm_support" : @"xyz", +// }; +// GIDAuthentication *auth = [self auth]; +// XCTestExpectation *notCalled = [self expectationWithDescription:@"Callback is not called"]; +// notCalled.inverted = YES; +// XCTestExpectation *called = [self expectationWithDescription:@"Callback is called"]; +// [auth doWithFreshTokens:^(GIDAuthentication *authentication, NSError *error) { +// [notCalled fulfill]; +// [called fulfill]; +// XCTAssertNil(authentication); +// XCTAssertEqualObjects(error.domain, @"anydomain"); +// XCTAssertEqual(error.code, 12345); +// }]; +// _tokenFetchHandler(nil, emmError); +// +// // Verify and clean up. +// [mockEMMErrorHandler verify]; +// [mockEMMErrorHandler stopMocking]; +// [self waitForExpectations:@[ notCalled ] timeout:1]; +// completion(); +// [self waitForExpectations:@[ called ] timeout:1]; +// [self assertOldTokensInAuth:auth]; +//} +// +//- (void)testCodingPreserveEMMParameters { +// _additionalTokenRequestParameters = @{ +// @"emm_support" : @"xyz", +// @"device_os" : @"old one", +// @"emm_passcode_info" : @"something", +// }; +// NSData *data = [NSKeyedArchiver archivedDataWithRootObject:[self auth]]; +// GIDAuthentication *auth = [NSKeyedUnarchiver unarchiveObjectWithData:data]; +// [auth doWithFreshTokens:^(GIDAuthentication * _Nonnull authentication, +// NSError * _Nullable error) {}]; +// _tokenFetchHandler([self tokenResponseWithNewTokens], nil); +// NSDictionary *expectedParameters = @{ +// @"emm_support" : @"xyz", +// @"device_os" : [NSString stringWithFormat:@"%@ %@", +// [UIDevice currentDevice].systemName, [UIDevice currentDevice].systemVersion], +// @"emm_passcode_info" : [GIDMDMPasscodeState passcodeState].info, +// kSDKVersionLoggingParameter : GIDVersion(), +// kEnvironmentLoggingParameter : GIDEnvironment(), +// }; +// XCTAssertEqualObjects(auth.authState.lastTokenResponse.request.additionalParameters, +// expectedParameters); +//} +// +//#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST +// +//#pragma mark - NSKeyValueObserving +// +//- (void)observeValueForKeyPath:(NSString *)keyPath +// ofObject:(id)object +// change:(NSDictionary *)change +// context:(void *)context { +// GIDAuthentication *auth = (GIDAuthentication *)object; +// ChangeType changeType; +// if ([keyPath isEqualToString:@"accessToken"]) { +// if (change[NSKeyValueChangeNotificationIsPriorKey]) { +// XCTAssertEqualObjects(auth.accessToken, kAccessToken); +// changeType = kChangeTypeAccessTokenPrior; +// } else { +// XCTAssertEqualObjects(auth.accessToken, kNewAccessToken); +// changeType = kChangeTypeAccessToken; +// } +// } else if ([keyPath isEqualToString:@"accessTokenExpirationDate"]) { +// if (change[NSKeyValueChangeNotificationIsPriorKey]) { +// [self assertDate:auth.accessTokenExpirationDate equalTime:_accessTokenExpireTime]; +// changeType = kChangeTypeAccessTokenExpirationDatePrior; +// } else { +// [self assertDate:auth.accessTokenExpirationDate equalTime:kNewExpireTime]; +// changeType = kChangeTypeAccessTokenExpirationDate; +// } +// } else if ([keyPath isEqualToString:@"idToken"]) { +// if (change[NSKeyValueChangeNotificationIsPriorKey]) { +// XCTAssertEqualObjects(auth.idToken, [self idToken]); +// changeType = kChangeTypeIDTokenPrior; +// } else { +// XCTAssertEqualObjects(auth.idToken, [self idTokenNew]); +// changeType = kChangeTypeIDToken; +// } +// } else if ([keyPath isEqualToString:@"idTokenExpirationDate"]) { +// if (change[NSKeyValueChangeNotificationIsPriorKey]) { +// if (_hasIDToken) { +// [self assertDate:auth.idTokenExpirationDate equalTime:_idTokenExpireTime]; +// } +// changeType = kChangeTypeIDTokenExpirationDatePrior; +// } else { +// if (_hasIDToken) { +// [self assertDate:auth.idTokenExpirationDate equalTime:kNewExpireTime2]; +// } +// changeType = kChangeTypeIDTokenExpirationDate; +// } +// } else { +// XCTFail(@"unexpected keyPath"); +// return; // so compiler knows |changeType| is always assigned +// } +// NSUInteger changeMask = 1u << changeType; +// XCTAssertFalse(_changesObserved & changeMask); // each change type should only fire once +// _changesObserved |= changeMask; +//} +// +//#pragma mark - Helpers +// +//- (GIDAuthentication *)auth { +// NSString *idToken = [self idToken]; +// NSNumber *accessTokenExpiresIn = +// @(_accessTokenExpireTime - [[NSDate date] timeIntervalSince1970]); +// OIDTokenRequest *tokenRequest = +// [OIDTokenRequest testInstanceWithAdditionalParameters:_additionalTokenRequestParameters]; +// OIDTokenResponse *tokenResponse = +// [OIDTokenResponse testInstanceWithIDToken:idToken +// accessToken:kAccessToken +// expiresIn:accessTokenExpiresIn +// refreshToken:kRefreshToken +// tokenRequest:tokenRequest]; +// return [[GIDAuthentication alloc] +// initWithAuthState:[OIDAuthState testInstanceWithTokenResponse:tokenResponse]]; +//} +// +//- (NSString *)idTokenWithExpireTime:(NSTimeInterval)expireTime { +// if (!_hasIDToken) { +// return nil; +// } +// return [OIDTokenResponse idTokenWithSub:kUserID exp:@(expireTime)]; +//} +// +//- (NSString *)idToken { +// return [self idTokenWithExpireTime:_idTokenExpireTime]; +//} +// +//- (NSString *)idTokenNew { +// return [self idTokenWithExpireTime:kNewExpireTime2]; +//} +// +//// Return the auth object that has certain property changes observed. +//- (GIDAuthentication *)observedAuth { +// GIDAuthentication *auth = [self auth]; +// for (unsigned int i = 0; i < kNumberOfObservedProperties; ++i) { +// [auth addObserver:self +// forKeyPath:kObservedProperties[i] +// options:NSKeyValueObservingOptionPrior +// context:NULL]; +// } +// [_observedAuths addObject:auth]; +// return auth; +//} +// +//- (OIDTokenResponse *)tokenResponseWithNewTokens { +// NSNumber *expiresIn = @(kNewExpireTime - [[NSDate date] timeIntervalSince1970]); +// return [OIDTokenResponse testInstanceWithIDToken:(_hasIDToken ? [self idTokenNew] : nil) +// accessToken:kNewAccessToken +// expiresIn:expiresIn +// refreshToken:kRefreshToken +// tokenRequest:_tokenRequest ?: nil]; +//} +// +//- (NSError *)fakeError { +// return [NSError errorWithDomain:@"fake.domain" code:-1 userInfo:nil]; +//} +// +//- (void)assertDate:(NSDate *)date equalTime:(NSTimeInterval)time { +// XCTAssertEqualWithAccuracy([date timeIntervalSince1970], time, kTimeAccuracy); +//} +// +//- (void)assertOldAccessTokenInAuth:(GIDAuthentication *)auth { +// XCTAssertEqualObjects(auth.accessToken, kAccessToken); +// [self assertDate:auth.accessTokenExpirationDate equalTime:_accessTokenExpireTime]; +// XCTAssertEqual(_changesObserved, kChangeNone); +//} +// +//- (void)assertNewAccessTokenInAuth:(GIDAuthentication *)auth { +// XCTAssertEqualObjects(auth.accessToken, kNewAccessToken); +// [self assertDate:auth.accessTokenExpirationDate equalTime:kNewExpireTime]; +// XCTAssertEqual(_changesObserved, kChangeAll); +//} +// +//- (void)assertOldTokensInAuth:(GIDAuthentication *)auth { +// [self assertOldAccessTokenInAuth:auth]; +// XCTAssertEqualObjects(auth.idToken, [self idToken]); +// if (_hasIDToken) { +// [self assertDate:auth.idTokenExpirationDate equalTime:_idTokenExpireTime]; +// } +//} +// +//- (void)assertNewTokensInAuth:(GIDAuthentication *)auth { +// [self assertNewAccessTokenInAuth:auth]; +// XCTAssertEqualObjects(auth.idToken, [self idTokenNew]); +// if (_hasIDToken) { +// [self assertDate:auth.idTokenExpirationDate equalTime:kNewExpireTime2]; +// } +//} +// +//- (void)setTokensExpireTime:(NSTimeInterval)fromNow { +// [self setExpireTimeForAccessToken:fromNow IDToken:fromNow]; +//} +// +//- (void)setExpireTimeForAccessToken:(NSTimeInterval)accessExpire IDToken:(NSTimeInterval)idExpire { +// _accessTokenExpireTime = [[NSDate date] timeIntervalSince1970] + accessExpire; +// _idTokenExpireTime = [[NSDate date] timeIntervalSince1970] + idExpire; +//} +// +//- (void)verifyTokensRefreshedWithMethod:(SEL)sel { +// GIDAuthentication *auth = [self observedAuth]; +// XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"]; +//#pragma clang diagnostic push +//#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +// // We know the method doesn't return anything, so there is no risk of leaking. +// [auth performSelector:sel withObject:^(GIDAuthentication *authentication, NSError *error) { +//#pragma clang diagnostic pop +// [expectation fulfill]; +// [self assertNewTokensInAuth:authentication]; +// XCTAssertNil(error); +// }]; +// _tokenFetchHandler([self tokenResponseWithNewTokens], nil); +// [self waitForExpectationsWithTimeout:1 handler:nil]; +// [self assertNewTokensInAuth:auth]; +//} +// +//- (void)verifyTokensNotRefreshedWithMethod:(SEL)sel { +// GIDAuthentication *auth = [self observedAuth]; +// XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"]; +//#pragma clang diagnostic push +//#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +// // We know the method doesn't return anything, so there is no risk of leaking. +// [auth performSelector:sel withObject:^(GIDAuthentication *authentication, NSError *error) { +//#pragma clang diagnostic pop +// [expectation fulfill]; +// [self assertOldTokensInAuth:authentication]; +// XCTAssertNil(error); +// }]; +// XCTAssertNil(_tokenFetchHandler); +// [self waitForExpectationsWithTimeout:1 handler:nil]; +// [self assertOldTokensInAuth:auth]; +//} +// +//@end diff --git a/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.m b/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.m index f6db0e85..f45969de 100644 --- a/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.m +++ b/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.m @@ -17,7 +17,6 @@ #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDConfiguration.h" #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDToken.h" -#import "GoogleSignIn/Tests/Unit/GIDAuthentication+Testing.h" #import "GoogleSignIn/Tests/Unit/GIDConfiguration+Testing.h" #import "GoogleSignIn/Tests/Unit/GIDProfileData+Testing.h" @@ -34,8 +33,7 @@ - (BOOL)isEqual:(id)object { } - (BOOL)isEqualToGoogleUser:(GIDGoogleUser *)other { - return [self.authentication isEqual:other.authentication] && - [self.userID isEqual:other.userID] && + return [self.userID isEqual:other.userID] && [self.profile isEqual:other.profile] && [self.configuration isEqual:other.configuration] && [self.idToken isEqual:other.idToken] && @@ -45,9 +43,8 @@ - (BOOL)isEqualToGoogleUser:(GIDGoogleUser *)other { // Not the hash implemention you want to use on prod, but just to match |isEqual:| here. - (NSUInteger)hash { - return [self.authentication hash] ^ [self.userID hash] ^ [self.configuration hash] ^ - [self.profile hash] ^ [self.idToken hash] ^ [self.refreshToken hash] ^ - [self.accessToken hash]; + return [self.userID hash] ^ [self.configuration hash] ^ [self.profile hash] ^ + [self.idToken hash] ^ [self.refreshToken hash] ^ [self.accessToken hash]; } @end diff --git a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m index ae2e561d..99c1c381 100644 --- a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m +++ b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m @@ -16,12 +16,10 @@ #import -#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDAuthentication.h" #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDConfiguration.h" #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDProfileData.h" #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDToken.h" -#import "GoogleSignIn/Sources/GIDAuthentication_Private.h" #import "GoogleSignIn/Sources/GIDGoogleUser_Private.h" #import "GoogleSignIn/Tests/Unit/GIDProfileData+Testing.h" #import "GoogleSignIn/Tests/Unit/OIDAuthState+Testing.h" @@ -53,10 +51,7 @@ - (void)testInitWithAuthState { OIDAuthState *authState = [OIDAuthState testInstance]; GIDGoogleUser *user = [[GIDGoogleUser alloc] initWithAuthState:authState profileData:[GIDProfileData testInstance]]; - GIDAuthentication *authentication = - [[GIDAuthentication alloc] initWithAuthState:authState]; - - XCTAssertEqualObjects(user.authentication, authentication); + XCTAssertEqualObjects(user.grantedScopes, @[ OIDAuthorizationRequestTestingScope2 ]); XCTAssertEqualObjects(user.userID, kUserID); XCTAssertEqualObjects(user.configuration.hostedDomain, kHostedDomain); @@ -110,7 +105,8 @@ - (void)testUpdateAuthState { refreshToken:kNewRefreshToken]; GIDProfileData *updatedProfileData = [GIDProfileData testInstance]; - [user updateAuthState:updatedAuthState profileData:updatedProfileData]; + [user updateWithTokenResponse:updatedAuthState.lastTokenResponse + profileData:updatedProfileData]; XCTAssertEqualObjects(user.accessToken.tokenString, kNewAccessToken); NSDate *expectedAccessTokenExpirationDate = [[NSDate date] dateByAddingTimeInterval:kAccessTokenExpiresIn]; @@ -142,7 +138,8 @@ - (void)testUpdateAuthState_tokensAreNotChanged { GIDToken *refreshTokenBeforeUpdate = user.refreshToken; GIDToken *idTokenBeforeUpdate = user.idToken; - [user updateAuthState:authState profileData:nil]; + [user updateWithTokenResponse:authState.lastTokenResponse + profileData:nil]; XCTAssertIdentical(user.accessToken, accessTokenBeforeUpdate); XCTAssertIdentical(user.idToken, idTokenBeforeUpdate); diff --git a/GoogleSignIn/Tests/Unit/GIDSignInTest.m b/GoogleSignIn/Tests/Unit/GIDSignInTest.m index a5f0c21f..3d95c5c0 100644 --- a/GoogleSignIn/Tests/Unit/GIDSignInTest.m +++ b/GoogleSignIn/Tests/Unit/GIDSignInTest.m @@ -30,7 +30,6 @@ #import "GoogleSignIn/Sources/GIDGoogleUser_Private.h" #import "GoogleSignIn/Sources/GIDSignIn_Private.h" #import "GoogleSignIn/Sources/GIDSignInPreferences.h" -#import "GoogleSignIn/Sources/GIDAuthentication_Private.h" #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST #import "GoogleSignIn/Sources/GIDEMMErrorHandler.h" @@ -318,7 +317,6 @@ - (void)setUp { self->_keychainRemoved = YES; }); _user = OCMStrictClassMock([GIDGoogleUser class]); - _authentication = OCMStrictClassMock([GIDAuthentication class]); _oidAuthorizationService = OCMStrictClassMock([OIDAuthorizationService class]); OCMStub([_oidAuthorizationService presentAuthorizationRequest:SAVE_TO_ARG_BLOCK(self->_savedAuthorizationRequest) @@ -400,6 +398,7 @@ - (void)testRestorePreviousSignInNoRefresh_hasPreviousUser { [[_authorization expect] setTokenRefreshDelegate:OCMOCK_ANY]; OCMStub([_authState lastTokenResponse]).andReturn(_tokenResponse); OCMStub([_authState refreshToken]).andReturn(kRefreshToken); + [[_authState expect] setStateChangeDelegate:OCMOCK_ANY]; id idTokenDecoded = OCMClassMock([OIDIDToken class]); OCMStub([idTokenDecoded alloc]).andReturn(idTokenDecoded); @@ -418,6 +417,7 @@ - (void)testRestorePreviousSignInNoRefresh_hasPreviousUser { OCMStub([_tokenResponse accessToken]).andReturn(kAccessToken); OCMStub([_tokenResponse accessTokenExpirationDate]).andReturn(nil); + [_signIn restorePreviousSignInNoRefresh]; @@ -580,8 +580,6 @@ - (void)testAddScopes { id profile = OCMStrictClassMock([GIDProfileData class]); OCMStub([profile email]).andReturn(kUserEmail); - - OCMStub([_user authentication]).andReturn(_authentication); // Mock for the method `addScopes`. OCMStub([_user configuration]).andReturn(_configuration); @@ -1353,14 +1351,16 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow // SaveAuthCallback __block OIDAuthState *authState; + __block OIDTokenResponse *updatedTokenResponse; __block GIDProfileData *profileData; if (keychainError) { _saveAuthorizationReturnValue = NO; } else { if (addScopesFlow) { - [[_user expect] updateAuthState:SAVE_TO_ARG_BLOCK(authState) - profileData:SAVE_TO_ARG_BLOCK(profileData)]; + [[[_authState expect] andReturn:tokenResponse] lastTokenResponse]; + [[_user expect] updateWithTokenResponse:SAVE_TO_ARG_BLOCK(updatedTokenResponse) + profileData:SAVE_TO_ARG_BLOCK(profileData)]; } else { [[[_user stub] andReturn:_user] alloc]; (void)[[[_user expect] andReturn:_user] initWithAuthState:SAVE_TO_ARG_BLOCK(authState) @@ -1393,7 +1393,11 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow [_authState verify]; XCTAssertTrue(_keychainSaved, @"should save to keychain"); - XCTAssertNotNil(authState); + if (addScopesFlow) { + XCTAssertNotNil(updatedTokenResponse); + } else { + XCTAssertNotNil(authState); + } // Check fat ID token decoding XCTAssertEqualObjects(profileData.name, kFatName); XCTAssertEqualObjects(profileData.givenName, kFatGivenName); @@ -1406,13 +1410,8 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow _keychainSaved = NO; _authError = nil; - if (!addScopesFlow) { - [[[_user expect] andReturn:_authentication] authentication]; - [[[_user expect] andReturn:_authentication] authentication]; - } - - __block GIDAuthenticationCompletion completion; - [[_authentication expect] doWithFreshTokens:SAVE_TO_ARG_BLOCK(completion)]; + __block GIDGoogleUserCompletion completion; + [[_user expect] doWithFreshTokens:SAVE_TO_ARG_BLOCK(completion)]; XCTestExpectation *expectation = [self expectationWithDescription:@"Callback should be called"]; @@ -1422,7 +1421,7 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow XCTAssertNil(error, @"should have no error"); }]; - completion(_authentication, nil); + completion(_user, nil); [self waitForExpectationsWithTimeout:1 handler:nil]; XCTAssertFalse(_keychainRemoved, @"should not remove keychain"); diff --git a/Samples/Swift/DaysUntilBirthday/Shared/Services/BirthdayLoader.swift b/Samples/Swift/DaysUntilBirthday/Shared/Services/BirthdayLoader.swift index 1dc4dea4..1de3b3de 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/Services/BirthdayLoader.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/Services/BirthdayLoader.swift @@ -42,8 +42,8 @@ final class BirthdayLoader: ObservableObject { guard let accessToken = GIDSignIn .sharedInstance .currentUser? - .authentication - .accessToken else { return nil } + .accessToken + .tokenString else { return nil } let configuration = URLSessionConfiguration.default configuration.httpAdditionalHeaders = [ "Authorization": "Bearer \(accessToken)" @@ -52,9 +52,8 @@ final class BirthdayLoader: ObservableObject { }() private func sessionWithFreshToken(completion: @escaping (Result) -> Void) { - let authentication = GIDSignIn.sharedInstance.currentUser?.authentication - authentication?.do { auth, error in - guard let token = auth?.accessToken else { + GIDSignIn.sharedInstance.currentUser?.do { user, error in + guard let token = user?.accessToken.tokenString else { completion(.failure(.couldNotCreateURLSession(error))) return } From 846f4ce6553fa500bac2dd3432e66e9e1699778b Mon Sep 17 00:00:00 2001 From: pinlu Date: Tue, 4 Oct 2022 11:33:22 -0700 Subject: [PATCH 18/26] Minor improvement --- GoogleSignIn/Sources/GIDGoogleUser.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GoogleSignIn/Sources/GIDGoogleUser.m b/GoogleSignIn/Sources/GIDGoogleUser.m index ee3823e0..1e97fda0 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser.m +++ b/GoogleSignIn/Sources/GIDGoogleUser.m @@ -170,7 +170,7 @@ - (void)doWithFreshTokens:(GIDGoogleUserCompletion)completion { refreshTokensHandlerQueue = [self->_refreshTokensHandlerQueue copy]; [self->_refreshTokensHandlerQueue removeAllObjects]; } - for (GIDAuthenticationCompletion completion in refreshTokensHandlerQueue) { + for (GIDGoogleUserCompletion completion in refreshTokensHandlerQueue) { dispatch_async(dispatch_get_main_queue(), ^{ completion(error ? nil : self, error); }); From 70bc182ecd0d1e40adec80e03ff8f3f7466a08fb Mon Sep 17 00:00:00 2001 From: pinlu Date: Wed, 5 Oct 2022 20:56:26 -0700 Subject: [PATCH 19/26] Add unit tests - Add GIDEMMSupportTest - Add test cases for `doWithFreshTokens:`in GIDGoogleUserTest - Delete GIDAuthenticationTest --- .../Tests/Unit/GIDAuthenticationTest.m | 673 ------------------ .../Tests/Unit/GIDEMMErrorHandlerTest.m | 1 + GoogleSignIn/Tests/Unit/GIDEMMSupportTest.m | 210 ++++++ GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m | 281 +++++++- 4 files changed, 485 insertions(+), 680 deletions(-) delete mode 100644 GoogleSignIn/Tests/Unit/GIDAuthenticationTest.m create mode 100644 GoogleSignIn/Tests/Unit/GIDEMMSupportTest.m diff --git a/GoogleSignIn/Tests/Unit/GIDAuthenticationTest.m b/GoogleSignIn/Tests/Unit/GIDAuthenticationTest.m deleted file mode 100644 index 9a898bac..00000000 --- a/GoogleSignIn/Tests/Unit/GIDAuthenticationTest.m +++ /dev/null @@ -1,673 +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 -// -//#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDAuthentication.h" -//#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h" -// -//#import "GoogleSignIn/Sources/GIDAuthentication_Private.h" -//#import "GoogleSignIn/Sources/GIDEMMErrorHandler.h" -//#import "GoogleSignIn/Sources/GIDMDMPasscodeState.h" -//#import "GoogleSignIn/Sources/GIDSignInPreferences.h" -//#import "GoogleSignIn/Tests/Unit/OIDAuthState+Testing.h" -//#import "GoogleSignIn/Tests/Unit/OIDTokenRequest+Testing.h" -//#import "GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h" -// -//#ifdef SWIFT_PACKAGE -//@import AppAuth; -//@import GoogleUtilities_MethodSwizzler; -//@import GoogleUtilities_SwizzlerTestHelpers; -//@import GTMAppAuth; -//@import GTMSessionFetcherCore; -//@import OCMock; -//#else -//#import -//#import -//#import -//#import -//#import -//#import -//#import -//#import -//#import -//#import -//#import -//#import -//#import -//#import -//#endif -// -//static NSString *const kClientID = @"87654321.googleusercontent.com"; -//static NSString *const kNewAccessToken = @"new_access_token"; -//static NSString *const kUserEmail = @"foo@gmail.com"; -//static NSTimeInterval const kExpireTime = 442886117; -//static NSTimeInterval const kNewExpireTime = 442886123; -//static NSTimeInterval const kNewExpireTime2 = 442886124; -// -//static NSTimeInterval const kTimeAccuracy = 10; -// -//// The system name in old iOS versions. -//static NSString *const kOldIOSName = @"iPhone OS"; -// -//// The system name in new iOS versions. -//static NSString *const kNewIOSName = @"iOS"; -// -//// List of observed properties of the class being tested. -//static NSString *const kObservedProperties[] = { -// @"accessToken", -// @"accessTokenExpirationDate", -// @"idToken", -// @"idTokenExpirationDate" -//}; -//static const NSUInteger kNumberOfObservedProperties = -// sizeof(kObservedProperties) / sizeof(*kObservedProperties); -// -//// Bit position for notification change type bitmask flags. -//// Must match the list of observed properties above. -//typedef NS_ENUM(NSUInteger, ChangeType) { -// kChangeTypeAccessTokenPrior, -// kChangeTypeAccessToken, -// kChangeTypeAccessTokenExpirationDatePrior, -// kChangeTypeAccessTokenExpirationDate, -// kChangeTypeIDTokenPrior, -// kChangeTypeIDToken, -// kChangeTypeIDTokenExpirationDatePrior, -// kChangeTypeIDTokenExpirationDate, -// kChangeTypeEnd // not a real change type but an end mark for calculating |kChangeAll| -//}; -// -//static const NSUInteger kChangeNone = 0u; -//static const NSUInteger kChangeAll = (1u << kChangeTypeEnd) - 1u; -// -//#if __has_feature(c_static_assert) || __has_extension(c_static_assert) -//_Static_assert(kChangeTypeEnd == (sizeof(kObservedProperties) / sizeof(*kObservedProperties)) * 2, -// "List of observed properties must match list of change notification enums"); -//#endif -// -//@interface GIDAuthenticationTest : XCTestCase -//@end -// -//@implementation GIDAuthenticationTest { -// // Whether the auth object has ID token or not. -// BOOL _hasIDToken; -// -// // Fake data used to generate the expiration date of the access token. -// NSTimeInterval _accessTokenExpireTime; -// -// // Fake data used to generate the expiration date of the ID token. -// NSTimeInterval _idTokenExpireTime; -// -// // Fake data used to generate the additional token request parameters. -// NSDictionary *_additionalTokenRequestParameters; -// -// // The saved token fetch handler. -// OIDTokenCallback _tokenFetchHandler; -// -// // The saved token request. -// OIDTokenRequest *_tokenRequest; -// -// // All GIDAuthentication objects that are observed. -// NSMutableArray *_observedAuths; -// -// // Bitmask flags for observed changes, as specified in |ChangeType|. -// NSUInteger _changesObserved; -// -// // The fake system name used for testing. -// NSString *_fakeSystemName; -//} -// -//- (void)setUp { -// _hasIDToken = YES; -// _accessTokenExpireTime = kAccessTokenExpiresIn; -// _idTokenExpireTime = kExpireTime; -// _additionalTokenRequestParameters = nil; -// _tokenFetchHandler = nil; -// _tokenRequest = nil; -// [GULSwizzler swizzleClass:[OIDAuthorizationService class] -// selector:@selector(performTokenRequest:originalAuthorizationResponse:callback:) -// isClassSelector:YES -// withBlock:^(id sender, -// OIDTokenRequest *request, -// OIDAuthorizationResponse *authorizationResponse, -// OIDTokenCallback callback) { -// XCTAssertNotNil(authorizationResponse.request.clientID); -// XCTAssertNotNil(authorizationResponse.request.configuration.tokenEndpoint); -// XCTAssertNil(self->_tokenFetchHandler); // only one on-going fetch allowed -// self->_tokenFetchHandler = [callback copy]; -// self->_tokenRequest = [request copy]; -// return nil; -// }]; -// _observedAuths = [[NSMutableArray alloc] init]; -// _changesObserved = 0; -// _fakeSystemName = kNewIOSName; -//#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST -// [GULSwizzler swizzleClass:[UIDevice class] -// selector:@selector(systemName) -// isClassSelector:NO -// withBlock:^(id sender) { return self->_fakeSystemName; }]; -//#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST -//} -// -//- (void)tearDown { -// [GULSwizzler unswizzleClass:[OIDAuthorizationService class] -// selector:@selector(performTokenRequest:originalAuthorizationResponse:callback:) -// isClassSelector:YES]; -//#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST -// [GULSwizzler unswizzleClass:[UIDevice class] -// selector:@selector(systemName) -// isClassSelector:NO]; -//#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST -// for (GIDAuthentication *auth in _observedAuths) { -// for (unsigned int i = 0; i < kNumberOfObservedProperties; ++i) { -// [auth removeObserver:self forKeyPath:kObservedProperties[i]]; -// } -// } -// _observedAuths = nil; -//} -// -//#pragma mark - Tests -// -//- (void)testInitWithAuthState { -// OIDAuthState *authState = [OIDAuthState testInstance]; -// GIDAuthentication *auth = [[GIDAuthentication alloc] initWithAuthState:authState]; -// -// XCTAssertEqualObjects(auth.clientID, authState.lastAuthorizationResponse.request.clientID); -// XCTAssertEqualObjects(auth.accessToken, authState.lastTokenResponse.accessToken); -// XCTAssertEqualObjects(auth.accessTokenExpirationDate, -// authState.lastTokenResponse.accessTokenExpirationDate); -// XCTAssertEqualObjects(auth.refreshToken, authState.refreshToken); -// XCTAssertEqualObjects(auth.idToken, authState.lastTokenResponse.idToken); -// OIDIDToken *idToken = [[OIDIDToken alloc] -// initWithIDTokenString:authState.lastTokenResponse.idToken]; -// XCTAssertEqualObjects(auth.idTokenExpirationDate, [idToken expiresAt]); -//} -// -//- (void)testInitWithAuthStateNoIDToken { -// OIDAuthState *authState = [OIDAuthState testInstanceWithIDToken:nil]; -// GIDAuthentication *auth = [[GIDAuthentication alloc] initWithAuthState:authState]; -// -// XCTAssertEqualObjects(auth.clientID, authState.lastAuthorizationResponse.request.clientID); -// XCTAssertEqualObjects(auth.accessToken, authState.lastTokenResponse.accessToken); -// XCTAssertEqualObjects(auth.accessTokenExpirationDate, -// authState.lastTokenResponse.accessTokenExpirationDate); -// XCTAssertEqualObjects(auth.refreshToken, authState.refreshToken); -// XCTAssertNil(auth.idToken); -// XCTAssertNil(auth.idTokenExpirationDate); -//} -// -//- (void)testAuthState { -// OIDAuthState *authState = [OIDAuthState testInstance]; -// GIDAuthentication *auth = [[GIDAuthentication alloc] initWithAuthState:authState]; -// OIDAuthState *authStateReturned = auth.authState; -// -// XCTAssertEqual(authState, authStateReturned); -//} -// -//- (void)testCoding { -// if (@available(iOS 11, macOS 10.13, *)) { -// GIDAuthentication *auth = [self auth]; -// NSData *data = [NSKeyedArchiver archivedDataWithRootObject:auth requiringSecureCoding:YES error:nil]; -// GIDAuthentication *newAuth = [NSKeyedUnarchiver unarchivedObjectOfClass:[GIDAuthentication class] -// fromData:data -// error:nil]; -// XCTAssertEqualObjects(auth, newAuth); -// XCTAssertTrue([GIDAuthentication supportsSecureCoding]); -// } else { -// XCTSkip(@"Required API is not available for this test."); -// } -//} -// -//#if TARGET_OS_IOS || TARGET_OS_MACCATALYST -//// Deprecated in iOS 13 and macOS 10.14 -//- (void)testLegacyCoding { -// GIDAuthentication *auth = [self auth]; -// NSData *data = [NSKeyedArchiver archivedDataWithRootObject:auth]; -// GIDAuthentication *newAuth = [NSKeyedUnarchiver unarchiveObjectWithData:data]; -// XCTAssertEqualObjects(auth, newAuth); -// XCTAssertTrue([GIDAuthentication supportsSecureCoding]); -//} -//#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST -// -//- (void)testDoWithFreshTokensWithBothExpired { -// // Both tokens expired 10 seconds ago. -// [self setExpireTimeForAccessToken:-10 IDToken:-10]; -// [self verifyTokensRefreshedWithMethod:@selector(doWithFreshTokens:)]; -//} -// -//- (void)testDoWithFreshTokensWithAccessTokenExpired { -// // Access token expired 10 seconds ago while ID token to expire in 10 minutes. -// [self setExpireTimeForAccessToken:-10 IDToken:10 * 60]; -// [self verifyTokensRefreshedWithMethod:@selector(doWithFreshTokens:)]; -//} -// -//- (void)testDoWithFreshTokensWithIDTokenToExpire { -// // Access token to expire in 10 minutes while ID token to expire in 10 seconds. -// [self setExpireTimeForAccessToken:10 * 60 IDToken:10]; -// [self verifyTokensRefreshedWithMethod:@selector(doWithFreshTokens:)]; -//} -// -//- (void)testDoWithFreshTokensWithBothFresh { -// // Both tokens to expire in 10 minutes. -// [self setExpireTimeForAccessToken:10 * 60 IDToken:10 * 60]; -// [self verifyTokensNotRefreshedWithMethod:@selector(doWithFreshTokens:)]; -//} -// -//- (void)testDoWithFreshTokensWithAccessTokenExpiredAndNoIDToken { -// _hasIDToken = NO; -// [self setExpireTimeForAccessToken:-10 IDToken:10 * 60]; // access token expired 10 seconds ago -// [self verifyTokensRefreshedWithMethod:@selector(doWithFreshTokens:)]; -//} -// -//- (void)testDoWithFreshTokensWithAccessTokenFreshAndNoIDToken { -// _hasIDToken = NO; -// [self setExpireTimeForAccessToken:10 * 60 IDToken:-10]; // access token to expire in 10 minutes -// [self verifyTokensNotRefreshedWithMethod:@selector(doWithFreshTokens:)]; -//} -// -//- (void)testDoWithFreshTokensError { -// [self setTokensExpireTime:-10]; // expired 10 seconds ago -// GIDAuthentication *auth = [self observedAuth]; -// XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"]; -// [auth doWithFreshTokens:^(GIDAuthentication *authentication, NSError *error) { -// [expectation fulfill]; -// XCTAssertNil(authentication); -// XCTAssertNotNil(error); -// }]; -// _tokenFetchHandler(nil, [self fakeError]); -// [self waitForExpectationsWithTimeout:1 handler:nil]; -// [self assertOldTokensInAuth:auth]; -//} -// -//- (void)testDoWithFreshTokensQueue { -// GIDAuthentication *auth = [self observedAuth]; -// XCTestExpectation *firstExpectation = -// [self expectationWithDescription:@"First callback is called"]; -// [auth doWithFreshTokens:^(GIDAuthentication *authentication, NSError *error) { -// [firstExpectation fulfill]; -// [self assertNewTokensInAuth:authentication]; -// XCTAssertNil(error); -// }]; -// XCTestExpectation *secondExpectation = -// [self expectationWithDescription:@"Second callback is called"]; -// [auth doWithFreshTokens:^(GIDAuthentication *authentication, NSError *error) { -// [secondExpectation fulfill]; -// [self assertNewTokensInAuth:authentication]; -// XCTAssertNil(error); -// }]; -// _tokenFetchHandler([self tokenResponseWithNewTokens], nil); -// [self waitForExpectationsWithTimeout:1 handler:nil]; -// [self assertNewTokensInAuth:auth]; -//} -// -//#pragma mark - EMM Support -// -//#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST -// -//- (void)testEMMSupport { -// _additionalTokenRequestParameters = @{ -// @"emm_support" : @"xyz", -// }; -// GIDAuthentication *auth = [self auth]; -// [auth doWithFreshTokens:^(GIDAuthentication * _Nonnull authentication, -// NSError * _Nullable error) {}]; -// _tokenFetchHandler([self tokenResponseWithNewTokens], nil); -// NSDictionary *expectedParameters = @{ -// @"emm_support" : @"xyz", -// @"device_os" : [NSString stringWithFormat:@"%@ %@", -// _fakeSystemName, [UIDevice currentDevice].systemVersion], -// kSDKVersionLoggingParameter : GIDVersion(), -// kEnvironmentLoggingParameter : GIDEnvironment(), -// }; -// XCTAssertEqualObjects(auth.authState.lastTokenResponse.request.additionalParameters, -// expectedParameters); -//} -// -//- (void)testSystemNameNormalization { -// _fakeSystemName = kOldIOSName; -// _additionalTokenRequestParameters = @{ -// @"emm_support" : @"xyz", -// }; -// GIDAuthentication *auth = [self auth]; -// [auth doWithFreshTokens:^(GIDAuthentication * _Nonnull authentication, -// NSError * _Nullable error) {}]; -// _tokenFetchHandler([self tokenResponseWithNewTokens], nil); -// NSDictionary *expectedParameters = @{ -// @"emm_support" : @"xyz", -// @"device_os" : [NSString stringWithFormat:@"%@ %@", -// kNewIOSName, [UIDevice currentDevice].systemVersion], -// kSDKVersionLoggingParameter : GIDVersion(), -// kEnvironmentLoggingParameter : GIDEnvironment(), -// }; -// XCTAssertEqualObjects(auth.authState.lastTokenResponse.request.additionalParameters, -// expectedParameters); -//} -// -//- (void)testEMMPasscodeInfo { -// _additionalTokenRequestParameters = @{ -// @"emm_support" : @"xyz", -// @"device_os" : @"old one", -// @"emm_passcode_info" : @"something", -// }; -// GIDAuthentication *auth = [self auth]; -// [auth doWithFreshTokens:^(GIDAuthentication * _Nonnull authentication, -// NSError * _Nullable error) {}]; -// _tokenFetchHandler([self tokenResponseWithNewTokens], nil); -// NSDictionary *expectedParameters = @{ -// @"emm_support" : @"xyz", -// @"device_os" : [NSString stringWithFormat:@"%@ %@", -// _fakeSystemName, [UIDevice currentDevice].systemVersion], -// @"emm_passcode_info" : [GIDMDMPasscodeState passcodeState].info, -// kSDKVersionLoggingParameter : GIDVersion(), -// kEnvironmentLoggingParameter : GIDEnvironment(), -// }; -// XCTAssertEqualObjects(auth.authState.lastTokenResponse.request.additionalParameters, -// expectedParameters); -//} -// -//- (void)testEMMError { -// // Set expectations. -// NSDictionary *errorJSON = @{ @"error" : @"EMM Specific Error" }; -// NSError *emmError = [NSError errorWithDomain:@"anydomain" -// code:12345 -// userInfo:@{ OIDOAuthErrorResponseErrorKey : errorJSON }]; -// id mockEMMErrorHandler = OCMStrictClassMock([GIDEMMErrorHandler class]); -// [[[mockEMMErrorHandler stub] andReturn:mockEMMErrorHandler] sharedInstance]; -// __block void (^completion)(void); -// [[[mockEMMErrorHandler expect] andReturnValue:@YES] -// handleErrorFromResponse:errorJSON completion:[OCMArg checkWithBlock:^(id arg) { -// completion = arg; -// return YES; -// }]]; -// -// // Start testing. -// _additionalTokenRequestParameters = @{ -// @"emm_support" : @"xyz", -// }; -// GIDAuthentication *auth = [self auth]; -// XCTestExpectation *notCalled = [self expectationWithDescription:@"Callback is not called"]; -// notCalled.inverted = YES; -// XCTestExpectation *called = [self expectationWithDescription:@"Callback is called"]; -// [auth doWithFreshTokens:^(GIDAuthentication *authentication, NSError *error) { -// [notCalled fulfill]; -// [called fulfill]; -// XCTAssertNil(authentication); -// XCTAssertEqualObjects(error.domain, kGIDSignInErrorDomain); -// XCTAssertEqual(error.code, kGIDSignInErrorCodeEMM); -// }]; -// _tokenFetchHandler(nil, emmError); -// -// // Verify and clean up. -// [mockEMMErrorHandler verify]; -// [mockEMMErrorHandler stopMocking]; -// [self waitForExpectations:@[ notCalled ] timeout:1]; -// completion(); -// [self waitForExpectations:@[ called ] timeout:1]; -// [self assertOldTokensInAuth:auth]; -//} -// -//- (void)testNonEMMError { -// // Set expectations. -// NSDictionary *errorJSON = @{ @"error" : @"Not EMM Specific Error" }; -// NSError *emmError = [NSError errorWithDomain:@"anydomain" -// code:12345 -// userInfo:@{ OIDOAuthErrorResponseErrorKey : errorJSON }]; -// id mockEMMErrorHandler = OCMStrictClassMock([GIDEMMErrorHandler class]); -// [[[mockEMMErrorHandler stub] andReturn:mockEMMErrorHandler] sharedInstance]; -// __block void (^completion)(void); -// [[[mockEMMErrorHandler expect] andReturnValue:@NO] -// handleErrorFromResponse:errorJSON completion:[OCMArg checkWithBlock:^(id arg) { -// completion = arg; -// return YES; -// }]]; -// -// // Start testing. -// _additionalTokenRequestParameters = @{ -// @"emm_support" : @"xyz", -// }; -// GIDAuthentication *auth = [self auth]; -// XCTestExpectation *notCalled = [self expectationWithDescription:@"Callback is not called"]; -// notCalled.inverted = YES; -// XCTestExpectation *called = [self expectationWithDescription:@"Callback is called"]; -// [auth doWithFreshTokens:^(GIDAuthentication *authentication, NSError *error) { -// [notCalled fulfill]; -// [called fulfill]; -// XCTAssertNil(authentication); -// XCTAssertEqualObjects(error.domain, @"anydomain"); -// XCTAssertEqual(error.code, 12345); -// }]; -// _tokenFetchHandler(nil, emmError); -// -// // Verify and clean up. -// [mockEMMErrorHandler verify]; -// [mockEMMErrorHandler stopMocking]; -// [self waitForExpectations:@[ notCalled ] timeout:1]; -// completion(); -// [self waitForExpectations:@[ called ] timeout:1]; -// [self assertOldTokensInAuth:auth]; -//} -// -//- (void)testCodingPreserveEMMParameters { -// _additionalTokenRequestParameters = @{ -// @"emm_support" : @"xyz", -// @"device_os" : @"old one", -// @"emm_passcode_info" : @"something", -// }; -// NSData *data = [NSKeyedArchiver archivedDataWithRootObject:[self auth]]; -// GIDAuthentication *auth = [NSKeyedUnarchiver unarchiveObjectWithData:data]; -// [auth doWithFreshTokens:^(GIDAuthentication * _Nonnull authentication, -// NSError * _Nullable error) {}]; -// _tokenFetchHandler([self tokenResponseWithNewTokens], nil); -// NSDictionary *expectedParameters = @{ -// @"emm_support" : @"xyz", -// @"device_os" : [NSString stringWithFormat:@"%@ %@", -// [UIDevice currentDevice].systemName, [UIDevice currentDevice].systemVersion], -// @"emm_passcode_info" : [GIDMDMPasscodeState passcodeState].info, -// kSDKVersionLoggingParameter : GIDVersion(), -// kEnvironmentLoggingParameter : GIDEnvironment(), -// }; -// XCTAssertEqualObjects(auth.authState.lastTokenResponse.request.additionalParameters, -// expectedParameters); -//} -// -//#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST -// -//#pragma mark - NSKeyValueObserving -// -//- (void)observeValueForKeyPath:(NSString *)keyPath -// ofObject:(id)object -// change:(NSDictionary *)change -// context:(void *)context { -// GIDAuthentication *auth = (GIDAuthentication *)object; -// ChangeType changeType; -// if ([keyPath isEqualToString:@"accessToken"]) { -// if (change[NSKeyValueChangeNotificationIsPriorKey]) { -// XCTAssertEqualObjects(auth.accessToken, kAccessToken); -// changeType = kChangeTypeAccessTokenPrior; -// } else { -// XCTAssertEqualObjects(auth.accessToken, kNewAccessToken); -// changeType = kChangeTypeAccessToken; -// } -// } else if ([keyPath isEqualToString:@"accessTokenExpirationDate"]) { -// if (change[NSKeyValueChangeNotificationIsPriorKey]) { -// [self assertDate:auth.accessTokenExpirationDate equalTime:_accessTokenExpireTime]; -// changeType = kChangeTypeAccessTokenExpirationDatePrior; -// } else { -// [self assertDate:auth.accessTokenExpirationDate equalTime:kNewExpireTime]; -// changeType = kChangeTypeAccessTokenExpirationDate; -// } -// } else if ([keyPath isEqualToString:@"idToken"]) { -// if (change[NSKeyValueChangeNotificationIsPriorKey]) { -// XCTAssertEqualObjects(auth.idToken, [self idToken]); -// changeType = kChangeTypeIDTokenPrior; -// } else { -// XCTAssertEqualObjects(auth.idToken, [self idTokenNew]); -// changeType = kChangeTypeIDToken; -// } -// } else if ([keyPath isEqualToString:@"idTokenExpirationDate"]) { -// if (change[NSKeyValueChangeNotificationIsPriorKey]) { -// if (_hasIDToken) { -// [self assertDate:auth.idTokenExpirationDate equalTime:_idTokenExpireTime]; -// } -// changeType = kChangeTypeIDTokenExpirationDatePrior; -// } else { -// if (_hasIDToken) { -// [self assertDate:auth.idTokenExpirationDate equalTime:kNewExpireTime2]; -// } -// changeType = kChangeTypeIDTokenExpirationDate; -// } -// } else { -// XCTFail(@"unexpected keyPath"); -// return; // so compiler knows |changeType| is always assigned -// } -// NSUInteger changeMask = 1u << changeType; -// XCTAssertFalse(_changesObserved & changeMask); // each change type should only fire once -// _changesObserved |= changeMask; -//} -// -//#pragma mark - Helpers -// -//- (GIDAuthentication *)auth { -// NSString *idToken = [self idToken]; -// NSNumber *accessTokenExpiresIn = -// @(_accessTokenExpireTime - [[NSDate date] timeIntervalSince1970]); -// OIDTokenRequest *tokenRequest = -// [OIDTokenRequest testInstanceWithAdditionalParameters:_additionalTokenRequestParameters]; -// OIDTokenResponse *tokenResponse = -// [OIDTokenResponse testInstanceWithIDToken:idToken -// accessToken:kAccessToken -// expiresIn:accessTokenExpiresIn -// refreshToken:kRefreshToken -// tokenRequest:tokenRequest]; -// return [[GIDAuthentication alloc] -// initWithAuthState:[OIDAuthState testInstanceWithTokenResponse:tokenResponse]]; -//} -// -//- (NSString *)idTokenWithExpireTime:(NSTimeInterval)expireTime { -// if (!_hasIDToken) { -// return nil; -// } -// return [OIDTokenResponse idTokenWithSub:kUserID exp:@(expireTime)]; -//} -// -//- (NSString *)idToken { -// return [self idTokenWithExpireTime:_idTokenExpireTime]; -//} -// -//- (NSString *)idTokenNew { -// return [self idTokenWithExpireTime:kNewExpireTime2]; -//} -// -//// Return the auth object that has certain property changes observed. -//- (GIDAuthentication *)observedAuth { -// GIDAuthentication *auth = [self auth]; -// for (unsigned int i = 0; i < kNumberOfObservedProperties; ++i) { -// [auth addObserver:self -// forKeyPath:kObservedProperties[i] -// options:NSKeyValueObservingOptionPrior -// context:NULL]; -// } -// [_observedAuths addObject:auth]; -// return auth; -//} -// -//- (OIDTokenResponse *)tokenResponseWithNewTokens { -// NSNumber *expiresIn = @(kNewExpireTime - [[NSDate date] timeIntervalSince1970]); -// return [OIDTokenResponse testInstanceWithIDToken:(_hasIDToken ? [self idTokenNew] : nil) -// accessToken:kNewAccessToken -// expiresIn:expiresIn -// refreshToken:kRefreshToken -// tokenRequest:_tokenRequest ?: nil]; -//} -// -//- (NSError *)fakeError { -// return [NSError errorWithDomain:@"fake.domain" code:-1 userInfo:nil]; -//} -// -//- (void)assertDate:(NSDate *)date equalTime:(NSTimeInterval)time { -// XCTAssertEqualWithAccuracy([date timeIntervalSince1970], time, kTimeAccuracy); -//} -// -//- (void)assertOldAccessTokenInAuth:(GIDAuthentication *)auth { -// XCTAssertEqualObjects(auth.accessToken, kAccessToken); -// [self assertDate:auth.accessTokenExpirationDate equalTime:_accessTokenExpireTime]; -// XCTAssertEqual(_changesObserved, kChangeNone); -//} -// -//- (void)assertNewAccessTokenInAuth:(GIDAuthentication *)auth { -// XCTAssertEqualObjects(auth.accessToken, kNewAccessToken); -// [self assertDate:auth.accessTokenExpirationDate equalTime:kNewExpireTime]; -// XCTAssertEqual(_changesObserved, kChangeAll); -//} -// -//- (void)assertOldTokensInAuth:(GIDAuthentication *)auth { -// [self assertOldAccessTokenInAuth:auth]; -// XCTAssertEqualObjects(auth.idToken, [self idToken]); -// if (_hasIDToken) { -// [self assertDate:auth.idTokenExpirationDate equalTime:_idTokenExpireTime]; -// } -//} -// -//- (void)assertNewTokensInAuth:(GIDAuthentication *)auth { -// [self assertNewAccessTokenInAuth:auth]; -// XCTAssertEqualObjects(auth.idToken, [self idTokenNew]); -// if (_hasIDToken) { -// [self assertDate:auth.idTokenExpirationDate equalTime:kNewExpireTime2]; -// } -//} -// -//- (void)setTokensExpireTime:(NSTimeInterval)fromNow { -// [self setExpireTimeForAccessToken:fromNow IDToken:fromNow]; -//} -// -//- (void)setExpireTimeForAccessToken:(NSTimeInterval)accessExpire IDToken:(NSTimeInterval)idExpire { -// _accessTokenExpireTime = [[NSDate date] timeIntervalSince1970] + accessExpire; -// _idTokenExpireTime = [[NSDate date] timeIntervalSince1970] + idExpire; -//} -// -//- (void)verifyTokensRefreshedWithMethod:(SEL)sel { -// GIDAuthentication *auth = [self observedAuth]; -// XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"]; -//#pragma clang diagnostic push -//#pragma clang diagnostic ignored "-Warc-performSelector-leaks" -// // We know the method doesn't return anything, so there is no risk of leaking. -// [auth performSelector:sel withObject:^(GIDAuthentication *authentication, NSError *error) { -//#pragma clang diagnostic pop -// [expectation fulfill]; -// [self assertNewTokensInAuth:authentication]; -// XCTAssertNil(error); -// }]; -// _tokenFetchHandler([self tokenResponseWithNewTokens], nil); -// [self waitForExpectationsWithTimeout:1 handler:nil]; -// [self assertNewTokensInAuth:auth]; -//} -// -//- (void)verifyTokensNotRefreshedWithMethod:(SEL)sel { -// GIDAuthentication *auth = [self observedAuth]; -// XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"]; -//#pragma clang diagnostic push -//#pragma clang diagnostic ignored "-Warc-performSelector-leaks" -// // We know the method doesn't return anything, so there is no risk of leaking. -// [auth performSelector:sel withObject:^(GIDAuthentication *authentication, NSError *error) { -//#pragma clang diagnostic pop -// [expectation fulfill]; -// [self assertOldTokensInAuth:authentication]; -// XCTAssertNil(error); -// }]; -// XCTAssertNil(_tokenFetchHandler); -// [self waitForExpectationsWithTimeout:1 handler:nil]; -// [self assertOldTokensInAuth:auth]; -//} -// -//@end diff --git a/GoogleSignIn/Tests/Unit/GIDEMMErrorHandlerTest.m b/GoogleSignIn/Tests/Unit/GIDEMMErrorHandlerTest.m index 57b81702..5f714f0a 100644 --- a/GoogleSignIn/Tests/Unit/GIDEMMErrorHandlerTest.m +++ b/GoogleSignIn/Tests/Unit/GIDEMMErrorHandlerTest.m @@ -11,6 +11,7 @@ // 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 diff --git a/GoogleSignIn/Tests/Unit/GIDEMMSupportTest.m b/GoogleSignIn/Tests/Unit/GIDEMMSupportTest.m new file mode 100644 index 00000000..fad6098b --- /dev/null +++ b/GoogleSignIn/Tests/Unit/GIDEMMSupportTest.m @@ -0,0 +1,210 @@ +// 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 + +#import "GoogleSignIn/Sources/GIDEMMSupport.h" + +#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h" + +#import "GoogleSignIn/Sources/GIDEMMErrorHandler.h" +#import "GoogleSignIn/Sources/GIDMDMPasscodeState.h" + +#ifdef SWIFT_PACKAGE +@import AppAuth; +@import GoogleUtilities_MethodSwizzler; +@import GoogleUtilities_SwizzlerTestHelpers; +@import OCMock; +#else +#import +#import +#import +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +// The system name in old iOS versions. +static NSString *const kOldIOSName = @"iPhone OS"; + +// The system name in new iOS versions. +static NSString *const kNewIOSName = @"iOS"; + +@interface GIDEMMSupportTest : XCTestCase +@end + +@implementation GIDEMMSupportTest + +- (void)testUpdatedEMMParametersWithParameters_NoEMM { + NSDictionary *originalParameters = @{ + @"xyz" : @"xyz", + }; + + NSDictionary *updatedEMMParameters = + [GIDEMMSupport updatedEMMParametersWithParameters:originalParameters]; + + XCTAssertEqualObjects(updatedEMMParameters, originalParameters); +} + +- (void)testUpdateEMMParametersWithParameters_systemName { + [GULSwizzler swizzleClass:[UIDevice class] + selector:@selector(systemName) + isClassSelector:NO + withBlock:^(id sender) { return kNewIOSName; }]; + + NSDictionary *originalParameters = @{ + @"emm_support" : @"xyz", + }; + + NSDictionary *updatedEMMParameters = + [GIDEMMSupport updatedEMMParametersWithParameters:originalParameters]; + + NSDictionary *expectedParameters = @{ + @"emm_support" : @"xyz", + @"device_os" : [NSString stringWithFormat:@"%@ %@", + kNewIOSName, [UIDevice currentDevice].systemVersion] + }; + + XCTAssertEqualObjects(updatedEMMParameters, expectedParameters); + + + [GULSwizzler unswizzleClass:[UIDevice class] + selector:@selector(systemName) + isClassSelector:NO]; +} + +// When the systemName is @"iPhone OS" we still get "iOS". +- (void)testUpdateEMMParametersWithParameters_systemNameNormalization { + [GULSwizzler swizzleClass:[UIDevice class] + selector:@selector(systemName) + isClassSelector:NO + withBlock:^(id sender) { return kOldIOSName; }]; + + NSDictionary *originalParameters = @{ + @"emm_support" : @"xyz", + }; + + NSDictionary *updatedEMMParameters = + [GIDEMMSupport updatedEMMParametersWithParameters:originalParameters]; + + NSDictionary *expectedParameters = @{ + @"emm_support" : @"xyz", + @"device_os" : [NSString stringWithFormat:@"%@ %@", + kNewIOSName, [UIDevice currentDevice].systemVersion] + }; + + XCTAssertEqualObjects(updatedEMMParameters, expectedParameters); + + + [GULSwizzler unswizzleClass:[UIDevice class] + selector:@selector(systemName) + isClassSelector:NO]; +} + +- (void)testUpdateEMMParametersWithParameters_passcodInfo { + [GULSwizzler swizzleClass:[UIDevice class] + selector:@selector(systemName) + isClassSelector:NO + withBlock:^(id sender) { return kOldIOSName; }]; + + NSDictionary *originalParameters = @{ + @"emm_support" : @"xyz", + @"device_os" : @"old one", + @"emm_passcode_info" : @"something", + }; + + NSDictionary *updatedEMMParameters = + [GIDEMMSupport updatedEMMParametersWithParameters:originalParameters]; + + NSDictionary *expectedParameters = @{ + @"emm_support" : @"xyz", + @"device_os" : [NSString stringWithFormat:@"%@ %@", + kNewIOSName, [UIDevice currentDevice].systemVersion], + @"emm_passcode_info" : [GIDMDMPasscodeState passcodeState].info, + }; + + XCTAssertEqualObjects(updatedEMMParameters, expectedParameters); + + [GULSwizzler unswizzleClass:[UIDevice class] + selector:@selector(systemName) + isClassSelector:NO]; +} + +- (void)testHandleTokenFetchEMMError_errorIsEMM { + // Set expectations. + NSDictionary *errorJSON = @{ @"error" : @"EMM Specific Error" }; + NSError *emmError = [NSError errorWithDomain:@"anydomain" + code:12345 + userInfo:@{ OIDOAuthErrorResponseErrorKey : errorJSON }]; + id mockEMMErrorHandler = OCMStrictClassMock([GIDEMMErrorHandler class]); + [[[mockEMMErrorHandler stub] andReturn:mockEMMErrorHandler] sharedInstance]; + __block void (^savedCompletion)(void); + [[[mockEMMErrorHandler expect] andReturnValue:@YES] + handleErrorFromResponse:errorJSON completion:[OCMArg checkWithBlock:^(id arg) { + savedCompletion = arg; + return YES; + }]]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"]; + + [GIDEMMSupport handleTokenFetchEMMError:emmError completion:^(NSError *error) { + [expectation fulfill]; + XCTAssertEqualObjects(error.domain, kGIDSignInErrorDomain); + XCTAssertEqual(error.code, kGIDSignInErrorCodeEMM); + }]; + + savedCompletion(); + [self waitForExpectationsWithTimeout:3 handler:nil]; + [mockEMMErrorHandler verify]; + [mockEMMErrorHandler stopMocking]; +} + +- (void)testHandleTokenFetchEMMError_errorIsNotEMM { + // Set expectations. + NSDictionary *errorJSON = @{ @"error" : @"Not EMM Specific Error" }; + NSError *emmError = [NSError errorWithDomain:@"anydomain" + code:12345 + userInfo:@{ OIDOAuthErrorResponseErrorKey : errorJSON }]; + id mockEMMErrorHandler = OCMStrictClassMock([GIDEMMErrorHandler class]); + [[[mockEMMErrorHandler stub] andReturn:mockEMMErrorHandler] sharedInstance]; + __block void (^savedCompletion)(void); + [[[mockEMMErrorHandler expect] andReturnValue:@NO] + handleErrorFromResponse:errorJSON completion:[OCMArg checkWithBlock:^(id arg) { + savedCompletion = arg; + return YES; + }]]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"]; + + [GIDEMMSupport handleTokenFetchEMMError:emmError completion:^(NSError *error) { + [expectation fulfill]; + XCTAssertEqualObjects(error.domain, @"anydomain"); + XCTAssertEqual(error.code, 12345); + }]; + + savedCompletion(); + [self waitForExpectationsWithTimeout:3 handler:nil]; + [mockEMMErrorHandler verify]; + [mockEMMErrorHandler stopMocking]; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST diff --git a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m index 99c1c381..08575ef9 100644 --- a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m +++ b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m @@ -29,8 +29,21 @@ #ifdef SWIFT_PACKAGE @import AppAuth; +@import GoogleUtilities_MethodSwizzler; +@import GoogleUtilities_SwizzlerTestHelpers; +@import GTMAppAuth; #else #import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import #endif static NSString *const kNewAccessToken = @"new_access_token"; @@ -43,7 +56,33 @@ @interface GIDGoogleUserTest : XCTestCase @end -@implementation GIDGoogleUserTest +@implementation GIDGoogleUserTest { + // The saved token fetch handler. + OIDTokenCallback _tokenFetchHandler; +} + +- (void)setUp { + _tokenFetchHandler = nil; + + // We need to use swizzle here because OCMock can not stub class method with arguments. + [GULSwizzler swizzleClass:[OIDAuthorizationService class] + selector:@selector(performTokenRequest:originalAuthorizationResponse:callback:) + isClassSelector:YES + withBlock:^(id sender, + OIDTokenRequest *request, + OIDAuthorizationResponse *authorizationResponse, + OIDTokenCallback callback) { + // Save the OIDTokenCallback. + self->_tokenFetchHandler = [callback copy]; + }]; +} + +- (void)tearDown { + [GULSwizzler unswizzleClass:[OIDAuthorizationService class] + selector:@selector(performTokenRequest:originalAuthorizationResponse:callback:) + isClassSelector:YES]; + +} #pragma mark - Tests @@ -109,14 +148,10 @@ - (void)testUpdateAuthState { profileData:updatedProfileData]; XCTAssertEqualObjects(user.accessToken.tokenString, kNewAccessToken); - NSDate *expectedAccessTokenExpirationDate = [[NSDate date] dateByAddingTimeInterval:kAccessTokenExpiresIn]; - XCTAssertEqualWithAccuracy([user.accessToken.expirationDate timeIntervalSince1970], - [expectedAccessTokenExpirationDate timeIntervalSince1970], kTimeAccuracy); + [self verifyUser:user accessTokenExpiresIn:kAccessTokenExpiresIn]; XCTAssertEqualObjects(user.idToken.tokenString, updatedIDToken); - NSDate *expectedIDTokenExpirationDate = [[NSDate date] dateByAddingTimeInterval:kNewIDTokenExpiresIn]; - XCTAssertEqualWithAccuracy([user.idToken.expirationDate timeIntervalSince1970], - [expectedIDTokenExpirationDate timeIntervalSince1970], kTimeAccuracy); + [self verifyUser:user idTokenExpiresIn:kNewIDTokenExpiresIn]; XCTAssertEqualObjects(user.refreshToken.tokenString, kNewRefreshToken); @@ -173,8 +208,223 @@ - (void)testFetcherAuthorizer_returnTheSameInstance { XCTAssertIdentical(fetcherAuthorizer, fetcherAuthorizer2); } +#pragma mark - Test `doWithFreshTokens:` + +- (void)testDoWithFreshTokens_refresh_givenBothTokensExpired { + // Both tokens expired 10 seconds ago. + GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:-10 idTokenExpiresIn:-10]; + NSString *newIdToken = [self idTokenWithExpiresIn:kNewIDTokenExpiresIn]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"]; + + // Save the intermediate states. + [user doWithFreshTokens:^(GIDGoogleUser * _Nullable user, NSError * _Nullable error) { + [expectation fulfill]; + XCTAssertNil(error); + XCTAssertEqualObjects(user.accessToken.tokenString, kNewAccessToken); + [self verifyUser:user accessTokenExpiresIn:kAccessTokenExpiresIn]; + XCTAssertEqualObjects(user.idToken.tokenString, newIdToken); + [self verifyUser:user idTokenExpiresIn:kNewIDTokenExpiresIn]; + }]; + + // Creates a fake response. + OIDTokenResponse *fakeResponse = [OIDTokenResponse testInstanceWithIDToken:newIdToken + accessToken:kNewAccessToken + expiresIn:@(kAccessTokenExpiresIn) + refreshToken:kRefreshToken + tokenRequest:nil]; + + _tokenFetchHandler(fakeResponse, nil); + [self waitForExpectationsWithTimeout:1 handler:nil]; + + +} + +- (void)testDoWithRefreshTokens_refresh_givenBothTokensExpired_NoNewIDToken { + // Both tokens expired 10 seconds ago. + GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:-10 idTokenExpiresIn:-10]; + // Creates a fake response without ID token. + + OIDTokenResponse *fakeResponse = [OIDTokenResponse testInstanceWithIDToken:nil + accessToken:kNewAccessToken + expiresIn:@(kAccessTokenExpiresIn) + refreshToken:kRefreshToken + tokenRequest:nil]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"]; + + // Save the intermediate states. + [user doWithFreshTokens:^(GIDGoogleUser * _Nullable user, NSError * _Nullable error) { + [expectation fulfill]; + XCTAssertNil(error); + XCTAssertEqualObjects(user.accessToken.tokenString, kNewAccessToken); + [self verifyUser:user accessTokenExpiresIn:kAccessTokenExpiresIn]; + XCTAssertNil(user.idToken); + }]; + + + _tokenFetchHandler(fakeResponse, nil); + [self waitForExpectationsWithTimeout:1 handler:nil]; + + +} + +- (void)testDoWithFreshTokens_refresh_givenAccessTokenExpired { + // Access token expired 10 seconds ago. ID token will expire in 10 minutes. + GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:-10 idTokenExpiresIn:10 * 60]; + // Creates a fake response. + NSString *newIdToken = [self idTokenWithExpiresIn:kNewIDTokenExpiresIn]; + OIDTokenResponse *fakeResponse = [OIDTokenResponse testInstanceWithIDToken:newIdToken + accessToken:kNewAccessToken + expiresIn:@(kAccessTokenExpiresIn) + refreshToken:kRefreshToken + tokenRequest:nil]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"]; + + // Save the intermediate states. + [user doWithFreshTokens:^(GIDGoogleUser * _Nullable user, NSError * _Nullable error) { + [expectation fulfill]; + XCTAssertNil(error); + XCTAssertEqualObjects(user.accessToken.tokenString, kNewAccessToken); + [self verifyUser:user accessTokenExpiresIn:kAccessTokenExpiresIn]; + XCTAssertEqualObjects(user.idToken.tokenString, newIdToken); + [self verifyUser:user idTokenExpiresIn:kNewIDTokenExpiresIn]; + }]; + + + _tokenFetchHandler(fakeResponse, nil); + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + +- (void)testDoWithFreshTokens_refresh_givenIDTokenExpired { + // ID token expired 10 seconds ago. Access token will expire in 10 minutes. + GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:10 * 60 idTokenExpiresIn:-10]; + + // Creates a fake response. + NSString *newIdToken = [self idTokenWithExpiresIn:kNewIDTokenExpiresIn]; + OIDTokenResponse *fakeResponse = [OIDTokenResponse testInstanceWithIDToken:newIdToken + accessToken:kNewAccessToken + expiresIn:@(kAccessTokenExpiresIn) + refreshToken:kRefreshToken + tokenRequest:nil]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"]; + + // Save the intermediate states. + [user doWithFreshTokens:^(GIDGoogleUser * _Nullable user, NSError * _Nullable error) { + [expectation fulfill]; + XCTAssertNil(error); + XCTAssertEqualObjects(user.accessToken.tokenString, kNewAccessToken); + [self verifyUser:user accessTokenExpiresIn:kAccessTokenExpiresIn]; + + XCTAssertEqualObjects(user.idToken.tokenString, newIdToken); + [self verifyUser:user idTokenExpiresIn:kNewIDTokenExpiresIn]; + }]; + + + _tokenFetchHandler(fakeResponse, nil); + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + +- (void)testDoWithFreshTokens_noRefreh_givenBothNotExpire { + // Both tokens will expire in 10 min. + NSTimeInterval expiresIn = 10 * 60; + GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:expiresIn + idTokenExpiresIn:expiresIn]; + + NSString *accessTokenStringBeforeRefresh = user.accessToken.tokenString; + NSString *idTokenStringBeforeRefresh = user.idToken.tokenString; + + XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"]; + + // Save the intermediate states. + [user doWithFreshTokens:^(GIDGoogleUser * _Nullable user, NSError * _Nullable error) { + [expectation fulfill]; + XCTAssertNil(error); + }]; + + [self waitForExpectationsWithTimeout:1 handler:nil]; + + XCTAssertEqualObjects(user.accessToken.tokenString, accessTokenStringBeforeRefresh); + [self verifyUser:user accessTokenExpiresIn:expiresIn]; + XCTAssertEqualObjects(user.idToken.tokenString, idTokenStringBeforeRefresh); + [self verifyUser:user idTokenExpiresIn:expiresIn]; +} + +- (void)testDoWithFreshTokens_noRefresh_givenRefreshErrors { + // Both tokens expired 10 second ago. + NSTimeInterval expiresIn = -10; + GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:expiresIn + idTokenExpiresIn:expiresIn]; + + NSString *accessTokenStringBeforeRefresh = user.accessToken.tokenString; + NSString *idTokenStringBeforeRefresh = user.idToken.tokenString; + + XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"]; + + // Save the intermediate states. + [user doWithFreshTokens:^(GIDGoogleUser * _Nullable user, NSError * _Nullable error) { + [expectation fulfill]; + XCTAssertNotNil(error); + XCTAssertNil(user); + }]; + + _tokenFetchHandler(nil, [self fakeError]); + [self waitForExpectationsWithTimeout:1 handler:nil]; + + XCTAssertEqualObjects(user.accessToken.tokenString, accessTokenStringBeforeRefresh); + [self verifyUser:user accessTokenExpiresIn:expiresIn]; + XCTAssertEqualObjects(user.idToken.tokenString, idTokenStringBeforeRefresh); + [self verifyUser:user idTokenExpiresIn:expiresIn]; +} + +- (void)testDoWithFreshTokens_handleConcurrentRefresh { + // Both tokens expired 10 second ago. + NSTimeInterval expiresIn = -10; + GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:expiresIn + idTokenExpiresIn:expiresIn]; + // Creates a fake response. + NSString *newIdToken = [self idTokenWithExpiresIn:kNewIDTokenExpiresIn]; + OIDTokenResponse *fakeResponse = [OIDTokenResponse testInstanceWithIDToken:newIdToken + accessToken:kNewAccessToken + expiresIn:@(kAccessTokenExpiresIn) + refreshToken:kRefreshToken + tokenRequest:nil]; + + XCTestExpectation *firstExpectation = + [self expectationWithDescription:@"First callback is called"]; + [user doWithFreshTokens:^(GIDGoogleUser *user, NSError *error) { + [firstExpectation fulfill]; + XCTAssertNil(error); + + XCTAssertEqualObjects(user.accessToken.tokenString, kNewAccessToken); + [self verifyUser:user accessTokenExpiresIn:kAccessTokenExpiresIn]; + + XCTAssertEqualObjects(user.idToken.tokenString, newIdToken); + [self verifyUser:user idTokenExpiresIn:kNewIDTokenExpiresIn]; + }]; + XCTestExpectation *secondExpectation = + [self expectationWithDescription:@"Second callback is called"]; + [user doWithFreshTokens:^(GIDGoogleUser *user, NSError *error) { + [secondExpectation fulfill]; + XCTAssertNil(error); + + XCTAssertEqualObjects(user.accessToken.tokenString, kNewAccessToken); + [self verifyUser:user accessTokenExpiresIn:kAccessTokenExpiresIn]; + + XCTAssertEqualObjects(user.idToken.tokenString, newIdToken); + [self verifyUser:user idTokenExpiresIn:kNewIDTokenExpiresIn]; + }]; + + + _tokenFetchHandler(fakeResponse, nil); + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + #pragma mark - Helpers +// Returns a GIDGoogleUser with different tokens expiresIn time. The token strings are constants. - (GIDGoogleUser *)googleUserWithAccessTokenExpiresIn:(NSTimeInterval)accessTokenExpiresIn idTokenExpiresIn:(NSTimeInterval)idTokenExpiresIn { NSString *idToken = [self idTokenWithExpiresIn:idTokenExpiresIn]; @@ -192,4 +442,21 @@ - (NSString *)idTokenWithExpiresIn:(NSTimeInterval)expiresIn { return [OIDTokenResponse idTokenWithSub:kUserID exp:@(expireTime)]; } +- (void)verifyUser:(GIDGoogleUser *)user accessTokenExpiresIn:(NSTimeInterval)expiresIn { + NSDate *expectedAccessTokenExpirationDate = [[NSDate date] dateByAddingTimeInterval:expiresIn]; + XCTAssertEqualWithAccuracy([user.accessToken.expirationDate timeIntervalSince1970], + [expectedAccessTokenExpirationDate timeIntervalSince1970], + kTimeAccuracy); +} + +- (void)verifyUser:(GIDGoogleUser *)user idTokenExpiresIn:(NSTimeInterval)expiresIn { + NSDate *expectedIDTokenExpirationDate = [[NSDate date] dateByAddingTimeInterval:expiresIn]; + XCTAssertEqualWithAccuracy([user.idToken.expirationDate timeIntervalSince1970], + [expectedIDTokenExpirationDate timeIntervalSince1970], kTimeAccuracy); +} + +- (NSError *)fakeError { + return [NSError errorWithDomain:@"fake.domain" code:-1 userInfo:nil]; +} + @end From ee6f7a4e1d3817bec6bc4bfcab3de0f3b6af6290 Mon Sep 17 00:00:00 2001 From: pinlu Date: Thu, 6 Oct 2022 12:45:37 -0700 Subject: [PATCH 20/26] Minor improvements and move GIDEMMSupportTest to a separate PR. --- GoogleSignIn/Tests/Unit/GIDEMMSupportTest.m | 210 -------------------- GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m | 7 +- GoogleSignIn/Tests/Unit/GIDSignInTest.m | 6 - 3 files changed, 1 insertion(+), 222 deletions(-) delete mode 100644 GoogleSignIn/Tests/Unit/GIDEMMSupportTest.m diff --git a/GoogleSignIn/Tests/Unit/GIDEMMSupportTest.m b/GoogleSignIn/Tests/Unit/GIDEMMSupportTest.m deleted file mode 100644 index fad6098b..00000000 --- a/GoogleSignIn/Tests/Unit/GIDEMMSupportTest.m +++ /dev/null @@ -1,210 +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 - -#import "GoogleSignIn/Sources/GIDEMMSupport.h" - -#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h" - -#import "GoogleSignIn/Sources/GIDEMMErrorHandler.h" -#import "GoogleSignIn/Sources/GIDMDMPasscodeState.h" - -#ifdef SWIFT_PACKAGE -@import AppAuth; -@import GoogleUtilities_MethodSwizzler; -@import GoogleUtilities_SwizzlerTestHelpers; -@import OCMock; -#else -#import -#import -#import -#import -#endif - -NS_ASSUME_NONNULL_BEGIN - -// The system name in old iOS versions. -static NSString *const kOldIOSName = @"iPhone OS"; - -// The system name in new iOS versions. -static NSString *const kNewIOSName = @"iOS"; - -@interface GIDEMMSupportTest : XCTestCase -@end - -@implementation GIDEMMSupportTest - -- (void)testUpdatedEMMParametersWithParameters_NoEMM { - NSDictionary *originalParameters = @{ - @"xyz" : @"xyz", - }; - - NSDictionary *updatedEMMParameters = - [GIDEMMSupport updatedEMMParametersWithParameters:originalParameters]; - - XCTAssertEqualObjects(updatedEMMParameters, originalParameters); -} - -- (void)testUpdateEMMParametersWithParameters_systemName { - [GULSwizzler swizzleClass:[UIDevice class] - selector:@selector(systemName) - isClassSelector:NO - withBlock:^(id sender) { return kNewIOSName; }]; - - NSDictionary *originalParameters = @{ - @"emm_support" : @"xyz", - }; - - NSDictionary *updatedEMMParameters = - [GIDEMMSupport updatedEMMParametersWithParameters:originalParameters]; - - NSDictionary *expectedParameters = @{ - @"emm_support" : @"xyz", - @"device_os" : [NSString stringWithFormat:@"%@ %@", - kNewIOSName, [UIDevice currentDevice].systemVersion] - }; - - XCTAssertEqualObjects(updatedEMMParameters, expectedParameters); - - - [GULSwizzler unswizzleClass:[UIDevice class] - selector:@selector(systemName) - isClassSelector:NO]; -} - -// When the systemName is @"iPhone OS" we still get "iOS". -- (void)testUpdateEMMParametersWithParameters_systemNameNormalization { - [GULSwizzler swizzleClass:[UIDevice class] - selector:@selector(systemName) - isClassSelector:NO - withBlock:^(id sender) { return kOldIOSName; }]; - - NSDictionary *originalParameters = @{ - @"emm_support" : @"xyz", - }; - - NSDictionary *updatedEMMParameters = - [GIDEMMSupport updatedEMMParametersWithParameters:originalParameters]; - - NSDictionary *expectedParameters = @{ - @"emm_support" : @"xyz", - @"device_os" : [NSString stringWithFormat:@"%@ %@", - kNewIOSName, [UIDevice currentDevice].systemVersion] - }; - - XCTAssertEqualObjects(updatedEMMParameters, expectedParameters); - - - [GULSwizzler unswizzleClass:[UIDevice class] - selector:@selector(systemName) - isClassSelector:NO]; -} - -- (void)testUpdateEMMParametersWithParameters_passcodInfo { - [GULSwizzler swizzleClass:[UIDevice class] - selector:@selector(systemName) - isClassSelector:NO - withBlock:^(id sender) { return kOldIOSName; }]; - - NSDictionary *originalParameters = @{ - @"emm_support" : @"xyz", - @"device_os" : @"old one", - @"emm_passcode_info" : @"something", - }; - - NSDictionary *updatedEMMParameters = - [GIDEMMSupport updatedEMMParametersWithParameters:originalParameters]; - - NSDictionary *expectedParameters = @{ - @"emm_support" : @"xyz", - @"device_os" : [NSString stringWithFormat:@"%@ %@", - kNewIOSName, [UIDevice currentDevice].systemVersion], - @"emm_passcode_info" : [GIDMDMPasscodeState passcodeState].info, - }; - - XCTAssertEqualObjects(updatedEMMParameters, expectedParameters); - - [GULSwizzler unswizzleClass:[UIDevice class] - selector:@selector(systemName) - isClassSelector:NO]; -} - -- (void)testHandleTokenFetchEMMError_errorIsEMM { - // Set expectations. - NSDictionary *errorJSON = @{ @"error" : @"EMM Specific Error" }; - NSError *emmError = [NSError errorWithDomain:@"anydomain" - code:12345 - userInfo:@{ OIDOAuthErrorResponseErrorKey : errorJSON }]; - id mockEMMErrorHandler = OCMStrictClassMock([GIDEMMErrorHandler class]); - [[[mockEMMErrorHandler stub] andReturn:mockEMMErrorHandler] sharedInstance]; - __block void (^savedCompletion)(void); - [[[mockEMMErrorHandler expect] andReturnValue:@YES] - handleErrorFromResponse:errorJSON completion:[OCMArg checkWithBlock:^(id arg) { - savedCompletion = arg; - return YES; - }]]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"]; - - [GIDEMMSupport handleTokenFetchEMMError:emmError completion:^(NSError *error) { - [expectation fulfill]; - XCTAssertEqualObjects(error.domain, kGIDSignInErrorDomain); - XCTAssertEqual(error.code, kGIDSignInErrorCodeEMM); - }]; - - savedCompletion(); - [self waitForExpectationsWithTimeout:3 handler:nil]; - [mockEMMErrorHandler verify]; - [mockEMMErrorHandler stopMocking]; -} - -- (void)testHandleTokenFetchEMMError_errorIsNotEMM { - // Set expectations. - NSDictionary *errorJSON = @{ @"error" : @"Not EMM Specific Error" }; - NSError *emmError = [NSError errorWithDomain:@"anydomain" - code:12345 - userInfo:@{ OIDOAuthErrorResponseErrorKey : errorJSON }]; - id mockEMMErrorHandler = OCMStrictClassMock([GIDEMMErrorHandler class]); - [[[mockEMMErrorHandler stub] andReturn:mockEMMErrorHandler] sharedInstance]; - __block void (^savedCompletion)(void); - [[[mockEMMErrorHandler expect] andReturnValue:@NO] - handleErrorFromResponse:errorJSON completion:[OCMArg checkWithBlock:^(id arg) { - savedCompletion = arg; - return YES; - }]]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"Callback is called"]; - - [GIDEMMSupport handleTokenFetchEMMError:emmError completion:^(NSError *error) { - [expectation fulfill]; - XCTAssertEqualObjects(error.domain, @"anydomain"); - XCTAssertEqual(error.code, 12345); - }]; - - savedCompletion(); - [self waitForExpectationsWithTimeout:3 handler:nil]; - [mockEMMErrorHandler verify]; - [mockEMMErrorHandler stopMocking]; -} - -@end - -NS_ASSUME_NONNULL_END - -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST diff --git a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m index 08575ef9..4edb3ac9 100644 --- a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m +++ b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m @@ -81,7 +81,6 @@ - (void)tearDown { [GULSwizzler unswizzleClass:[OIDAuthorizationService class] selector:@selector(performTokenRequest:originalAuthorizationResponse:callback:) isClassSelector:YES]; - } #pragma mark - Tests @@ -236,8 +235,6 @@ - (void)testDoWithFreshTokens_refresh_givenBothTokensExpired { _tokenFetchHandler(fakeResponse, nil); [self waitForExpectationsWithTimeout:1 handler:nil]; - - } - (void)testDoWithRefreshTokens_refresh_givenBothTokensExpired_NoNewIDToken { @@ -265,8 +262,6 @@ - (void)testDoWithRefreshTokens_refresh_givenBothTokensExpired_NoNewIDToken { _tokenFetchHandler(fakeResponse, nil); [self waitForExpectationsWithTimeout:1 handler:nil]; - - } - (void)testDoWithFreshTokens_refresh_givenAccessTokenExpired { @@ -327,7 +322,7 @@ - (void)testDoWithFreshTokens_refresh_givenIDTokenExpired { [self waitForExpectationsWithTimeout:1 handler:nil]; } -- (void)testDoWithFreshTokens_noRefreh_givenBothNotExpire { +- (void)testDoWithFreshTokens_noRefresh_givenBothTokensNotExpired { // Both tokens will expire in 10 min. NSTimeInterval expiresIn = 10 * 60; GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:expiresIn diff --git a/GoogleSignIn/Tests/Unit/GIDSignInTest.m b/GoogleSignIn/Tests/Unit/GIDSignInTest.m index 3d95c5c0..1b8ae41d 100644 --- a/GoogleSignIn/Tests/Unit/GIDSignInTest.m +++ b/GoogleSignIn/Tests/Unit/GIDSignInTest.m @@ -207,9 +207,6 @@ @interface GIDSignInTest : XCTestCase { // Mock for |GIDGoogleUser|. id _user; - // Mock for |GIDAuthentication|. - id _authentication; - // Mock for |OIDAuthorizationService| id _oidAuthorizationService; @@ -367,7 +364,6 @@ - (void)tearDown { OCMVerifyAll(_tokenRequest); OCMVerifyAll(_authorization); OCMVerifyAll(_user); - OCMVerifyAll(_authentication); OCMVerifyAll(_oidAuthorizationService); #if TARGET_OS_IOS || TARGET_OS_MACCATALYST @@ -417,8 +413,6 @@ - (void)testRestorePreviousSignInNoRefresh_hasPreviousUser { OCMStub([_tokenResponse accessToken]).andReturn(kAccessToken); OCMStub([_tokenResponse accessTokenExpirationDate]).andReturn(nil); - - [_signIn restorePreviousSignInNoRefresh]; [_authorization verify]; From b9f312a6d7bd9240dca149754b9dcf3a38f32595 Mon Sep 17 00:00:00 2001 From: pinlu Date: Fri, 7 Oct 2022 14:18:19 -0700 Subject: [PATCH 21/26] Resolve comments --- GoogleSignIn/Sources/GIDGoogleUser.m | 103 ++++++++++++------ GoogleSignIn/Sources/GIDGoogleUser_Private.h | 3 + .../Public/GoogleSignIn/GIDGoogleUser.h | 13 +-- .../Source/SignInViewController.m | 4 +- 4 files changed, 82 insertions(+), 41 deletions(-) diff --git a/GoogleSignIn/Sources/GIDGoogleUser.m b/GoogleSignIn/Sources/GIDGoogleUser.m index 1e97fda0..19ba2b0f 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser.m +++ b/GoogleSignIn/Sources/GIDGoogleUser.m @@ -38,7 +38,7 @@ // Key constants used for encode and decode. static NSString *const kAuthenticationKey = @"authentication"; static NSString *const kProfileDataKey = @"profileData"; -static NSString *const kAuthState = @"authState"; +static NSString *const kAuthStateKey = @"authState"; // Parameters for the token exchange endpoint. static NSString *const kAudienceParameter = @"audience"; @@ -48,15 +48,58 @@ static NSString *const kEMMSupportParameterName = @"emm_support"; // Minimal time interval before expiration for the access token or it needs to be refreshed. -NSTimeInterval kMinimalTimeToExpire = 60.0; +static NSTimeInterval const kMinimalTimeToExpire = 60.0; + +#pragma mark - GIDAuthentication + +// Internal class for GIDGoogleUser decoding backward compatibility. +@interface GIDAuthentication : NSObject + +@property(nonatomic) OIDAuthState* authState; + +- (instancetype)initWithAuthState:(OIDAuthState *)authState; + +@end + +@implementation GIDAuthentication + +- (instancetype)initWithAuthState:(OIDAuthState *)authState { + self = [super init]; + if (self) { + _authState = authState; + } + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)decoder { + self = [super init]; + if (self) { + _authState = [decoder decodeObjectOfClass:[OIDAuthState class] forKey:kAuthStateKey]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)encoder { + [encoder encodeObject:_authState forKey:kAuthStateKey]; +} + +@end + +#pragma mark - GIDGoogleUser @implementation GIDGoogleUser { OIDAuthState *_authState; GIDConfiguration *_cachedConfiguration; - // A queue for pending refrsh token handlers so we don't fire multiple requests in parallel. + // A queue for pending token refresh handlers so we don't fire multiple requests in parallel. // Access to this ivar should be synchronized. - NSMutableArray *_refreshTokensHandlerQueue; + NSMutableArray *_tokenRefreshHandlerQueue; } - (nullable NSString *)userID { @@ -116,10 +159,10 @@ - (void)doWithFreshTokens:(GIDGoogleUserCompletion)completion { }); return; } - @synchronized (_refreshTokensHandlerQueue) { + @synchronized (_tokenRefreshHandlerQueue) { // Push the handler into the callback queue. - [_refreshTokensHandlerQueue addObject:[completion copy]]; - if (_refreshTokensHandlerQueue.count > 1) { + [_tokenRefreshHandlerQueue addObject:[completion copy]]; + if (_tokenRefreshHandlerQueue.count > 1) { // This is not the first handler in the queue, no fetch is needed. return; } @@ -153,10 +196,10 @@ - (void)doWithFreshTokens:(GIDGoogleUserCompletion)completion { #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST [GIDEMMSupport handleTokenFetchEMMError:error completion:^(NSError *_Nullable error) { // Process the handler queue to call back. - NSArray *refreshTokensHandlerQueue; - @synchronized(self->_refreshTokensHandlerQueue) { - refreshTokensHandlerQueue = [self->_refreshTokensHandlerQueue copy]; - [self->_refreshTokensHandlerQueue removeAllObjects]; + NSArray *refreshTokensHandlerQueue; + @synchronized(self->_tokenRefreshHandlerQueue) { + refreshTokensHandlerQueue = [self->_tokenRefreshHandlerQueue copy]; + [self->_tokenRefreshHandlerQueue removeAllObjects]; } for (GIDGoogleUserCompletion completion in refreshTokensHandlerQueue) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -165,10 +208,10 @@ - (void)doWithFreshTokens:(GIDGoogleUserCompletion)completion { } }]; #elif TARGET_OS_OSX || TARGET_OS_MACCATALYST - NSArray *refreshTokensHandlerQueue; - @synchronized(self->_refreshTokensHandlerQueue) { - refreshTokensHandlerQueue = [self->_refreshTokensHandlerQueue copy]; - [self->_refreshTokensHandlerQueue removeAllObjects]; + NSArray *refreshTokensHandlerQueue; + @synchronized(self->_tokenRefreshHandlerQueue) { + refreshTokensHandlerQueue = [self->_tokenRefreshHandlerQueue copy]; + [self->_tokenRefreshHandlerQueue removeAllObjects]; } for (GIDGoogleUserCompletion completion in refreshTokensHandlerQueue) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -192,7 +235,7 @@ - (instancetype)initWithAuthState:(OIDAuthState *)authState profileData:(nullable GIDProfileData *)profileData { self = [super init]; if (self) { - _refreshTokensHandlerQueue = [[NSMutableArray alloc] init]; + _tokenRefreshHandlerQueue = [[NSMutableArray alloc] init]; _authState = authState; _authState.stateChangeDelegate = self; _profile = profileData; @@ -292,30 +335,26 @@ + (BOOL)supportsSecureCoding { - (nullable instancetype)initWithCoder:(NSCoder *)decoder { self = [super init]; if (self) { - _refreshTokensHandlerQueue = [[NSMutableArray alloc] init]; - _profile = [decoder decodeObjectOfClass:[GIDProfileData class] forKey:kProfileDataKey]; - _authState = [decoder decodeObjectOfClass:[OIDAuthState class] forKey:kAuthState]; - _authState.stateChangeDelegate = self; + GIDProfileData *profile = + [decoder decodeObjectOfClass:[GIDProfileData class] forKey:kProfileDataKey]; -#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]; -#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST - authorization.tokenRefreshDelegate = self; - self.fetcherAuthorizer = authorization; + OIDAuthState *authState; + if ([decoder containsValueForKey:kAuthStateKey]) { // Current encoding + authState = [decoder decodeObjectOfClass:[OIDAuthState class] forKey:kAuthStateKey]; + } else { // Old encoding + GIDAuthentication *authentication = [decoder decodeObjectOfClass:[GIDAuthentication class] + forKey:kAuthenticationKey]; + authState = authentication.authState; + } - [self updateTokensWithAuthState:_authState]; + self = [self initWithAuthState:authState profileData:profile]; } return self; } - (void)encodeWithCoder:(NSCoder *)encoder { [encoder encodeObject:_profile forKey:kProfileDataKey]; - [encoder encodeObject:_authState forKey:kAuthState]; + [encoder encodeObject:_authState forKey:kAuthStateKey]; } @end diff --git a/GoogleSignIn/Sources/GIDGoogleUser_Private.h b/GoogleSignIn/Sources/GIDGoogleUser_Private.h index b53e99df..bdc418c9 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser_Private.h +++ b/GoogleSignIn/Sources/GIDGoogleUser_Private.h @@ -28,6 +28,9 @@ NS_ASSUME_NONNULL_BEGIN @class OIDAuthState; +/// 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 () diff --git a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h index 6d2dac5f..25edada6 100644 --- a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h +++ b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h @@ -31,9 +31,6 @@ 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); - /// This class represents a user account. @interface GIDGoogleUser : NSObject @@ -67,11 +64,13 @@ typedef void (^GIDGoogleUserCompletion)(GIDGoogleUser *_Nullable user, NSError * @property(nonatomic, readonly) id fetcherAuthorizer; #pragma clang diagnostic pop -/// Get a valid access token and a valid ID token, refreshing them first if they have expired or are about to expire. +/// Get a valid access token and a valid ID token, refreshing them first if they have expired or +/// are about to expire. /// -/// @param completion A completion block that takes a `GIDGoogleUser` or an error if the attempt to refresh tokens was -/// unsuccessful. The block will be called asynchronously on the main queue. -- (void)doWithFreshTokens:(GIDGoogleUserCompletion)completion; +/// @param completion A completion block that takes a `GIDGoogleUser` or an error if the attempt to +/// refresh tokens was unsuccessful. The block will be called asynchronously on the main queue. +- (void)doWithFreshTokens:(void (^)(GIDGoogleUser *_Nullable user, + NSError *_Nullable error))completion; @end diff --git a/Samples/ObjC/SignInSample/Source/SignInViewController.m b/Samples/ObjC/SignInSample/Source/SignInViewController.m index f213a631..12a5d82e 100644 --- a/Samples/ObjC/SignInSample/Source/SignInViewController.m +++ b/Samples/ObjC/SignInSample/Source/SignInViewController.m @@ -185,7 +185,7 @@ - (CGFloat)minimumButtonWidth { - (void)reportAuthStatus { GIDGoogleUser *googleUser = [GIDSignIn.sharedInstance currentUser]; - if (googleUser.authentication) { + if (googleUser) { _signInAuthStatus.text = @"Status: Authenticated"; } else { // To authenticate, use Google Sign-In button. @@ -198,7 +198,7 @@ - (void)reportAuthStatus { // Update the interface elements containing user data to reflect the // currently signed in user. - (void)refreshUserInfo { - if (GIDSignIn.sharedInstance.currentUser.authentication == nil) { + if (!GIDSignIn.sharedInstance.currentUser) { self.userName.text = kPlaceholderUserName; self.userEmailAddress.text = kPlaceholderEmailAddress; self.userAvatar.image = [UIImage imageNamed:kPlaceholderAvatarImageName]; From 6d8575d30d0dd34f8baa8b4d225bb0d9e82561e1 Mon Sep 17 00:00:00 2001 From: pinlu Date: Mon, 10 Oct 2022 13:59:17 -0700 Subject: [PATCH 22/26] Minor improvement. --- GoogleSignIn/Sources/GIDGoogleUser.m | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/GoogleSignIn/Sources/GIDGoogleUser.m b/GoogleSignIn/Sources/GIDGoogleUser.m index 19ba2b0f..23706c75 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser.m +++ b/GoogleSignIn/Sources/GIDGoogleUser.m @@ -57,20 +57,10 @@ @interface GIDAuthentication : NSObject @property(nonatomic) OIDAuthState* authState; -- (instancetype)initWithAuthState:(OIDAuthState *)authState; - @end @implementation GIDAuthentication -- (instancetype)initWithAuthState:(OIDAuthState *)authState { - self = [super init]; - if (self) { - _authState = authState; - } - return self; -} - #pragma mark - NSSecureCoding + (BOOL)supportsSecureCoding { From ded0a373f6abdbbd10bffd2bba73d9c29fbffd87 Mon Sep 17 00:00:00 2001 From: pinlu Date: Mon, 10 Oct 2022 20:06:36 -0700 Subject: [PATCH 23/26] Resolve comments --- GoogleSignIn/Sources/GIDGoogleUser.m | 59 +++++++++---------- GoogleSignIn/Sources/GIDGoogleUser_Private.h | 2 +- GoogleSignIn/Sources/GIDSignIn.m | 9 ++- .../Public/GoogleSignIn/GIDGoogleUser.h | 1 - GoogleSignIn/Tests/Unit/GIDSignInTest.m | 2 +- 5 files changed, 37 insertions(+), 36 deletions(-) diff --git a/GoogleSignIn/Sources/GIDGoogleUser.m b/GoogleSignIn/Sources/GIDGoogleUser.m index 23706c75..749fc114 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser.m +++ b/GoogleSignIn/Sources/GIDGoogleUser.m @@ -36,7 +36,6 @@ static NSString *const kHostedDomainIDTokenClaimKey = @"hd"; // Key constants used for encode and decode. -static NSString *const kAuthenticationKey = @"authentication"; static NSString *const kProfileDataKey = @"profileData"; static NSString *const kAuthStateKey = @"authState"; @@ -52,7 +51,7 @@ #pragma mark - GIDAuthentication -// Internal class for GIDGoogleUser decoding backward compatibility. +// Internal class for GIDGoogleUser NSCoding backward compatibility. @interface GIDAuthentication : NSObject @property(nonatomic) OIDAuthState* authState; @@ -70,13 +69,13 @@ + (BOOL)supportsSecureCoding { - (nullable instancetype)initWithCoder:(NSCoder *)decoder { self = [super init]; if (self) { - _authState = [decoder decodeObjectOfClass:[OIDAuthState class] forKey:kAuthStateKey]; + self.authState = [decoder decodeObjectOfClass:[OIDAuthState class] forKey:kAuthStateKey]; } return self; } - (void)encodeWithCoder:(NSCoder *)encoder { - [encoder encodeObject:_authState forKey:kAuthStateKey]; + [encoder encodeObject:self.authState forKey:kAuthStateKey]; } @end @@ -84,7 +83,6 @@ - (void)encodeWithCoder:(NSCoder *)encoder { #pragma mark - GIDGoogleUser @implementation GIDGoogleUser { - OIDAuthState *_authState; GIDConfiguration *_cachedConfiguration; // A queue for pending token refresh handlers so we don't fire multiple requests in parallel. @@ -95,8 +93,7 @@ @implementation GIDGoogleUser { - (nullable NSString *)userID { NSString *idTokenString = self.idToken.tokenString; if (idTokenString) { - OIDIDToken *idTokenDecoded = - [[OIDIDToken alloc] initWithIDTokenString:idTokenString]; + OIDIDToken *idTokenDecoded = [[OIDIDToken alloc] initWithIDTokenString:idTokenString]; if (idTokenDecoded && idTokenDecoded.subject) { return [idTokenDecoded.subject copy]; } @@ -106,7 +103,7 @@ - (nullable NSString *)userID { - (nullable NSArray *)grantedScopes { NSArray *grantedScopes; - NSString *grantedScopeString = _authState.lastTokenResponse.scope; + NSString *grantedScopeString = self.authState.lastTokenResponse.scope; if (grantedScopeString) { // If we have a 'scope' parameter from the backend, this is authoritative. // Remove leading and trailing whitespace. @@ -126,11 +123,11 @@ - (GIDConfiguration *)configuration { @synchronized(self) { // Caches the configuration since it would not change for one GIDGoogleUser instance. if (!_cachedConfiguration) { - NSString *clientID = _authState.lastAuthorizationResponse.request.clientID; + NSString *clientID = self.authState.lastAuthorizationResponse.request.clientID; NSString *serverClientID = - _authState.lastTokenResponse.request.additionalParameters[kAudienceParameter]; + self.authState.lastTokenResponse.request.additionalParameters[kAudienceParameter]; NSString *openIDRealm = - _authState.lastTokenResponse.request.additionalParameters[kOpenIDRealmParameter]; + self.authState.lastTokenResponse.request.additionalParameters[kOpenIDRealmParameter]; _cachedConfiguration = [[GIDConfiguration alloc] initWithClientID:clientID serverClientID:serverClientID @@ -162,25 +159,25 @@ - (void)doWithFreshTokens:(GIDGoogleUserCompletion)completion { #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST [additionalParameters addEntriesFromDictionary: [GIDEMMSupport updatedEMMParametersWithParameters: - _authState.lastTokenResponse.request.additionalParameters]]; + self.authState.lastTokenResponse.request.additionalParameters]]; #elif TARGET_OS_OSX || TARGET_OS_MACCATALYST [additionalParameters addEntriesFromDictionary: - _authState.lastTokenResponse.request.additionalParameters]; + self.authState.lastTokenResponse.request.additionalParameters]; #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST additionalParameters[kSDKVersionLoggingParameter] = GIDVersion(); additionalParameters[kEnvironmentLoggingParameter] = GIDEnvironment(); OIDTokenRequest *tokenRefreshRequest = - [_authState tokenRefreshRequestWithAdditionalParameters:additionalParameters]; + [self.authState tokenRefreshRequestWithAdditionalParameters:additionalParameters]; [OIDAuthorizationService performTokenRequest:tokenRefreshRequest - originalAuthorizationResponse:_authState.lastAuthorizationResponse + originalAuthorizationResponse:self.authState.lastAuthorizationResponse callback:^(OIDTokenResponse *_Nullable tokenResponse, NSError *_Nullable error) { if (tokenResponse) { - [self->_authState updateWithTokenResponse:tokenResponse error:nil]; + [self.authState updateWithTokenResponse:tokenResponse error:nil]; } else { if (error.domain == OIDOAuthTokenErrorDomain) { - [self->_authState updateWithAuthorizationError:error]; + [self.authState updateWithAuthorizationError:error]; } } #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST @@ -212,12 +209,16 @@ - (void)doWithFreshTokens:(GIDGoogleUserCompletion)completion { }]; } +- (OIDAuthState *) authState{ + return ((GTMAppAuthFetcherAuthorization *)self.fetcherAuthorizer).authState; +} + #pragma mark - Private Methods #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - (nullable NSString *)emmSupport { - return - _authState.lastAuthorizationResponse.request.additionalParameters[kEMMSupportParameterName]; + return self.authState.lastAuthorizationResponse + .request.additionalParameters[kEMMSupportParameterName]; } #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST @@ -226,19 +227,18 @@ - (instancetype)initWithAuthState:(OIDAuthState *)authState self = [super init]; if (self) { _tokenRefreshHandlerQueue = [[NSMutableArray alloc] init]; - _authState = authState; - _authState.stateChangeDelegate = self; _profile = profileData; #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST GTMAppAuthFetcherAuthorization *authorization = self.emmSupport ? - [[GIDAppAuthFetcherAuthorizationWithEMMSupport alloc] initWithAuthState:_authState] : - [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:_authState]; + [[GIDAppAuthFetcherAuthorizationWithEMMSupport alloc] initWithAuthState:authState] : + [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState]; #elif TARGET_OS_OSX || TARGET_OS_MACCATALYST GTMAppAuthFetcherAuthorization *authorization = - [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:_authState]; + [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState]; #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST authorization.tokenRefreshDelegate = self; + authorization.authState.stateChangeDelegate = self; self.fetcherAuthorizer = authorization; [self updateTokensWithAuthState:authState]; @@ -246,13 +246,11 @@ - (instancetype)initWithAuthState:(OIDAuthState *)authState return self; } -- (void)updateWithTokenResponse:(nullable OIDTokenResponse *)tokenResponse +- (void)updateWithTokenResponse:(OIDTokenResponse *)tokenResponse profileData:(nullable GIDProfileData *)profileData { @synchronized(self) { _profile = profileData; - if (tokenResponse) { - [_authState updateWithTokenResponse:tokenResponse error:nil]; - } + [self.authState updateWithTokenResponse:tokenResponse error:nil]; } } @@ -322,6 +320,7 @@ + (BOOL)supportsSecureCoding { return YES; } + - (nullable instancetype)initWithCoder:(NSCoder *)decoder { self = [super init]; if (self) { @@ -333,7 +332,7 @@ - (nullable instancetype)initWithCoder:(NSCoder *)decoder { authState = [decoder decodeObjectOfClass:[OIDAuthState class] forKey:kAuthStateKey]; } else { // Old encoding GIDAuthentication *authentication = [decoder decodeObjectOfClass:[GIDAuthentication class] - forKey:kAuthenticationKey]; + forKey:@"authentication"]; authState = authentication.authState; } @@ -344,7 +343,7 @@ - (nullable instancetype)initWithCoder:(NSCoder *)decoder { - (void)encodeWithCoder:(NSCoder *)encoder { [encoder encodeObject:_profile forKey:kProfileDataKey]; - [encoder encodeObject:_authState forKey:kAuthStateKey]; + [encoder encodeObject:self.authState forKey:kAuthStateKey]; } @end diff --git a/GoogleSignIn/Sources/GIDGoogleUser_Private.h b/GoogleSignIn/Sources/GIDGoogleUser_Private.h index bdc418c9..bc4765a2 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser_Private.h +++ b/GoogleSignIn/Sources/GIDGoogleUser_Private.h @@ -59,7 +59,7 @@ typedef void (^GIDGoogleUserCompletion)(GIDGoogleUser *_Nullable user, NSError * profileData:(nullable GIDProfileData *)profileData; // Update the auth state and profile data. -- (void)updateWithTokenResponse:(nullable OIDTokenResponse *)tokenResponse +- (void)updateWithTokenResponse:(OIDTokenResponse *)tokenResponse profileData:(nullable GIDProfileData *)profileData; @end diff --git a/GoogleSignIn/Sources/GIDSignIn.m b/GoogleSignIn/Sources/GIDSignIn.m index f51d06d3..77e23fec 100644 --- a/GoogleSignIn/Sources/GIDSignIn.m +++ b/GoogleSignIn/Sources/GIDSignIn.m @@ -216,7 +216,8 @@ - (BOOL)restorePreviousSignInNoRefresh { [[OIDIDToken alloc] initWithIDTokenString:authState.lastTokenResponse.idToken]; GIDProfileData *profileData = [self profileDataWithIDToken:idToken]; - GIDGoogleUser *user = [[GIDGoogleUser alloc] initWithAuthState:authState profileData:profileData]; + GIDGoogleUser *user = [[GIDGoogleUser alloc] initWithAuthState:authState + profileData:profileData]; [self setCurrentUserWithKVO:user]; return YES; } @@ -861,8 +862,10 @@ - (void)addCompletionCallback:(GIDAuthFlow *)authFlow { completion(nil, handlerAuthFlow.error); } else { OIDAuthState *authState = handlerAuthFlow.authState; - NSString *_Nullable serverAuthCode = [authState.lastTokenResponse.additionalParameters[@"server_code"] copy]; - GIDUserAuth *userAuth = [[GIDUserAuth alloc] initWithGoogleUser:self->_currentUser serverAuthCode:serverAuthCode]; + NSString *_Nullable serverAuthCode = + [authState.lastTokenResponse.additionalParameters[@"server_code"] copy]; + GIDUserAuth *userAuth = [[GIDUserAuth alloc] initWithGoogleUser:self->_currentUser + serverAuthCode:serverAuthCode]; completion(userAuth, nil); } }); diff --git a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h index 25edada6..558d9a9f 100644 --- a/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h +++ b/GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h @@ -25,7 +25,6 @@ #endif @class GIDConfiguration; -@class GIDGoogleUser; @class GIDToken; @class GIDProfileData; diff --git a/GoogleSignIn/Tests/Unit/GIDSignInTest.m b/GoogleSignIn/Tests/Unit/GIDSignInTest.m index 1b8ae41d..af465b7a 100644 --- a/GoogleSignIn/Tests/Unit/GIDSignInTest.m +++ b/GoogleSignIn/Tests/Unit/GIDSignInTest.m @@ -390,7 +390,7 @@ - (void)testShareInstance { } - (void)testRestorePreviousSignInNoRefresh_hasPreviousUser { - [[[_authorization expect] andReturn:_authState] authState]; + [[[_authorization stub] andReturn:_authState] authState]; [[_authorization expect] setTokenRefreshDelegate:OCMOCK_ANY]; OCMStub([_authState lastTokenResponse]).andReturn(_tokenResponse); OCMStub([_authState refreshToken]).andReturn(kRefreshToken); From 7f4aab8af01e8cab183325dfb0833107fe1b242d Mon Sep 17 00:00:00 2001 From: pinlu Date: Tue, 11 Oct 2022 15:54:08 -0700 Subject: [PATCH 24/26] Update authState with OIDAuthorizationResponse --- GoogleSignIn/Sources/GIDGoogleUser.m | 8 ++++++++ GoogleSignIn/Sources/GIDGoogleUser_Private.h | 1 + GoogleSignIn/Sources/GIDSignIn.m | 1 + GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m | 2 ++ GoogleSignIn/Tests/Unit/GIDSignInTest.m | 4 ++++ 5 files changed, 16 insertions(+) diff --git a/GoogleSignIn/Sources/GIDGoogleUser.m b/GoogleSignIn/Sources/GIDGoogleUser.m index 749fc114..88abbdf6 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser.m +++ b/GoogleSignIn/Sources/GIDGoogleUser.m @@ -247,9 +247,17 @@ - (instancetype)initWithAuthState:(OIDAuthState *)authState } - (void)updateWithTokenResponse:(OIDTokenResponse *)tokenResponse + authorizationResponse:(OIDAuthorizationResponse *)authorizationResponse profileData:(nullable GIDProfileData *)profileData { @synchronized(self) { _profile = profileData; + + // We don't want to trigger the delegate before we update authState completely. So we unset the + // delegate before the first update. Also the order of updates is important because + // `updateWithAuthorizationResponse` would clears the last token reponse and refresh token. + self.authState.stateChangeDelegate = nil; + [self.authState updateWithAuthorizationResponse:authorizationResponse error:nil]; + self.authState.stateChangeDelegate = self; [self.authState updateWithTokenResponse:tokenResponse error:nil]; } } diff --git a/GoogleSignIn/Sources/GIDGoogleUser_Private.h b/GoogleSignIn/Sources/GIDGoogleUser_Private.h index bc4765a2..8bf9f7dc 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser_Private.h +++ b/GoogleSignIn/Sources/GIDGoogleUser_Private.h @@ -60,6 +60,7 @@ typedef void (^GIDGoogleUserCompletion)(GIDGoogleUser *_Nullable user, NSError * // Update the auth state and profile data. - (void)updateWithTokenResponse:(OIDTokenResponse *)tokenResponse + authorizationResponse:(OIDAuthorizationResponse *)authorizationResponse profileData:(nullable GIDProfileData *)profileData; @end diff --git a/GoogleSignIn/Sources/GIDSignIn.m b/GoogleSignIn/Sources/GIDSignIn.m index 77e23fec..c8504ac5 100644 --- a/GoogleSignIn/Sources/GIDSignIn.m +++ b/GoogleSignIn/Sources/GIDSignIn.m @@ -787,6 +787,7 @@ - (void)addSaveAuthCallback:(GIDAuthFlow *)authFlow { if (self->_currentOptions.addScopesFlow) { [self->_currentUser updateWithTokenResponse:authState.lastTokenResponse + authorizationResponse:authState.lastAuthorizationResponse profileData:handlerAuthFlow.profileData]; } else { GIDGoogleUser *user = [[GIDGoogleUser alloc] initWithAuthState:authState diff --git a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m index 4edb3ac9..396b94a8 100644 --- a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m +++ b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m @@ -144,6 +144,7 @@ - (void)testUpdateAuthState { GIDProfileData *updatedProfileData = [GIDProfileData testInstance]; [user updateWithTokenResponse:updatedAuthState.lastTokenResponse + authorizationResponse:updatedAuthState.lastAuthorizationResponse profileData:updatedProfileData]; XCTAssertEqualObjects(user.accessToken.tokenString, kNewAccessToken); @@ -173,6 +174,7 @@ - (void)testUpdateAuthState_tokensAreNotChanged { GIDToken *idTokenBeforeUpdate = user.idToken; [user updateWithTokenResponse:authState.lastTokenResponse + authorizationResponse:authState.lastAuthorizationResponse profileData:nil]; XCTAssertIdentical(user.accessToken, accessTokenBeforeUpdate); diff --git a/GoogleSignIn/Tests/Unit/GIDSignInTest.m b/GoogleSignIn/Tests/Unit/GIDSignInTest.m index af465b7a..d60c90a2 100644 --- a/GoogleSignIn/Tests/Unit/GIDSignInTest.m +++ b/GoogleSignIn/Tests/Unit/GIDSignInTest.m @@ -1346,14 +1346,17 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow // SaveAuthCallback __block OIDAuthState *authState; __block OIDTokenResponse *updatedTokenResponse; + __block OIDAuthorizationResponse *updatedAuthorizationResponse; __block GIDProfileData *profileData; if (keychainError) { _saveAuthorizationReturnValue = NO; } else { if (addScopesFlow) { + [[[_authState expect] andReturn:authResponse] lastAuthorizationResponse]; [[[_authState expect] andReturn:tokenResponse] lastTokenResponse]; [[_user expect] updateWithTokenResponse:SAVE_TO_ARG_BLOCK(updatedTokenResponse) + authorizationResponse:SAVE_TO_ARG_BLOCK(updatedAuthorizationResponse) profileData:SAVE_TO_ARG_BLOCK(profileData)]; } else { [[[_user stub] andReturn:_user] alloc]; @@ -1389,6 +1392,7 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow XCTAssertTrue(_keychainSaved, @"should save to keychain"); if (addScopesFlow) { XCTAssertNotNil(updatedTokenResponse); + XCTAssertNotNil(updatedAuthorizationResponse); } else { XCTAssertNotNil(authState); } From 6ee30738abc8adbb8b37eeb8956cc83e8097ef5a Mon Sep 17 00:00:00 2001 From: pinlu Date: Wed, 12 Oct 2022 14:27:23 -0700 Subject: [PATCH 25/26] Add unit test for GIDGoogleUser old encoding format --- GoogleSignIn/Sources/GIDAuthentication.h | 32 ++++++++++ GoogleSignIn/Sources/GIDAuthentication.m | 59 +++++++++++++++++++ GoogleSignIn/Sources/GIDGoogleUser.m | 35 +---------- GoogleSignIn/Sources/GIDGoogleUser_Private.h | 4 +- .../Tests/Unit/GIDGoogleUser+Testing.h | 9 +++ .../Tests/Unit/GIDGoogleUser+Testing.m | 31 ++++++++++ GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m | 19 ++++++ 7 files changed, 153 insertions(+), 36 deletions(-) create mode 100644 GoogleSignIn/Sources/GIDAuthentication.h create mode 100644 GoogleSignIn/Sources/GIDAuthentication.m diff --git a/GoogleSignIn/Sources/GIDAuthentication.h b/GoogleSignIn/Sources/GIDAuthentication.h new file mode 100644 index 00000000..e38ee151 --- /dev/null +++ b/GoogleSignIn/Sources/GIDAuthentication.h @@ -0,0 +1,32 @@ +/* + * 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 + +@class OIDAuthState; + +NS_ASSUME_NONNULL_BEGIN + +// Internal class for GIDGoogleUser NSCoding backward compatibility. +@interface GIDAuthentication : NSObject + +@property(nonatomic) OIDAuthState* authState; + +- (instancetype)initWithAuthState:(OIDAuthState *)authState; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleSignIn/Sources/GIDAuthentication.m b/GoogleSignIn/Sources/GIDAuthentication.m new file mode 100644 index 00000000..52496830 --- /dev/null +++ b/GoogleSignIn/Sources/GIDAuthentication.m @@ -0,0 +1,59 @@ +/* + * 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 "GoogleSignIn/Sources/GIDAuthentication.h" + +#ifdef SWIFT_PACKAGE +@import AppAuth; +#else +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +static NSString *const kAuthStateKey = @"authState"; + +@implementation GIDAuthentication + +- (instancetype)initWithAuthState:(OIDAuthState *)authState { + self = [super init]; + if (self) { + self.authState = authState; + } + return self; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)decoder { + self = [super init]; + if (self) { + self.authState = [decoder decodeObjectOfClass:[OIDAuthState class] forKey:kAuthStateKey]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)encoder { + [encoder encodeObject:self.authState forKey:kAuthStateKey]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleSignIn/Sources/GIDGoogleUser.m b/GoogleSignIn/Sources/GIDGoogleUser.m index 88abbdf6..649717af 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser.m +++ b/GoogleSignIn/Sources/GIDGoogleUser.m @@ -19,6 +19,7 @@ #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDConfiguration.h" #import "GoogleSignIn/Sources/GIDAppAuthFetcherAuthorizationWithEMMSupport.h" +#import "GoogleSignIn/Sources/GIDAuthentication.h" #import "GoogleSignIn/Sources/GIDEMMSupport.h" #import "GoogleSignIn/Sources/GIDProfileData_Private.h" #import "GoogleSignIn/Sources/GIDSignInPreferences.h" @@ -49,39 +50,6 @@ // Minimal time interval before expiration for the access token or it needs to be refreshed. static NSTimeInterval const kMinimalTimeToExpire = 60.0; -#pragma mark - GIDAuthentication - -// Internal class for GIDGoogleUser NSCoding backward compatibility. -@interface GIDAuthentication : NSObject - -@property(nonatomic) OIDAuthState* authState; - -@end - -@implementation GIDAuthentication - -#pragma mark - NSSecureCoding - -+ (BOOL)supportsSecureCoding { - return YES; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)decoder { - self = [super init]; - if (self) { - self.authState = [decoder decodeObjectOfClass:[OIDAuthState class] forKey:kAuthStateKey]; - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)encoder { - [encoder encodeObject:self.authState forKey:kAuthStateKey]; -} - -@end - -#pragma mark - GIDGoogleUser - @implementation GIDGoogleUser { GIDConfiguration *_cachedConfiguration; @@ -328,7 +296,6 @@ + (BOOL)supportsSecureCoding { return YES; } - - (nullable instancetype)initWithCoder:(NSCoder *)decoder { self = [super init]; if (self) { diff --git a/GoogleSignIn/Sources/GIDGoogleUser_Private.h b/GoogleSignIn/Sources/GIDGoogleUser_Private.h index 8bf9f7dc..94a019ae 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser_Private.h +++ b/GoogleSignIn/Sources/GIDGoogleUser_Private.h @@ -24,10 +24,10 @@ #import #endif -NS_ASSUME_NONNULL_BEGIN - @class OIDAuthState; +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); diff --git a/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.h b/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.h index 8aa6faa3..b94235db 100644 --- a/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.h +++ b/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.h @@ -23,3 +23,12 @@ - (NSUInteger)hash; @end + +// The old format GIDGoogleUser containing a GIDAuthentication. +// Note: remove this class when GIDGoogleUser no longer support old encoding. +@interface GIDGoogleUserOldFormat : GIDGoogleUser + +- (instancetype)initOldGIDGoogleUserWithAuthState:(OIDAuthState *)authState + profileData:(GIDProfileData *)profileData; + +@end diff --git a/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.m b/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.m index f45969de..87a36c8f 100644 --- a/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.m +++ b/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.m @@ -14,12 +14,19 @@ #import "GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.h" +#import "GoogleSignIn/Sources/GIDGoogleUser_Private.h" + +#import "GoogleSignIn/Sources/GIDAuthentication.h" #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDConfiguration.h" #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDToken.h" #import "GoogleSignIn/Tests/Unit/GIDConfiguration+Testing.h" #import "GoogleSignIn/Tests/Unit/GIDProfileData+Testing.h" +// Key constants used for encode and decode. +static NSString *const kProfileDataKey = @"profileData"; +static NSString *const kAuthentication = @"authentication"; + @implementation GIDGoogleUser (Testing) - (BOOL)isEqual:(id)object { @@ -48,3 +55,27 @@ - (NSUInteger)hash { } @end + +@implementation GIDGoogleUserOldFormat { + GIDAuthentication *_authentication; + GIDProfileData *_profile; +} + +- (instancetype)initOldGIDGoogleUserWithAuthState:(OIDAuthState *)authState + profileData:(GIDProfileData *)profileData { + self = [super initWithAuthState:authState profileData:profileData]; + if (self) { + _authentication = [[GIDAuthentication alloc] initWithAuthState:authState]; + _profile = profileData; + } + return self; +} + +#pragma mark - NSSecureCoding + +- (void)encodeWithCoder:(NSCoder *)encoder { + [encoder encodeObject:_profile forKey:kProfileDataKey]; + [encoder encodeObject:_authentication forKey:kAuthentication]; +} + +@end diff --git a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m index 396b94a8..f8205fc4 100644 --- a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m +++ b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m @@ -21,6 +21,7 @@ #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDToken.h" #import "GoogleSignIn/Sources/GIDGoogleUser_Private.h" +#import "GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.h" #import "GoogleSignIn/Tests/Unit/GIDProfileData+Testing.h" #import "GoogleSignIn/Tests/Unit/OIDAuthState+Testing.h" #import "GoogleSignIn/Tests/Unit/OIDAuthorizationRequest+Testing.h" @@ -132,6 +133,24 @@ - (void)testLegacyCoding { } #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST +// Test the old encoding format for backword compatability. +- (void)testOldFormatCoding { + if (@available(iOS 11, macOS 10.13, *)) { + OIDAuthState *authState = [OIDAuthState testInstance]; + GIDProfileData *profileDate = [GIDProfileData testInstance]; + GIDGoogleUserOldFormat *user = + [[GIDGoogleUserOldFormat alloc] initOldGIDGoogleUserWithAuthState:authState + profileData:profileDate]; + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:user + requiringSecureCoding:YES + error:nil]; + GIDGoogleUser *newUser = [NSKeyedUnarchiver unarchivedObjectOfClass:[GIDGoogleUser class] + fromData:data + error:nil]; + XCTAssertEqualObjects(user, newUser); + } +} + - (void)testUpdateAuthState { GIDGoogleUser *user = [self googleUserWithAccessTokenExpiresIn:kAccessTokenExpiresIn idTokenExpiresIn:kIDTokenExpiresIn]; From c065b293ab3cdc0ca615988304f3d1a8664150f3 Mon Sep 17 00:00:00 2001 From: pinlu Date: Thu, 13 Oct 2022 11:31:06 -0700 Subject: [PATCH 26/26] Improve testing class GIDGoogleUserOldFormat --- GoogleSignIn/Sources/GIDAuthentication.m | 4 ++-- GoogleSignIn/Sources/GIDGoogleUser.m | 3 ++- GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.h | 6 +++--- GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.m | 4 ++-- GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m | 5 ++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/GoogleSignIn/Sources/GIDAuthentication.m b/GoogleSignIn/Sources/GIDAuthentication.m index 52496830..d8a400f2 100644 --- a/GoogleSignIn/Sources/GIDAuthentication.m +++ b/GoogleSignIn/Sources/GIDAuthentication.m @@ -31,7 +31,7 @@ @implementation GIDAuthentication - (instancetype)initWithAuthState:(OIDAuthState *)authState { self = [super init]; if (self) { - self.authState = authState; + _authState = authState; } return self; } @@ -45,7 +45,7 @@ + (BOOL)supportsSecureCoding { - (nullable instancetype)initWithCoder:(NSCoder *)decoder { self = [super init]; if (self) { - self.authState = [decoder decodeObjectOfClass:[OIDAuthState class] forKey:kAuthStateKey]; + _authState = [decoder decodeObjectOfClass:[OIDAuthState class] forKey:kAuthStateKey]; } return self; } diff --git a/GoogleSignIn/Sources/GIDGoogleUser.m b/GoogleSignIn/Sources/GIDGoogleUser.m index 649717af..0c18a49e 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser.m +++ b/GoogleSignIn/Sources/GIDGoogleUser.m @@ -222,7 +222,8 @@ - (void)updateWithTokenResponse:(OIDTokenResponse *)tokenResponse // We don't want to trigger the delegate before we update authState completely. So we unset the // delegate before the first update. Also the order of updates is important because - // `updateWithAuthorizationResponse` would clears the last token reponse and refresh token. + // `updateWithAuthorizationResponse` would clear the last token reponse and refresh token. + // TODO: Rewrite authState update logic when the issue is addressed.(openid/AppAuth-iOS#728) self.authState.stateChangeDelegate = nil; [self.authState updateWithAuthorizationResponse:authorizationResponse error:nil]; self.authState.stateChangeDelegate = self; diff --git a/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.h b/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.h index b94235db..3f833030 100644 --- a/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.h +++ b/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.h @@ -24,11 +24,11 @@ @end -// The old format GIDGoogleUser containing a GIDAuthentication. +// The old format GIDGoogleUser contains a GIDAuthentication. // Note: remove this class when GIDGoogleUser no longer support old encoding. @interface GIDGoogleUserOldFormat : GIDGoogleUser -- (instancetype)initOldGIDGoogleUserWithAuthState:(OIDAuthState *)authState - profileData:(GIDProfileData *)profileData; +- (instancetype)initWithAuthState:(OIDAuthState *)authState + profileData:(GIDProfileData *)profileData; @end diff --git a/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.m b/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.m index 87a36c8f..3428c896 100644 --- a/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.m +++ b/GoogleSignIn/Tests/Unit/GIDGoogleUser+Testing.m @@ -61,8 +61,8 @@ @implementation GIDGoogleUserOldFormat { GIDProfileData *_profile; } -- (instancetype)initOldGIDGoogleUserWithAuthState:(OIDAuthState *)authState - profileData:(GIDProfileData *)profileData { +- (instancetype)initWithAuthState:(OIDAuthState *)authState + profileData:(GIDProfileData *)profileData { self = [super initWithAuthState:authState profileData:profileData]; if (self) { _authentication = [[GIDAuthentication alloc] initWithAuthState:authState]; diff --git a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m index f8205fc4..488d3f63 100644 --- a/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m +++ b/GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m @@ -138,9 +138,8 @@ - (void)testOldFormatCoding { if (@available(iOS 11, macOS 10.13, *)) { OIDAuthState *authState = [OIDAuthState testInstance]; GIDProfileData *profileDate = [GIDProfileData testInstance]; - GIDGoogleUserOldFormat *user = - [[GIDGoogleUserOldFormat alloc] initOldGIDGoogleUserWithAuthState:authState - profileData:profileDate]; + GIDGoogleUserOldFormat *user = [[GIDGoogleUserOldFormat alloc] initWithAuthState:authState + profileData:profileDate]; NSData *data = [NSKeyedArchiver archivedDataWithRootObject:user requiringSecureCoding:YES error:nil];