diff --git a/GoogleSignIn/Sources/GIDAuthorizationFlowProcessor/Implementations/GIDAuthorizationFlowProcessor.m b/GoogleSignIn/Sources/GIDAuthorizationFlowProcessor/Implementations/GIDAuthorizationFlowProcessor.m index b8b62d2d..8ce9ce60 100644 --- a/GoogleSignIn/Sources/GIDAuthorizationFlowProcessor/Implementations/GIDAuthorizationFlowProcessor.m +++ b/GoogleSignIn/Sources/GIDAuthorizationFlowProcessor/Implementations/GIDAuthorizationFlowProcessor.m @@ -16,12 +16,8 @@ #import "GoogleSignIn/Sources/GIDAuthorizationFlowProcessor/Implementations/GIDAuthorizationFlowProcessor.h" -#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDConfiguration.h" - -#import "GoogleSignIn/Sources/GIDEMMSupport.h" -#import "GoogleSignIn/Sources/GIDSignInCallbackSchemes.h" +#import "GoogleSignIn/Sources/GIDAuthorizationUtil.h" #import "GoogleSignIn/Sources/GIDSignInInternalOptions.h" -#import "GoogleSignIn/Sources/GIDSignInPreferences.h" #ifdef SWIFT_PACKAGE @import AppAuth; @@ -31,20 +27,10 @@ NS_ASSUME_NONNULL_BEGIN -// Parameters for the auth and token exchange endpoints. -static NSString *const kAudienceParameter = @"audience"; - -static NSString *const kIncludeGrantedScopesParameter = @"include_granted_scopes"; -static NSString *const kLoginHintParameter = @"login_hint"; -static NSString *const kHostedDomainParameter = @"hd"; - @interface GIDAuthorizationFlowProcessor () /// AppAuth external user-agent session state. -@property(nonatomic, nullable)id currentAuthorizationFlow; - -/// AppAuth configuration object. -@property(nonatomic)OIDServiceConfiguration *appAuthConfiguration; +@property(nonatomic, nullable) id currentAuthorizationFlow; @end @@ -60,47 +46,9 @@ - (void)startWithOptions:(GIDSignInInternalOptions *)options emmSupport:(nullable NSString *)emmSupport completion:(void (^)(OIDAuthorizationResponse *_Nullable authorizationResponse, NSError *_Nullable error))completion { - GIDSignInCallbackSchemes *schemes = - [[GIDSignInCallbackSchemes alloc] initWithClientIdentifier:options.configuration.clientID]; - NSString *urlString = [NSString stringWithFormat:@"%@:%@", - [schemes clientIdentifierScheme], kBrowserCallbackPath]; - NSURL *redirectURL = [NSURL URLWithString:urlString]; - - NSMutableDictionary *additionalParameters = [@{} mutableCopy]; - additionalParameters[kIncludeGrantedScopesParameter] = @"true"; - if (options.configuration.serverClientID) { - additionalParameters[kAudienceParameter] = options.configuration.serverClientID; - } - if (options.loginHint) { - additionalParameters[kLoginHintParameter] = options.loginHint; - } - if (options.configuration.hostedDomain) { - additionalParameters[kHostedDomainParameter] = options.configuration.hostedDomain; - } - -#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST - [additionalParameters addEntriesFromDictionary: - [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 - additionalParameters[kSDKVersionLoggingParameter] = GIDVersion(); - additionalParameters[kEnvironmentLoggingParameter] = GIDEnvironment(); - - NSURL *authorizationEndpointURL = [GIDSignInPreferences authorizationEndpointURL]; - NSURL *tokenEndpointURL = [GIDSignInPreferences tokenEndpointURL]; - OIDServiceConfiguration *appAuthConfiguration = - [[OIDServiceConfiguration alloc] initWithAuthorizationEndpoint:authorizationEndpointURL - tokenEndpoint:tokenEndpointURL]; OIDAuthorizationRequest *request = - [[OIDAuthorizationRequest alloc] initWithConfiguration:appAuthConfiguration - clientId:options.configuration.clientID - scopes:options.scopes - redirectURL:redirectURL - responseType:OIDResponseTypeCode - additionalParameters:additionalParameters]; + [GIDAuthorizationUtil authorizationRequestWithOptions:options + emmSupport:emmSupport]; _currentAuthorizationFlow = [OIDAuthorizationService presentAuthorizationRequest:request diff --git a/GoogleSignIn/Sources/GIDAuthorizationUtil.h b/GoogleSignIn/Sources/GIDAuthorizationUtil.h new file mode 100644 index 00000000..a93ff3d7 --- /dev/null +++ b/GoogleSignIn/Sources/GIDAuthorizationUtil.h @@ -0,0 +1,50 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class OIDAuthorizationRequest; +@class GIDSignInInternalOptions; + +NS_ASSUME_NONNULL_BEGIN + +/// The util class for authorization process. +@interface GIDAuthorizationUtil : NSObject + +/// Creates the request to AppAuth to start the authorization flow. +/// +/// @param options The `GIDSignInInternalOptions` object to provide serverClientID, hostedDomain, +/// clientID, scopes, loginHint and extraParams. +/// @param emmSupport The EMM support info string. ++ (OIDAuthorizationRequest *) + authorizationRequestWithOptions:(GIDSignInInternalOptions *)options + emmSupport:(nullable NSString *)emmSupport; + +/// Unions granted scopes with new scopes or returns an error if the new scopes are the subset of +/// the granted scopes. +/// +/// @param scopes The existing scopes. +/// @param newScopes The new scopes to add. +/// @param error The reference to the error. +/// @return The array of all scopes or nil if there is an error. ++ (nullable NSArray *) + resolvedScopesFromGrantedScoped:(NSArray *)scopes + withNewScopes:(NSArray *)newScopes + error:(NSError * __autoreleasing *)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleSignIn/Sources/GIDAuthorizationUtil.m b/GoogleSignIn/Sources/GIDAuthorizationUtil.m new file mode 100644 index 00000000..fd14632f --- /dev/null +++ b/GoogleSignIn/Sources/GIDAuthorizationUtil.m @@ -0,0 +1,106 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "GoogleSignIn/Sources/GIDAuthorizationUtil.h" + +#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDConfiguration.h" + +#import "GoogleSignIn/Sources/GIDEMMSupport.h" +#import "GoogleSignIn/Sources/GIDSignInCallbackSchemes.h" +#import "GoogleSignIn/Sources/GIDSignInInternalOptions.h" +#import "GoogleSignIn/Sources/GIDSignInPreferences.h" + +#ifdef SWIFT_PACKAGE +@import AppAuth; +#else +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +@implementation GIDAuthorizationUtil + ++ (OIDAuthorizationRequest *) + authorizationRequestWithOptions:(GIDSignInInternalOptions *)options + emmSupport:(nullable NSString *)emmSupport { + GIDSignInCallbackSchemes *schemes = + [[GIDSignInCallbackSchemes alloc] initWithClientIdentifier:options.configuration.clientID]; + NSString *urlString = [NSString stringWithFormat:@"%@:%@", + [schemes clientIdentifierScheme], kBrowserCallbackPath]; + NSURL *redirectURL = [NSURL URLWithString:urlString]; + + NSMutableDictionary *additionalParameters = [@{} mutableCopy]; + additionalParameters[kIncludeGrantedScopesParameter] = @"true"; + if (options.configuration.serverClientID) { + additionalParameters[kAudienceParameter] = options.configuration.serverClientID; + } + if (options.loginHint) { + additionalParameters[kLoginHintParameter] = options.loginHint; + } + if (options.configuration.hostedDomain) { + additionalParameters[kHostedDomainParameter] = options.configuration.hostedDomain; + } + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + [additionalParameters addEntriesFromDictionary: + [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 + additionalParameters[kSDKVersionLoggingParameter] = GIDVersion(); + additionalParameters[kEnvironmentLoggingParameter] = GIDEnvironment(); + + NSURL *authorizationEndpointURL = [GIDSignInPreferences authorizationEndpointURL]; + NSURL *tokenEndpointURL = [GIDSignInPreferences tokenEndpointURL]; + OIDServiceConfiguration *appAuthConfiguration = + [[OIDServiceConfiguration alloc] initWithAuthorizationEndpoint:authorizationEndpointURL + tokenEndpoint:tokenEndpointURL]; + OIDAuthorizationRequest *request = + [[OIDAuthorizationRequest alloc] initWithConfiguration:appAuthConfiguration + clientId:options.configuration.clientID + scopes:options.scopes + redirectURL:redirectURL + responseType:OIDResponseTypeCode + additionalParameters:additionalParameters]; + + return request; +} + ++ (nullable NSArray *) + resolvedScopesFromGrantedScoped:(NSArray *)scopes + withNewScopes:(NSArray *)newScopes + error:(NSError * __autoreleasing *)error { + NSMutableSet *grantedScopes = [NSMutableSet setWithArray:scopes]; + NSSet *requestedScopes = [NSSet setWithArray:newScopes]; + + if ([requestedScopes isSubsetOfSet:grantedScopes]) { + // All requested scopes have already been granted, generate an error. + *error = [NSError errorWithDomain:kGIDSignInErrorDomain + code:kGIDSignInErrorCodeScopesAlreadyGranted + userInfo:nil]; + return nil; + } + + // Use the union of granted and requested scopes. + [grantedScopes unionSet:requestedScopes]; + return [grantedScopes allObjects]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleSignIn/Sources/GIDGoogleUser.m b/GoogleSignIn/Sources/GIDGoogleUser.m index 828ac5b6..b113c266 100644 --- a/GoogleSignIn/Sources/GIDGoogleUser.m +++ b/GoogleSignIn/Sources/GIDGoogleUser.m @@ -42,8 +42,6 @@ static NSString *const kProfileDataKey = @"profileData"; static NSString *const kAuthStateKey = @"authState"; -// Parameters for the token exchange endpoint. -static NSString *const kAudienceParameter = @"audience"; static NSString *const kOpenIDRealmParameter = @"openid.realm"; // Additional parameter names for EMM. diff --git a/GoogleSignIn/Sources/GIDSignIn.m b/GoogleSignIn/Sources/GIDSignIn.m index 0a84fce8..56c43a85 100644 --- a/GoogleSignIn/Sources/GIDSignIn.m +++ b/GoogleSignIn/Sources/GIDSignIn.m @@ -23,6 +23,7 @@ #import "GoogleSignIn/Sources/GIDAuthorizationFlowProcessor/API/GIDAuthorizationFlowProcessor.h" #import "GoogleSignIn/Sources/GIDAuthorizationFlowProcessor/Implementations/GIDAuthorizationFlowProcessor.h" +#import "GoogleSignIn/Sources/GIDAuthorizationUtil.h" #import "GoogleSignIn/Sources/GIDHTTPFetcher/API/GIDHTTPFetcher.h" #import "GoogleSignIn/Sources/GIDHTTPFetcher/Implementations/GIDHTTPFetcher.h" #import "GoogleSignIn/Sources/GIDEMMSupport.h" @@ -75,6 +76,9 @@ NS_ASSUME_NONNULL_BEGIN +// The EMM support version +NSString *const kEMMVersion = @"1"; + // The name of the query parameter used for logging the restart of auth from EMM callback. static NSString *const kEMMRestartAuthParameter = @"emmres"; @@ -84,9 +88,6 @@ // Expected path for EMM callback. static NSString *const kEMMCallbackPath = @"/emmcallback"; -// The EMM support version -static NSString *const kEMMVersion = @"1"; - // The error code for Google Identity. NSErrorDomain const kGIDSignInErrorDomain = @"com.google.GIDSignIn"; @@ -111,8 +112,6 @@ // The delay before the new sign-in flow can be presented after the existing one is cancelled. static const NSTimeInterval kPresentationDelayAfterCancel = 1.0; -// Parameters for the auth and token exchange endpoints. -static NSString *const kAudienceParameter = @"audience"; // See b/11669751 . static NSString *const kOpenIDRealmParameter = @"openid.realm"; @@ -260,24 +259,12 @@ - (void)signInWithPresentingViewController:(UIViewController *)presentingViewCon - (void)addScopes:(NSArray *)scopes presentingViewController:(UIViewController *)presentingViewController completion:(nullable GIDSignInCompletion)completion { - GIDConfiguration *configuration = self.currentUser.configuration; - GIDSignInInternalOptions *options = - [GIDSignInInternalOptions defaultOptionsWithConfiguration:configuration - presentingViewController:presentingViewController - loginHint:self.currentUser.profile.email - addScopesFlow:YES - completion:completion]; - - NSSet *requestedScopes = [NSSet setWithArray:scopes]; - NSMutableSet *grantedScopes = - [NSMutableSet setWithArray:self.currentUser.grantedScopes]; - - // Check to see if all requested scopes have already been granted. - if ([requestedScopes isSubsetOfSet:grantedScopes]) { - // All requested scopes have already been granted, notify callback of failure. - NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain - code:kGIDSignInErrorCodeScopesAlreadyGranted - userInfo:nil]; + NSError *error; + NSArray *allScopes = + [GIDAuthorizationUtil resolvedScopesFromGrantedScoped:self.currentUser.grantedScopes + withNewScopes:scopes + error:&error]; + if (error) { if (completion) { dispatch_async(dispatch_get_main_queue(), ^{ completion(nil, error); @@ -285,10 +272,15 @@ - (void)addScopes:(NSArray *)scopes } return; } - - // Use the union of granted and requested scopes. - [grantedScopes unionSet:requestedScopes]; - options.scopes = [grantedScopes allObjects]; + + GIDConfiguration *configuration = self.currentUser.configuration; + GIDSignInInternalOptions *options = + [GIDSignInInternalOptions defaultOptionsWithConfiguration:configuration + presentingViewController:presentingViewController + loginHint:self.currentUser.profile.email + addScopesFlow:YES + completion:completion]; + options.scopes = allScopes; [self signInWithOptions:options]; } diff --git a/GoogleSignIn/Sources/GIDSignInPreferences.h b/GoogleSignIn/Sources/GIDSignInPreferences.h index f7f0a637..3304f9df 100644 --- a/GoogleSignIn/Sources/GIDSignInPreferences.h +++ b/GoogleSignIn/Sources/GIDSignInPreferences.h @@ -24,6 +24,15 @@ extern NSString *const kSDKVersionLoggingParameter; /// The name of the query parameter used for logging the Apple execution environment. extern NSString *const kEnvironmentLoggingParameter; +/// The name of the query parameter for the token exchange endpoint. +extern NSString *const kAudienceParameter; + +extern NSString *const kIncludeGrantedScopesParameter; + +extern NSString *const kLoginHintParameter; + +extern NSString *const kHostedDomainParameter; + /// Expected path in the URL scheme to be handled. extern NSString *const kBrowserCallbackPath; diff --git a/GoogleSignIn/Sources/GIDSignInPreferences.m b/GoogleSignIn/Sources/GIDSignInPreferences.m index 366f3bbb..6696b491 100644 --- a/GoogleSignIn/Sources/GIDSignInPreferences.m +++ b/GoogleSignIn/Sources/GIDSignInPreferences.m @@ -16,8 +16,14 @@ NS_ASSUME_NONNULL_BEGIN +/// Parameters for the auth and token exchange endpoints. +NSString *const kAudienceParameter = @"audience"; +NSString *const kIncludeGrantedScopesParameter = @"include_granted_scopes"; +NSString *const kLoginHintParameter = @"login_hint"; +NSString *const kHostedDomainParameter = @"hd"; NSString *const kSDKVersionLoggingParameter = @"gpsdk"; NSString *const kEnvironmentLoggingParameter = @"gidenv"; + NSString *const kBrowserCallbackPath = @"/oauth2callback"; static NSString *const kLSOServer = @"accounts.google.com"; diff --git a/GoogleSignIn/Sources/GIDSignIn_Private.h b/GoogleSignIn/Sources/GIDSignIn_Private.h index 35a45c93..234d7705 100644 --- a/GoogleSignIn/Sources/GIDSignIn_Private.h +++ b/GoogleSignIn/Sources/GIDSignIn_Private.h @@ -24,8 +24,6 @@ #import #endif -NS_ASSUME_NONNULL_BEGIN - @class GIDGoogleUser; @class GIDSignInInternalOptions; @@ -34,6 +32,11 @@ NS_ASSUME_NONNULL_BEGIN @protocol GIDKeychainHandler; @protocol GIDProfileDataFetcher; +NS_ASSUME_NONNULL_BEGIN + +/// The EMM support version. +extern NSString *const kEMMVersion; + /// Represents a completion block that takes a `GIDSignInResult` on success or an error if the /// operation was unsuccessful. typedef void (^GIDSignInCompletion)(GIDSignInResult *_Nullable signInResult, diff --git a/GoogleSignIn/Tests/Unit/GIDAuthorizationUtilTest.m b/GoogleSignIn/Tests/Unit/GIDAuthorizationUtilTest.m new file mode 100644 index 00000000..b87770c2 --- /dev/null +++ b/GoogleSignIn/Tests/Unit/GIDAuthorizationUtilTest.m @@ -0,0 +1,205 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#import + +#import "GoogleSignIn/Sources/GIDAuthorizationUtil.h" + +#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDConfiguration.h" + +#import "GoogleSignIn/Sources/GIDSignInInternalOptions.h" +#import "GoogleSignIn/Sources/GIDSignInPreferences.h" +#import "GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h" + +#ifdef SWIFT_PACKAGE +@import AppAuth; +#else +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +static NSString * const kClientId = @"FakeClientID"; +static NSString * const kUserEmail = @"FakeUserEmail"; +static NSString * const kServerClientId = @"FakeServerClientID"; + +static NSString * const kScopeBirthday = @"birthday"; +static NSString * const kScopeEmail = @"email"; +static NSString * const kScopeProfile = @"profile"; + +@interface GIDAuthorizationUtilTest : XCTestCase + +@end + +@implementation GIDAuthorizationUtilTest { + GIDConfiguration *_configuration; +} + +- (void)setUp { + [super setUp]; + _configuration = [[GIDConfiguration alloc] initWithClientID:kClientId + serverClientID:kServerClientId + hostedDomain:kHostedDomain + openIDRealm:nil]; +} + +- (void)testCreateAuthorizationRequest_signInFlow { + GIDSignInInternalOptions *options = + [GIDSignInInternalOptions defaultOptionsWithConfiguration:_configuration +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + presentingViewController:nil +#elif TARGET_OS_OSX + presentingWindow:nil +#endif // TARGET_OS_OSX + loginHint:kUserEmail + addScopesFlow:NO + completion:nil]; + OIDAuthorizationRequest *request = + [GIDAuthorizationUtil authorizationRequestWithOptions:options + emmSupport:nil]; + + NSDictionary *params = request.additionalParameters; + XCTAssertEqualObjects(params[kIncludeGrantedScopesParameter], @"true"); + XCTAssertEqualObjects(params[kSDKVersionLoggingParameter], GIDVersion()); + XCTAssertEqualObjects(params[kEnvironmentLoggingParameter], GIDEnvironment()); + XCTAssertEqualObjects(params[kLoginHintParameter], kUserEmail, @"login hint should match"); + XCTAssertEqualObjects(params[kHostedDomainParameter], kHostedDomain, + @"hosted domain should match"); + XCTAssertEqualObjects(params[kAudienceParameter], kServerClientId, @"client ID should match"); + + NSArray *defaultScopes = @[kScopeEmail, kScopeProfile]; + NSString *expectedScopeString = [defaultScopes componentsJoinedByString:@" "]; + XCTAssertEqualObjects(request.scope, expectedScopeString); +} + +- (void)testCreateAuthorizationRequest_additionalScopes { + NSArray *addtionalScopes = @[kScopeBirthday]; + GIDSignInInternalOptions *options = + [GIDSignInInternalOptions defaultOptionsWithConfiguration:_configuration +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + presentingViewController:nil +#elif TARGET_OS_OSX + presentingWindow:nil +#endif // TARGET_OS_OSX + loginHint:kUserEmail + addScopesFlow:NO + scopes:addtionalScopes + completion:nil]; + OIDAuthorizationRequest *request = + [GIDAuthorizationUtil authorizationRequestWithOptions:options + emmSupport:nil]; + + NSArray *expectedScopes = @[kScopeBirthday, kScopeEmail, kScopeProfile]; + NSString *expectedScopeString = [expectedScopes componentsJoinedByString:@" "]; + XCTAssertEqualObjects(request.scope, expectedScopeString); +} + +- (void)testCreateAuthrizationRequest_addScopes { + NSArray *scopes = @[kScopeEmail, kScopeProfile, kScopeBirthday]; + GIDSignInInternalOptions *options = + [GIDSignInInternalOptions defaultOptionsWithConfiguration:_configuration +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + presentingViewController:nil +#elif TARGET_OS_OSX + presentingWindow:nil +#endif // TARGET_OS_OSX + loginHint:kUserEmail + addScopesFlow:YES + scopes:scopes + completion:nil]; + + OIDAuthorizationRequest *request = + [GIDAuthorizationUtil authorizationRequestWithOptions:options + emmSupport:nil]; + + NSString *expectedScopeString = [scopes componentsJoinedByString:@" "]; + XCTAssertEqualObjects(request.scope, expectedScopeString); +} + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +- (void)testCreateAuthorizationRequest_signInFlow_EMM { + GIDSignInInternalOptions *options = + [GIDSignInInternalOptions defaultOptionsWithConfiguration:_configuration +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST + presentingViewController:nil +#elif TARGET_OS_OSX + presentingWindow:nil +#endif // TARGET_OS_OSX + loginHint:kUserEmail + addScopesFlow:NO + completion:nil]; + OIDAuthorizationRequest *request = + [GIDAuthorizationUtil authorizationRequestWithOptions:options + emmSupport:kEMMVersion]; + + NSString *systemName = [UIDevice currentDevice].systemName; + if ([systemName isEqualToString:@"iPhone OS"]) { + systemName = @"iOS"; + } + NSString *expectedOSVersion = [NSString stringWithFormat:@"%@ %@", + systemName, [UIDevice currentDevice].systemVersion]; + NSDictionary *authParams = request.additionalParameters; + + BOOL isEligibleForEMM = [UIDevice currentDevice].systemVersion.integerValue >= 9; + if (isEligibleForEMM) { + XCTAssertEqualObjects(authParams[@"emm_support"], kEMMVersion, + @"EMM support should match in auth request"); + XCTAssertEqualObjects(authParams[@"device_os"], expectedOSVersion, + @"OS version should match in auth request"); + } else { + XCTAssertNil(authParams[@"emm_support"], + @"EMM support should not be in auth request for unsupported OS"); + XCTAssertNil(authParams[@"device_os"], + @"OS version should not be in auth request for unsupported OS"); + } +} + +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +- (void)testUnionScopes_success { + NSArray *scopes = @[kScopeEmail, kScopeProfile]; + NSArray *newScopes = @[kScopeBirthday]; + + NSError *error; + NSArray *allScopes = + [GIDAuthorizationUtil resolvedScopesFromGrantedScoped:scopes + withNewScopes:newScopes + error:&error]; + + NSArray *expectedScopes = @[kScopeEmail, kScopeProfile, kScopeBirthday]; + XCTAssertEqualObjects(allScopes, expectedScopes); + XCTAssertNil(error); +} + +- (void)testUnionScopes_addExistingScopes_error { + NSArray *scopes = @[kScopeEmail, kScopeProfile, kScopeBirthday]; + NSArray *newScopes = @[kScopeBirthday]; + + NSError *error; + NSArray *allScopes = + [GIDAuthorizationUtil resolvedScopesFromGrantedScoped:scopes + withNewScopes:newScopes + error:&error]; + + XCTAssertNil(allScopes); + XCTAssertEqualObjects(error.domain, kGIDSignInErrorDomain); + XCTAssertEqual(error.code, kGIDSignInErrorCodeScopesAlreadyGranted); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/GoogleSignIn/Tests/Unit/GIDSignInTest.m b/GoogleSignIn/Tests/Unit/GIDSignInTest.m index d0fc72fa..1ae41732 100644 --- a/GoogleSignIn/Tests/Unit/GIDSignInTest.m +++ b/GoogleSignIn/Tests/Unit/GIDSignInTest.m @@ -91,8 +91,6 @@ static NSString * const kClientId2 = @"FakeClientID2"; static NSString * const kServerClientId = @"FakeServerClientID"; static NSString * const kLanguage = @"FakeLanguage"; -static NSString * const kScope = @"FakeScope"; -static NSString * const kScope2 = @"FakeScope2"; static NSString * const kAuthCode = @"FakeAuthCode"; static NSString * const kKeychainName = @"auth"; static NSString * const kUserEmail = @"FakeUserEmail"; @@ -133,13 +131,6 @@ static NSString * const kUserIDKey = @"userID"; static NSString * const kHostedDomainKey = @"hostedDomain"; static NSString * const kIDTokenExpirationKey = @"idTokenExp"; -static NSString * const kScopeKey = @"scope"; - -// Basic profile (Fat ID Token / userinfo endpoint) keys -static NSString *const kBasicProfilePictureKey = @"picture"; -static NSString *const kBasicProfileNameKey = @"name"; -static NSString *const kBasicProfileGivenNameKey = @"given_name"; -static NSString *const kBasicProfileFamilyNameKey = @"family_name"; static NSString * const kCustomKeychainName = @"CUSTOM_KEYCHAIN_NAME"; static NSString * const kAddActivity = @"http://schemas.google.com/AddActivity"; @@ -152,9 +143,6 @@ static NSString *const kFakeURL = @"http://foo.com"; -static NSString *const kEMMSupport = @"1"; - -static NSString *const kGrantedScope = @"grantedScope"; static NSString *const kNewScope = @"newScope"; #if TARGET_OS_IOS || TARGET_OS_MACCATALYST @@ -238,9 +226,6 @@ @interface GIDSignInTest : XCTestCase { // The completion to be used when testing |GIDSignIn|. GIDSignInCompletion _completion; - // The saved authorization request. - OIDAuthorizationRequest *_savedAuthorizationRequest; - #if TARGET_OS_IOS || TARGET_OS_MACCATALYST // The saved presentingViewController from the authorization request. UIViewController *_savedPresentingViewController; @@ -290,11 +275,11 @@ - (void)setUp { _user = OCMStrictClassMock([GIDGoogleUser class]); _oidAuthorizationService = OCMStrictClassMock([OIDAuthorizationService class]); OCMStub([_oidAuthorizationService - presentAuthorizationRequest:SAVE_TO_ARG_BLOCK(self->_savedAuthorizationRequest) + presentAuthorizationRequest:OCMOCK_ANY #if TARGET_OS_IOS || TARGET_OS_MACCATALYST - presentingViewController:SAVE_TO_ARG_BLOCK(self->_savedPresentingViewController) + presentingViewController:SAVE_TO_ARG_BLOCK(self->_savedPresentingViewController) #elif TARGET_OS_OSX - presentingWindow:SAVE_TO_ARG_BLOCK(self->_savedPresentingWindow) + presentingWindow:SAVE_TO_ARG_BLOCK(self->_savedPresentingWindow) #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST callback:COPY_TO_ARG_BLOCK(self->_savedAuthorizationCallback)]); OCMStub([self->_oidAuthorizationService @@ -521,108 +506,6 @@ - (void)testOAuthLogin_RestoredSignInOldAccessToken { modalCancel:NO]; } -- (void)testOAuthLogin_AdditionalScopes { - NSString *expectedScopeString; - - [self OAuthLoginWithAddScopesFlow:NO - authError:nil - tokenError:nil - emmPasscodeInfoRequired:NO - keychainError:NO - restoredSignIn:NO - oldAccessToken:NO - modalCancel:NO - useAdditionalScopes:YES - additionalScopes:nil]; - - expectedScopeString = [@[ @"email", @"profile" ] componentsJoinedByString:@" "]; - XCTAssertEqualObjects(_savedAuthorizationRequest.scope, expectedScopeString); - - [self OAuthLoginWithAddScopesFlow:NO - authError:nil - tokenError:nil - emmPasscodeInfoRequired:NO - keychainError:NO - restoredSignIn:NO - oldAccessToken:NO - modalCancel:NO - useAdditionalScopes:YES - additionalScopes:@[ kScope ]]; - - expectedScopeString = [@[ kScope, @"email", @"profile" ] componentsJoinedByString:@" "]; - XCTAssertEqualObjects(_savedAuthorizationRequest.scope, expectedScopeString); - - [self OAuthLoginWithAddScopesFlow:NO - authError:nil - tokenError:nil - emmPasscodeInfoRequired:NO - keychainError:NO - restoredSignIn:NO - oldAccessToken:NO - modalCancel:NO - useAdditionalScopes:YES - additionalScopes:@[ kScope, kScope2 ]]; - - expectedScopeString = [@[ kScope, kScope2, @"email", @"profile" ] componentsJoinedByString:@" "]; - XCTAssertEqualObjects(_savedAuthorizationRequest.scope, expectedScopeString); -} - -- (void)testAddScopes { - // Restore the previous sign-in account. This is the preparation for adding scopes. - [self OAuthLoginWithAddScopesFlow:NO - authError:nil - tokenError:nil - emmPasscodeInfoRequired:NO - keychainError:NO - restoredSignIn:YES - oldAccessToken:NO - modalCancel:NO]; - - XCTAssertNotNil(_signIn.currentUser); - - id profile = OCMStrictClassMock([GIDProfileData class]); - OCMStub([profile email]).andReturn(kUserEmail); - - // Mock for the method `addScopes`. - 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]); - - [self OAuthLoginWithAddScopesFlow:YES - authError:nil - tokenError:nil - emmPasscodeInfoRequired:NO - keychainError:NO - restoredSignIn:NO - oldAccessToken:NO - modalCancel:NO]; - - NSArray *grantedScopes; - NSString *grantedScopeString = _savedAuthorizationRequest.scope; - - if (grantedScopeString) { - grantedScopeString = [grantedScopeString stringByTrimmingCharactersInSet: - [NSCharacterSet whitespaceCharacterSet]]; - // Tokenize with space as a delimiter. - NSMutableArray *parsedScopes = - [[grantedScopeString componentsSeparatedByString:@" "] mutableCopy]; - // Remove empty strings. - [parsedScopes removeObject:@""]; - grantedScopes = [parsedScopes copy]; - } - - NSArray *expectedScopes = @[kNewScope, kGrantedScope]; - XCTAssertEqualObjects(grantedScopes, expectedScopes); - - [_user verify]; - [profile verify]; - [profile stopMocking]; -} - - (void)testOpenIDRealm { _signIn.configuration = [[GIDConfiguration alloc] initWithClientID:kClientId serverClientID:nil @@ -642,41 +525,6 @@ - (void)testOpenIDRealm { XCTAssertEqual(params[kOpenIDRealmKey], kOpenIDRealm, @"OpenID Realm should match."); } -- (void)testOAuthLogin_LoginHint { - _hint = kUserEmail; - - [self OAuthLoginWithAddScopesFlow:NO - authError:nil - tokenError:nil - emmPasscodeInfoRequired:NO - keychainError:NO - restoredSignIn:NO - oldAccessToken:NO - modalCancel:NO]; - - NSDictionary *params = _savedAuthorizationRequest.additionalParameters; - XCTAssertEqualObjects(params[@"login_hint"], kUserEmail, @"login hint should match"); -} - -- (void)testOAuthLogin_HostedDomain { - _signIn.configuration = [[GIDConfiguration alloc] initWithClientID:kClientId - serverClientID:nil - hostedDomain:kHostedDomain - openIDRealm:nil]; - - [self OAuthLoginWithAddScopesFlow:NO - authError:nil - tokenError:nil - emmPasscodeInfoRequired:NO - keychainError:NO - restoredSignIn:NO - oldAccessToken:NO - modalCancel:NO]; - - NSDictionary *params = _savedAuthorizationRequest.additionalParameters; - XCTAssertEqualObjects(params[@"hd"], kHostedDomain, @"hosted domain should match"); -} - - (void)testOAuthLogin_ConsentCanceled { [self OAuthLoginWithAddScopesFlow:NO authError:@"access_denied" @@ -1005,15 +853,10 @@ - (void)testEmmSupportRequestParameters { } NSString *expectedOSVersion = [NSString stringWithFormat:@"%@ %@", systemName, [UIDevice currentDevice].systemVersion]; - NSDictionary *authParams = - _savedAuthorizationRequest.additionalParameters; + NSDictionary *tokenParams = _savedTokenRequest.additionalParameters; if (_isEligibleForEMM) { - XCTAssertEqualObjects(authParams[@"emm_support"], kEMMSupport, - @"EMM support should match in auth request"); - XCTAssertEqualObjects(authParams[@"device_os"], expectedOSVersion, - @"OS version should match in auth request"); - XCTAssertEqualObjects(tokenParams[@"emm_support"], kEMMSupport, + XCTAssertEqualObjects(tokenParams[@"emm_support"], kEMMVersion, @"EMM support should match in token request"); XCTAssertEqualObjects(tokenParams[@"device_os"], expectedOSVersion, @@ -1021,10 +864,6 @@ - (void)testEmmSupportRequestParameters { XCTAssertNil(tokenParams[@"emm_passcode_info"], @"no passcode info should be in token request"); } else { - XCTAssertNil(authParams[@"emm_support"], - @"EMM support should not be in auth request for unsupported OS"); - XCTAssertNil(authParams[@"device_os"], - @"OS version should not be in auth request for unsupported OS"); XCTAssertNil(tokenParams[@"emm_support"], @"EMM support should not be in token request for unsupported OS"); XCTAssertNil(tokenParams[@"device_os"], @@ -1153,26 +992,6 @@ - (void)verifyRevokeRequest:(NSURLRequest *)request withToken:(NSString *)token XCTAssertEqualObjects(strings[1], token); } -- (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow - authError:(NSString *)authError - tokenError:(NSError *)tokenError - emmPasscodeInfoRequired:(BOOL)emmPasscodeInfoRequired - keychainError:(BOOL)keychainError - restoredSignIn:(BOOL)restoredSignIn - oldAccessToken:(BOOL)oldAccessToken - modalCancel:(BOOL)modalCancel { - [self OAuthLoginWithAddScopesFlow:addScopesFlow - authError:authError - tokenError:tokenError - emmPasscodeInfoRequired:emmPasscodeInfoRequired - keychainError:keychainError - restoredSignIn:restoredSignIn - oldAccessToken:oldAccessToken - modalCancel:modalCancel - useAdditionalScopes:NO - additionalScopes:nil]; -} - // The authorization flow with parameters to control which branches to take. - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow authError:(NSString *)authError @@ -1181,9 +1000,7 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow keychainError:(BOOL)keychainError restoredSignIn:(BOOL)restoredSignIn oldAccessToken:(BOOL)oldAccessToken - modalCancel:(BOOL)modalCancel - useAdditionalScopes:(BOOL)useAdditionalScopes - additionalScopes:(NSArray *)additionalScopes { + modalCancel:(BOOL)modalCancel { if (restoredSignIn) { // clearAndAuthenticateWithOptions [_keychainHandler saveAuthState:_authState]; @@ -1253,33 +1070,17 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST completion:completion]; } else { - if (useAdditionalScopes) { #if TARGET_OS_IOS || TARGET_OS_MACCATALYST - [_signIn signInWithPresentingViewController:_presentingViewController + [_signIn signInWithPresentingViewController:_presentingViewController #elif TARGET_OS_OSX - [_signIn signInWithPresentingWindow:_presentingWindow + [_signIn signInWithPresentingWindow:_presentingWindow #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST - hint:_hint - additionalScopes:additionalScopes - completion:completion]; - } else { -#if TARGET_OS_IOS || TARGET_OS_MACCATALYST - [_signIn signInWithPresentingViewController:_presentingViewController -#elif TARGET_OS_OSX - [_signIn signInWithPresentingWindow:_presentingWindow -#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST - hint:_hint - completion:completion]; - } + hint:_hint + completion:completion]; } [_authState verify]; - - XCTAssertNotNil(_savedAuthorizationRequest); - NSDictionary *params = _savedAuthorizationRequest.additionalParameters; - XCTAssertEqualObjects(params[@"include_granted_scopes"], @"true"); - XCTAssertEqualObjects(params[kSDKVersionLoggingParameter], GIDVersion()); - XCTAssertEqualObjects(params[kEnvironmentLoggingParameter], GIDEnvironment()); + XCTAssertNotNil(_savedAuthorizationCallback); #if TARGET_OS_IOS || TARGET_OS_MACCATALYST XCTAssertEqual(_savedPresentingViewController, _presentingViewController);