Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions GoogleSignIn/Sources/GIDSignIn.m
Original file line number Diff line number Diff line change
Expand Up @@ -272,12 +272,25 @@ - (void)signInWithPresentingViewController:(UIViewController *)presentingViewCon
hint:(nullable NSString *)hint
additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
completion:(nullable GIDSignInCompletion)completion {
[self signInWithPresentingViewController:presentingViewController
hint:hint
additionalScopes:additionalScopes
nonce:nil
completion:completion];
}

- (void)signInWithPresentingViewController:(UIViewController *)presentingViewController
hint:(nullable NSString *)hint
additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
nonce:(nullable NSString *)nonce
completion:(nullable GIDSignInCompletion)completion {
GIDSignInInternalOptions *options =
[GIDSignInInternalOptions defaultOptionsWithConfiguration:_configuration
presentingViewController:presentingViewController
loginHint:hint
addScopesFlow:NO
scopes:additionalScopes
nonce:nonce
completion:completion];
[self signInWithOptions:options];
}
Expand Down Expand Up @@ -350,12 +363,25 @@ - (void)signInWithPresentingWindow:(NSWindow *)presentingWindow
hint:(nullable NSString *)hint
additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
completion:(nullable GIDSignInCompletion)completion {
[self signInWithPresentingWindow:presentingWindow
hint:hint
additionalScopes:additionalScopes
nonce:nil
completion:completion];
}

- (void)signInWithPresentingWindow:(NSWindow *)presentingWindow
hint:(nullable NSString *)hint
additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
nonce:(nullable NSString *)nonce
completion:(nullable GIDSignInCompletion)completion {
GIDSignInInternalOptions *options =
[GIDSignInInternalOptions defaultOptionsWithConfiguration:_configuration
presentingWindow:presentingWindow
loginHint:hint
addScopesFlow:NO
scopes:additionalScopes
nonce:nonce
completion:completion];
[self signInWithOptions:options];
}
Expand Down Expand Up @@ -573,7 +599,7 @@ - (void)signInWithOptions:(GIDSignInInternalOptions *)options {
if (!_configuration) {
// NOLINTNEXTLINE(google-objc-avoid-throwing-exception)
[NSException raise:NSInvalidArgumentException
format:@"No active configuration. Make sure GIDClientID is set in Info.plist."];
format:@"No active configuration. Make sure GIDClientID is set in Info.plist."];
return;
}

Expand Down Expand Up @@ -667,7 +693,6 @@ - (void)authorizationRequestWithOptions:(GIDSignInInternalOptions *)options comp
[_timedLoader startTiming];
[self->_appCheck getLimitedUseTokenWithCompletion:^(GACAppCheckToken * _Nullable token,
NSError * _Nullable error) {
OIDAuthorizationRequest *request = nil;
if (token) {
additionalParameters[kClientAssertionTypeParameter] = kClientAssertionTypeParameterValue;
additionalParameters[kClientAssertionParameter] = token.token;
Expand All @@ -677,7 +702,7 @@ - (void)authorizationRequestWithOptions:(GIDSignInInternalOptions *)options comp
NSLog(@"[Google Sign-In iOS]: Error retrieving App Check limited use token: %@", error);
}
#endif
request = [self authorizationRequestWithOptions:options
OIDAuthorizationRequest *request = [self authorizationRequestWithOptions:options
additionalParameters:additionalParameters];
if (self->_timedLoader.animationStatus == GIDTimedLoaderAnimationStatusAnimating) {
[self->_timedLoader stopTimingWithCompletion:^{
Expand Down Expand Up @@ -707,6 +732,7 @@ - (void)authorizationRequestWithOptions:(GIDSignInInternalOptions *)options comp
scopes:options.scopes
redirectURL:[self redirectURLWithOptions:options]
responseType:OIDResponseTypeCode
nonce:options.nonce
additionalParameters:additionalParameters];
return request;
}
Expand Down Expand Up @@ -758,7 +784,7 @@ - (NSURL *)redirectURLWithOptions:(GIDSignInInternalOptions *)options {

- (void)processAuthorizationResponse:(OIDAuthorizationResponse *)authorizationResponse
error:(NSError *)error
emmSupport:(NSString *)emmSupport{
emmSupport:(NSString *)emmSupport {
if (_restarting) {
// The auth flow is restarting, so the work here would be performed in the next round.
_restarting = NO;
Expand Down
6 changes: 6 additions & 0 deletions GoogleSignIn/Sources/GIDSignInInternalOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ NS_ASSUME_NONNULL_BEGIN
/// The login hint to be used during the flow.
@property(nonatomic, copy, nullable) NSString *loginHint;

/// A cryptographically random value used to associate a Client session with an ID Token,
/// and to mitigate replay attacks.
@property(nonatomic, readonly, copy, nullable) NSString *nonce;

/// Creates the default options.
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
+ (instancetype)defaultOptionsWithConfiguration:(nullable GIDConfiguration *)configuration
Expand All @@ -77,6 +81,7 @@ NS_ASSUME_NONNULL_BEGIN
loginHint:(nullable NSString *)loginHint
addScopesFlow:(BOOL)addScopesFlow
scopes:(nullable NSArray *)scopes
nonce:(nullable NSString *)nonce
completion:(nullable GIDSignInCompletion)completion;

#elif TARGET_OS_OSX
Expand All @@ -91,6 +96,7 @@ NS_ASSUME_NONNULL_BEGIN
loginHint:(nullable NSString *)loginHint
addScopesFlow:(BOOL)addScopesFlow
scopes:(nullable NSArray *)scopes
nonce:(nullable NSString *)nonce
completion:(nullable GIDSignInCompletion)completion;
#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST

Expand Down
4 changes: 4 additions & 0 deletions GoogleSignIn/Sources/GIDSignInInternalOptions.m
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ + (instancetype)defaultOptionsWithConfiguration:(nullable GIDConfiguration *)con
loginHint:(nullable NSString *)loginHint
addScopesFlow:(BOOL)addScopesFlow
scopes:(nullable NSArray *)scopes
nonce:(nullable NSString *)nonce
completion:(nullable GIDSignInCompletion)completion {
#elif TARGET_OS_OSX
+ (instancetype)defaultOptionsWithConfiguration:(nullable GIDConfiguration *)configuration
presentingWindow:(nullable NSWindow *)presentingWindow
loginHint:(nullable NSString *)loginHint
addScopesFlow:(BOOL)addScopesFlow
scopes:(nullable NSArray *)scopes
nonce:(nullable NSString *)nonce
completion:(nullable GIDSignInCompletion)completion {
#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
GIDSignInInternalOptions *options = [[GIDSignInInternalOptions alloc] init];
Expand All @@ -54,6 +56,7 @@ + (instancetype)defaultOptionsWithConfiguration:(nullable GIDConfiguration *)con
options->_loginHint = loginHint;
options->_completion = completion;
options->_scopes = [GIDScopes scopesWithBasicProfile:scopes];
options->_nonce = nonce;
}
return options;
}
Expand All @@ -80,6 +83,7 @@ + (instancetype)defaultOptionsWithConfiguration:(nullable GIDConfiguration *)con
loginHint:loginHint
addScopesFlow:addScopesFlow
scopes:@[]
nonce:nil
completion:completion];
return options;
}
Expand Down
49 changes: 48 additions & 1 deletion GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,31 @@ NS_EXTENSION_UNAVAILABLE("The sign-in flow is not supported in App Extensions.")
NSError *_Nullable error))completion
NS_EXTENSION_UNAVAILABLE("The sign-in flow is not supported in App Extensions.");


/// Starts an interactive sign-in flow on iOS using the provided hint, additional scopes, and nonce.
///
/// The completion will be called at the end of this process. Any saved sign-in state will be
/// replaced by the result of this flow. Note that this method should not be called when the app is
/// starting up, (e.g in `application:didFinishLaunchingWithOptions:`); instead use the
/// `restorePreviousSignInWithCompletion:` method to restore a previous sign-in.
///
/// @param presentingViewController The view controller used to present `SFSafariViewController` on
/// iOS 9 and 10.
/// @param hint An optional hint for the authorization server, for example the user's ID or email
/// address, to be prefilled if possible.
/// @param additionalScopes An optional array of scopes to request in addition to the basic profile scopes.
/// @param nonce A custom nonce.
/// @param completion The optional block that is called on completion. This block will
/// be called asynchronously on the main queue.
- (void)signInWithPresentingViewController:(UIViewController *)presentingViewController
hint:(nullable NSString *)hint
additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
nonce:(nullable NSString *)nonce
completion:
(nullable void (^)(GIDSignInResult *_Nullable signInResult,
NSError *_Nullable error))completion
NS_EXTENSION_UNAVAILABLE("The sign-in flow is not supported in App Extensions.");

#elif TARGET_OS_OSX

/// Starts an interactive sign-in flow on macOS.
Expand Down Expand Up @@ -229,7 +254,7 @@ NS_EXTENSION_UNAVAILABLE("The sign-in flow is not supported in App Extensions.")
completion:(nullable void (^)(GIDSignInResult *_Nullable signInResult,
NSError *_Nullable error))completion;

/// Starts an interactive sign-in flow on macOS using the provided hint.
/// Starts an interactive sign-in flow on macOS using the provided hint and additional scopes.
///
/// The completion will be called at the end of this process. Any saved sign-in state will be
/// replaced by the result of this flow. Note that this method should not be called when the app is
Expand All @@ -248,6 +273,28 @@ NS_EXTENSION_UNAVAILABLE("The sign-in flow is not supported in App Extensions.")
completion:(nullable void (^)(GIDSignInResult *_Nullable signInResult,
NSError *_Nullable error))completion;

/// Starts an interactive sign-in flow on macOS using the provided hint, additional scopes, and nonce.
///
/// The completion will be called at the end of this process. Any saved sign-in state will be
/// replaced by the result of this flow. Note that this method should not be called when the app is
/// starting up, (e.g in `application:didFinishLaunchingWithOptions:`); instead use the
/// `restorePreviousSignInWithCompletion:` method to restore a previous sign-in.
///
/// @param presentingWindow The window used to supply `presentationContextProvider` for `ASWebAuthenticationSession`.
/// @param hint An optional hint for the authorization server, for example the user's ID or email
/// address, to be prefilled if possible.
/// @param additionalScopes An optional array of scopes to request in addition to the basic profile scopes.
/// @param nonce A custom nonce.
/// @param completion The optional block that is called on completion. This block will
/// be called asynchronously on the main queue.
- (void)signInWithPresentingWindow:(NSWindow *)presentingWindow
hint:(nullable NSString *)hint
additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
nonce:(nullable NSString *)nonce
completion:(nullable void (^)(GIDSignInResult *_Nullable signInResult,
NSError *_Nullable error))completion;


#endif

@end
Expand Down
54 changes: 46 additions & 8 deletions GoogleSignIn/Tests/Unit/GIDSignInTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -500,8 +500,7 @@ - (void)testRestorePreviousSignInNoRefresh_hasPreviousUser {

// Mock generating a GIDConfiguration when initializing GIDGoogleUser.
OIDAuthorizationResponse *authResponse =
[OIDAuthorizationResponse testInstanceWithAdditionalParameters:nil
errorString:nil];
[OIDAuthorizationResponse testInstance];

OCMStub([_authState lastAuthorizationResponse]).andReturn(authResponse);
OCMStub([_tokenResponse idToken]).andReturn(kFakeIDToken);
Expand Down Expand Up @@ -676,7 +675,8 @@ - (void)testOAuthLogin_AdditionalScopes {
oldAccessToken:NO
modalCancel:NO
useAdditionalScopes:YES
additionalScopes:nil];
additionalScopes:nil
manualNonce:nil];

expectedScopeString = [@[ @"email", @"profile" ] componentsJoinedByString:@" "];
XCTAssertEqualObjects(_savedAuthorizationRequest.scope, expectedScopeString);
Expand All @@ -690,7 +690,8 @@ - (void)testOAuthLogin_AdditionalScopes {
oldAccessToken:NO
modalCancel:NO
useAdditionalScopes:YES
additionalScopes:@[ kScope ]];
additionalScopes:@[ kScope ]
manualNonce:nil];

expectedScopeString = [@[ kScope, @"email", @"profile" ] componentsJoinedByString:@" "];
XCTAssertEqualObjects(_savedAuthorizationRequest.scope, expectedScopeString);
Expand All @@ -704,7 +705,8 @@ - (void)testOAuthLogin_AdditionalScopes {
oldAccessToken:NO
modalCancel:NO
useAdditionalScopes:YES
additionalScopes:@[ kScope, kScope2 ]];
additionalScopes:@[ kScope, kScope2 ]
manualNonce:nil];

expectedScopeString = [@[ kScope, kScope2, @"email", @"profile" ] componentsJoinedByString:@" "];
XCTAssertEqualObjects(_savedAuthorizationRequest.scope, expectedScopeString);
Expand Down Expand Up @@ -796,6 +798,37 @@ - (void)testOpenIDRealm {
XCTAssertEqual(params[kOpenIDRealmKey], kOpenIDRealm, @"OpenID Realm should match.");
}

- (void)testManualNonce {
_signIn.configuration = [[GIDConfiguration alloc] initWithClientID:kClientId
serverClientID:nil
hostedDomain:nil
openIDRealm:kOpenIDRealm];

OCMStub(
[_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
).andDo(^(NSInvocation *invocation) {
self->_keychainSaved = self->_saveAuthorizationReturnValue;
});

NSString* manualNonce = @"manual_nonce";

[self OAuthLoginWithAddScopesFlow:NO
authError:nil
tokenError:nil
emmPasscodeInfoRequired:NO
keychainError:NO
restoredSignIn:NO
oldAccessToken:NO
modalCancel:NO
useAdditionalScopes:NO
additionalScopes:@[]
manualNonce:manualNonce];

XCTAssertEqualObjects(_savedAuthorizationRequest.nonce,
manualNonce,
@"Provided nonce should match nonce in authorization request.");
}

- (void)testOAuthLogin_LoginHint {
_hint = kUserEmail;

Expand Down Expand Up @@ -1375,7 +1408,8 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow
oldAccessToken:oldAccessToken
modalCancel:modalCancel
useAdditionalScopes:NO
additionalScopes:nil];
additionalScopes:nil
manualNonce:nil];
}

// The authorization flow with parameters to control which branches to take.
Expand All @@ -1388,18 +1422,20 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow
oldAccessToken:(BOOL)oldAccessToken
modalCancel:(BOOL)modalCancel
useAdditionalScopes:(BOOL)useAdditionalScopes
additionalScopes:(NSArray *)additionalScopes {
additionalScopes:(NSArray *)additionalScopes
manualNonce:(NSString *)nonce {
if (restoredSignIn) {
// clearAndAuthenticateWithOptions
[[[_authorization expect] andReturn:_authState] authState];
BOOL isAuthorized = restoredSignIn ? YES : NO;
BOOL isAuthorized = restoredSignIn;
[[[_authState expect] andReturnValue:[NSNumber numberWithBool:isAuthorized]] isAuthorized];
}

NSDictionary<NSString *, NSString *> *additionalParameters = emmPasscodeInfoRequired ?
@{ @"emm_passcode_info_required" : @"1" } : nil;
OIDAuthorizationResponse *authResponse =
[OIDAuthorizationResponse testInstanceWithAdditionalParameters:additionalParameters
nonce:nonce
errorString:authError];

OIDTokenResponse *tokenResponse =
Expand Down Expand Up @@ -1475,6 +1511,8 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow
[_signIn signInWithPresentingWindow:_presentingWindow
#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
hint:_hint
additionalScopes:nil
nonce:nonce
completion:completion];
}
}
Expand Down
12 changes: 7 additions & 5 deletions GoogleSignIn/Tests/Unit/OIDAuthorizationRequest+Testing.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@
#import <AppAuth/OIDAuthorizationRequest.h>
#endif

extern NSString *const OIDAuthorizationRequestTestingClientID;
extern NSString *const OIDAuthorizationRequestTestingScope;
extern NSString *const OIDAuthorizationRequestTestingScope2;
extern NSString *const OIDAuthorizationRequestTestingCodeVerifier;
extern NSString * _Nonnull const OIDAuthorizationRequestTestingClientID;
extern NSString * _Nonnull const OIDAuthorizationRequestTestingScope;
extern NSString * _Nonnull const OIDAuthorizationRequestTestingScope2;
extern NSString * _Nonnull const OIDAuthorizationRequestTestingCodeVerifier;

@interface OIDAuthorizationRequest (Testing)

+ (instancetype)testInstance;
+ (instancetype _Nonnull)testInstance;

+ (instancetype _Nonnull)testInstanceWithNonce:(nullable NSString *)nonce;

@end
Loading