diff --git a/GoogleSignIn/Sources/GIDSignIn.m b/GoogleSignIn/Sources/GIDSignIn.m index 4da18b2c..ff4fcdae 100644 --- a/GoogleSignIn/Sources/GIDSignIn.m +++ b/GoogleSignIn/Sources/GIDSignIn.m @@ -367,6 +367,14 @@ - (void)addScopes:(NSArray *)scopes addScopesFlow:YES completion:completion]; + OIDAuthorizationRequest *lastAuthorizationRequest = + self.currentUser.authState.lastAuthorizationResponse.request; + NSString *lastTokenClaimsAsJSON = + lastAuthorizationRequest.additionalParameters[kTokenClaimsParameter]; + if (lastTokenClaimsAsJSON) { + options.tokenClaimsAsJSON = lastTokenClaimsAsJSON; + } + NSSet *requestedScopes = [NSSet setWithArray:scopes]; NSMutableSet *grantedScopes = [NSMutableSet setWithArray:self.currentUser.grantedScopes]; @@ -499,6 +507,14 @@ - (void)addScopes:(NSArray *)scopes addScopesFlow:YES completion:completion]; + OIDAuthorizationRequest *lastAuthorizationRequest = + self.currentUser.authState.lastAuthorizationResponse.request; + NSString *lastTokenClaimsAsJSON = + lastAuthorizationRequest.additionalParameters[kTokenClaimsParameter]; + if (lastTokenClaimsAsJSON) { + options.tokenClaimsAsJSON = lastTokenClaimsAsJSON; + } + NSSet *requestedScopes = [NSSet setWithArray:scopes]; NSMutableSet *grantedScopes = [NSMutableSet setWithArray:self.currentUser.grantedScopes]; @@ -739,20 +755,23 @@ - (void)signInWithOptions:(GIDSignInInternalOptions *)options { } }]; } else { - NSError *claimsError; - - // If tokenClaims are invalid or JSON serialization fails, return with an error. - options.tokenClaimsAsJSON = [_tokenClaimsInternalOptions - validatedJSONStringForClaims:options.tokenClaims - error:&claimsError]; - if (claimsError) { - if (options.completion) { - self->_currentOptions = nil; - dispatch_async(dispatch_get_main_queue(), ^{ - options.completion(nil, claimsError); - }); + // Only serialize tokenClaims if options.tokenClaimsAsJSON isn't already set. + if (!options.tokenClaimsAsJSON) { + NSError *claimsError; + + // If tokenClaims are invalid or JSON serialization fails, return with an error. + options.tokenClaimsAsJSON = [_tokenClaimsInternalOptions + validatedJSONStringForClaims:options.tokenClaims + error:&claimsError]; + if (claimsError) { + if (options.completion) { + _currentOptions = nil; + dispatch_async(dispatch_get_main_queue(), ^{ + options.completion(nil, claimsError); + }); + } + return; } - return; } [self authenticateWithOptions:options]; } diff --git a/GoogleSignIn/Tests/Unit/GIDSignInTest.m b/GoogleSignIn/Tests/Unit/GIDSignInTest.m index 17453785..55f239d0 100644 --- a/GoogleSignIn/Tests/Unit/GIDSignInTest.m +++ b/GoogleSignIn/Tests/Unit/GIDSignInTest.m @@ -644,6 +644,7 @@ - (void)testOAuthLogin { authError:nil tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO restoredSignIn:NO oldAccessToken:NO @@ -661,6 +662,7 @@ - (void)testOAuthLogin_RestoredSignIn { authError:nil tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO restoredSignIn:YES oldAccessToken:NO @@ -678,6 +680,7 @@ - (void)testOAuthLogin_RestoredSignInOldAccessToken { authError:nil tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO restoredSignIn:YES oldAccessToken:YES @@ -697,6 +700,7 @@ - (void)testOAuthLogin_AdditionalScopes { authError:nil tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO tokenClaimsError:NO restoredSignIn:NO @@ -714,6 +718,7 @@ - (void)testOAuthLogin_AdditionalScopes { authError:nil tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO tokenClaimsError:NO restoredSignIn:NO @@ -731,6 +736,7 @@ - (void)testOAuthLogin_AdditionalScopes { authError:nil tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO tokenClaimsError:NO restoredSignIn:NO @@ -758,6 +764,7 @@ - (void)testOAuthLogin_WithTokenClaims_FormatsParametersCorrectly { authError:nil tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO tokenClaimsError:NO restoredSignIn:NO @@ -776,6 +783,7 @@ - (void)testOAuthLogin_WithTokenClaims_FormatsParametersCorrectly { authError:nil tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO tokenClaimsError:NO restoredSignIn:NO @@ -803,6 +811,7 @@ - (void)testOAuthLogin_WithTokenClaims_ReturnsIdTokenWithCorrectClaims { authError:nil tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO tokenClaimsError:NO restoredSignIn:NO @@ -838,6 +847,7 @@ - (void)testAddScopes { authError:nil tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO restoredSignIn:YES oldAccessToken:NO @@ -856,11 +866,13 @@ - (void)testAddScopes { OCMStub([_user configuration]).andReturn(configuration); OCMStub([_user profile]).andReturn(profile); OCMStub([_user grantedScopes]).andReturn(@[kGrantedScope]); + OCMStub([_user authState]).andReturn(_authState); [self OAuthLoginWithAddScopesFlow:YES authError:nil tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO restoredSignIn:NO oldAccessToken:NO @@ -888,6 +900,76 @@ - (void)testAddScopes { [profile stopMocking]; } +- (void)testAddScopes_WithPreviouslyRequestedClaims { + GIDTokenClaim *authTimeClaim = [GIDTokenClaim authTimeClaim]; + // Restore the previous sign-in account. This is the preparation for adding scopes. + OCMStub( + [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef] + ).andDo(^(NSInvocation *invocation) { + self->_keychainSaved = self->_saveAuthorizationReturnValue; + }); + [self OAuthLoginWithAddScopesFlow:NO + authError:nil + tokenError:nil + emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO + keychainError:NO + tokenClaimsError:NO + restoredSignIn:NO + oldAccessToken:NO + modalCancel:NO + useAdditionalScopes:NO + additionalScopes:nil + manualNonce:nil + tokenClaims:[NSSet setWithObject:authTimeClaim]]; + + XCTAssertNotNil(_signIn.currentUser); + + id profile = OCMStrictClassMock([GIDProfileData class]); + OCMStub([profile email]).andReturn(kUserEmail); + + GIDConfiguration *configuration = [[GIDConfiguration alloc] initWithClientID:kClientId + serverClientID:nil + hostedDomain:nil + openIDRealm:kOpenIDRealm]; + OCMStub([_user configuration]).andReturn(configuration); + OCMStub([_user profile]).andReturn(profile); + OCMStub([_user grantedScopes]).andReturn(@[kGrantedScope]); + OCMStub([_user authState]).andReturn(_authState); + + [self OAuthLoginWithAddScopesFlow:YES + authError:nil + tokenError:nil + emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:YES + keychainError:NO + restoredSignIn:NO + oldAccessToken:NO + modalCancel:NO]; + + NSArray *grantedScopes; + NSString *grantedScopeString = _savedAuthorizationRequest.scope; + + if (grantedScopeString) { + NSCharacterSet *whiteSpaceChars = [NSCharacterSet whitespaceCharacterSet]; + grantedScopeString = + [grantedScopeString stringByTrimmingCharactersInSet:whiteSpaceChars]; + NSMutableArray *parsedScopes = + [[grantedScopeString componentsSeparatedByString:@" "] mutableCopy]; + [parsedScopes removeObject:@""]; + grantedScopes = [parsedScopes copy]; + } + + NSArray *expectedScopes = @[kNewScope, kGrantedScope]; + XCTAssertEqualObjects(grantedScopes, expectedScopes); + XCTAssertEqualObjects(_savedAuthorizationRequest.additionalParameters[@"claims"], + kNonEssentialAuthTimeClaimsJsonString, + @"Claims JSON should be correctly formatted"); + + [_user verify]; + [profile verify]; +} + - (void)testOpenIDRealm { _signIn.configuration = [[GIDConfiguration alloc] initWithClientID:kClientId serverClientID:nil @@ -904,6 +986,7 @@ - (void)testOpenIDRealm { authError:nil tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO restoredSignIn:NO oldAccessToken:NO @@ -931,6 +1014,7 @@ - (void)testManualNonce { authError:nil tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO tokenClaimsError:NO restoredSignIn:NO @@ -959,6 +1043,7 @@ - (void)testOAuthLogin_LoginHint { authError:nil tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO restoredSignIn:NO oldAccessToken:NO @@ -984,6 +1069,7 @@ - (void)testOAuthLogin_HostedDomain { authError:nil tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO restoredSignIn:NO oldAccessToken:NO @@ -998,6 +1084,7 @@ - (void)testOAuthLogin_ConsentCanceled { authError:@"access_denied" tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO restoredSignIn:NO oldAccessToken:NO @@ -1012,6 +1099,7 @@ - (void)testOAuthLogin_ModalCanceled { authError:nil tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO restoredSignIn:NO oldAccessToken:NO @@ -1036,6 +1124,7 @@ - (void)testOAuthLogin_KeychainError { authError:nil tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:YES restoredSignIn:NO oldAccessToken:NO @@ -1056,6 +1145,7 @@ - (void)testOAuthLogin_TokenClaims_FailsWithError { authError:nil tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO tokenClaimsError:YES restoredSignIn:NO @@ -1093,6 +1183,7 @@ - (void)testSignOut { authError:nil tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO restoredSignIn:YES oldAccessToken:NO @@ -1339,6 +1430,7 @@ - (void)testEmmSupportRequestParameters { authError:nil tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO restoredSignIn:NO oldAccessToken:NO @@ -1390,6 +1482,7 @@ - (void)testEmmPasscodeInfo { authError:nil tokenError:nil emmPasscodeInfoRequired:YES + tokenClaimsAsJSONRequired:NO keychainError:NO restoredSignIn:NO oldAccessToken:NO @@ -1421,6 +1514,7 @@ - (void)testAuthEndpointEMMError { authError:callbackParams[@"error"] tokenError:nil emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO restoredSignIn:NO oldAccessToken:NO @@ -1458,6 +1552,7 @@ - (void)testTokenEndpointEMMError { authError:nil tokenError:emmError emmPasscodeInfoRequired:NO + tokenClaimsAsJSONRequired:NO keychainError:NO restoredSignIn:NO oldAccessToken:NO @@ -1542,6 +1637,7 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow authError:(NSString *)authError tokenError:(NSError *)tokenError emmPasscodeInfoRequired:(BOOL)emmPasscodeInfoRequired + tokenClaimsAsJSONRequired:(BOOL)tokenClaimsAsJSONRequired keychainError:(BOOL)keychainError restoredSignIn:(BOOL)restoredSignIn oldAccessToken:(BOOL)oldAccessToken @@ -1550,6 +1646,7 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow authError:authError tokenError:tokenError emmPasscodeInfoRequired:emmPasscodeInfoRequired + tokenClaimsAsJSONRequired:tokenClaimsAsJSONRequired keychainError:keychainError tokenClaimsError:NO restoredSignIn:restoredSignIn @@ -1566,6 +1663,7 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow authError:(NSString *)authError tokenError:(NSError *)tokenError emmPasscodeInfoRequired:(BOOL)emmPasscodeInfoRequired + tokenClaimsAsJSONRequired:(BOOL)tokenClaimsAsJSONRequired keychainError:(BOOL)keychainError tokenClaimsError:(BOOL)tokenClaimsError restoredSignIn:(BOOL)restoredSignIn @@ -1582,8 +1680,9 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow [[[_authState expect] andReturnValue:[NSNumber numberWithBool:isAuthorized]] isAuthorized]; } - NSDictionary *additionalParameters = emmPasscodeInfoRequired ? - @{ @"emm_passcode_info_required" : @"1" } : nil; + NSDictionary *additionalParameters = + [self additionalParametersWithEMMPasscodeInfoRequired:emmPasscodeInfoRequired + tokenClaimsAsJSONRequired:tokenClaimsAsJSONRequired]; OIDAuthorizationResponse *authResponse = [OIDAuthorizationResponse testInstanceWithAdditionalParameters:additionalParameters nonce:nonce @@ -1646,6 +1745,7 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow self->_authError = error; }; if (addScopesFlow) { + [[[_authState expect] andReturn:authResponse] lastAuthorizationResponse]; [_signIn addScopes:@[kNewScope] #if TARGET_OS_IOS || TARGET_OS_MACCATALYST presentingViewController:_presentingViewController @@ -1846,4 +1946,22 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow } } +#pragma mark - Private Helpers + +- (NSDictionary *) + additionalParametersWithEMMPasscodeInfoRequired:(BOOL)emmPasscodeInfoRequired + tokenClaimsAsJSONRequired:(BOOL)tokenClaimsAsJSONRequired { + NSMutableDictionary *additionalParameters = + [NSMutableDictionary dictionary]; + + if (emmPasscodeInfoRequired) { + additionalParameters[@"emm_passcode_info_required"] = @"1"; + } + if (tokenClaimsAsJSONRequired) { + additionalParameters[@"claims"] = kNonEssentialAuthTimeClaimsJsonString; + } + + return [additionalParameters copy]; +} + @end diff --git a/GoogleSignIn/Tests/Unit/OIDAuthorizationRequest+Testing.h b/GoogleSignIn/Tests/Unit/OIDAuthorizationRequest+Testing.h index a70d5e80..d3e691c5 100644 --- a/GoogleSignIn/Tests/Unit/OIDAuthorizationRequest+Testing.h +++ b/GoogleSignIn/Tests/Unit/OIDAuthorizationRequest+Testing.h @@ -29,6 +29,6 @@ extern NSString * _Nonnull const OIDAuthorizationRequestTestingCodeVerifier; + (instancetype _Nonnull)testInstance; -+ (instancetype _Nonnull)testInstanceWithNonce:(nullable NSString *)nonce; - ++ (instancetype _Nonnull)testInstanceWithNonce:(nullable NSString *)nonce + additionalParameters:(nullable NSDictionary *)additionalParameters; @end diff --git a/GoogleSignIn/Tests/Unit/OIDAuthorizationRequest+Testing.m b/GoogleSignIn/Tests/Unit/OIDAuthorizationRequest+Testing.m index a8dd0b81..a45dac90 100644 --- a/GoogleSignIn/Tests/Unit/OIDAuthorizationRequest+Testing.m +++ b/GoogleSignIn/Tests/Unit/OIDAuthorizationRequest+Testing.m @@ -32,10 +32,12 @@ @implementation OIDAuthorizationRequest (Testing) + (instancetype)testInstance { - return [self testInstanceWithNonce:nil]; + return [self testInstanceWithNonce:nil additionalParameters:nil]; } -+ (instancetype)testInstanceWithNonce:(nullable NSString *)nonce { ++ (instancetype)testInstanceWithNonce:(nullable NSString *)nonce + additionalParameters: + (nullable NSDictionary *)additionalParameters { return [[OIDAuthorizationRequest alloc] initWithConfiguration:[OIDServiceConfiguration testInstance] clientId:OIDAuthorizationRequestTestingClientID @@ -44,7 +46,7 @@ + (instancetype)testInstanceWithNonce:(nullable NSString *)nonce { redirectURL:[NSURL URLWithString:@"http://test.com"] responseType:OIDResponseTypeCode nonce:nonce - additionalParameters:nil]; + additionalParameters:additionalParameters]; } @end diff --git a/GoogleSignIn/Tests/Unit/OIDAuthorizationResponse+Testing.m b/GoogleSignIn/Tests/Unit/OIDAuthorizationResponse+Testing.m index cc4f1211..5404d411 100644 --- a/GoogleSignIn/Tests/Unit/OIDAuthorizationResponse+Testing.m +++ b/GoogleSignIn/Tests/Unit/OIDAuthorizationResponse+Testing.m @@ -46,7 +46,10 @@ + (instancetype)testInstanceWithAdditionalParameters: [parameters addEntriesFromDictionary:additionalParameters]; } } - return [[OIDAuthorizationResponse alloc] initWithRequest:[OIDAuthorizationRequest testInstanceWithNonce:nonce] + OIDAuthorizationRequest *request = + [OIDAuthorizationRequest testInstanceWithNonce:nonce + additionalParameters:additionalParameters]; + return [[OIDAuthorizationResponse alloc] initWithRequest:request parameters:parameters]; }