GIDUserAuthFlowController.m 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. #import "GoogleSignIn/Sources/GIDUserAuthFlowController.h"
  2. #import "GoogleSignIn/Sources/GIDSignInInternalOptions.h"
  3. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h"
  4. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDConfiguration.h"
  5. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h"
  6. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDProfileData.h"
  7. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDUserAuth.h"
  8. #import "GoogleSignIn/Sources/GIDAuthFlow_TEMP.h"
  9. #import "GoogleSignIn/Sources/GIDEMMSupport.h"
  10. #import "GoogleSignIn/Sources/GIDSignInPreferences.h"
  11. #import "GoogleSignIn/Sources/GIDCallbackQueue.h"
  12. #import "GoogleSignIn/Sources/GIDSignInCallbackSchemes.h"
  13. #import "GoogleSignIn/Sources/GIDUserAuthFlowResult.h"
  14. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  15. #import "GoogleSignIn/Sources/GIDAuthStateMigration.h"
  16. #import "GoogleSignIn/Sources/GIDEMMErrorHandler.h"
  17. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  18. #import "GoogleSignIn/Sources/GIDGoogleUser_Private.h"
  19. #import "GoogleSignIn/Sources/GIDProfileData_Private.h"
  20. #import "GoogleSignIn/Sources/GIDUserAuth_Private.h"
  21. #ifdef SWIFT_PACKAGE
  22. @import AppAuth;
  23. #else
  24. #import <AppAuth/AppAuth.h>
  25. #endif
  26. NS_ASSUME_NONNULL_BEGIN
  27. // The name of the query parameter used for logging the restart of auth from EMM callback.
  28. static NSString *const kEMMRestartAuthParameter = @"emmres";
  29. // The URL template for the URL to get user info.
  30. static NSString *const kUserInfoURLTemplate = @"https://%@/oauth2/v3/userinfo?access_token=%@";
  31. static NSString *const kEMMPasscodeInfoRequiredKeyName = @"emm_passcode_info_required";
  32. // Expected path in the URL scheme to be handled.
  33. static NSString *const kBrowserCallbackPath = @"/oauth2callback";
  34. // Expected path for EMM callback.
  35. static NSString *const kEMMCallbackPath = @"/emmcallback";
  36. // The EMM support version
  37. static NSString *const kEMMVersion = @"1";
  38. // Basic profile (Fat ID Token / userinfo endpoint) keys
  39. static NSString *const kBasicProfileEmailKey = @"email";
  40. static NSString *const kBasicProfilePictureKey = @"picture";
  41. static NSString *const kBasicProfileNameKey = @"name";
  42. static NSString *const kBasicProfileGivenNameKey = @"given_name";
  43. static NSString *const kBasicProfileFamilyNameKey = @"family_name";
  44. // Parameters for the auth and token exchange endpoints.
  45. static NSString *const kAudienceParameter = @"audience";
  46. // See b/11669751 .
  47. static NSString *const kOpenIDRealmParameter = @"openid.realm";
  48. static NSString *const kIncludeGrantedScopesParameter = @"include_granted_scopes";
  49. static NSString *const kLoginHintParameter = @"login_hint";
  50. static NSString *const kHostedDomainParameter = @"hd";
  51. // Parameters in the callback URL coming back from browser.
  52. static NSString *const kOAuth2ErrorKeyName = @"error";
  53. static NSString *const kOAuth2AccessDenied = @"access_denied";
  54. // Error string for unavailable keychain.
  55. static NSString *const kKeychainError = @"keychain error";
  56. // Error string for user cancelations.
  57. static NSString *const kUserCanceledError = @"The user canceled the sign-in flow.";
  58. // Maximum retry interval in seconds for the fetcher.
  59. static const NSTimeInterval kFetcherMaxRetryInterval = 15.0;
  60. // The delay before the new sign-in flow can be presented after the existing one is cancelled.
  61. static const NSTimeInterval kPresentationDelayAfterCancel = 1.0;
  62. // Minimum time to expiration for a restored access token.
  63. static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
  64. //@implementation GIDAuthFlow_TEMP
  65. //@end
  66. // Keychain constants for saving state in the authentication flow.
  67. static NSString *const kGTMAppAuthKeychainName = @"auth";
  68. @implementation GIDUserAuthFlowController {
  69. // This value is used when sign-in flows are resumed via the handling of a URL. Its value is
  70. // set when a sign-in flow is begun via |signInWithOptions:| when the options passed don't
  71. // represent a sign in continuation.
  72. GIDSignInInternalOptions *_currentOptions;
  73. // AppAuth external user-agent session state.
  74. id<OIDExternalUserAgentSession> _currentAuthorizationFlow;
  75. // Flag to indicate that the auth flow is restarting.
  76. BOOL _restarting;
  77. // The completion block to be invoked at the end of the auth flow.
  78. GIDUserAuthFlowCompletion _completion;
  79. }
  80. #pragma mark - Public methods
  81. - (BOOL)handleURL:(NSURL *)url {
  82. // Check if the callback path matches the expected one for a URL from Safari/Chrome/SafariVC.
  83. if ([url.path isEqual:kBrowserCallbackPath]) {
  84. if ([_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:url]) {
  85. _currentAuthorizationFlow = nil;
  86. return YES;
  87. }
  88. return NO;
  89. }
  90. // Check if the callback path matches the expected one for a URL from Google Device Policy app.
  91. if ([url.path isEqual:kEMMCallbackPath]) {
  92. return [self handleDevicePolicyAppURL:url];
  93. }
  94. return NO;
  95. }
  96. - (void)authenticateInteractivelyWithOptions:(GIDSignInInternalOptions *)options
  97. completion:(GIDUserAuthFlowCompletion)completion{
  98. // Options for continuation are not the options we want to cache. The purpose of caching the
  99. // options in the first place is to provide continuation flows with a starting place from which to
  100. // derive suitable options for the continuation!
  101. if (!options.continuation) {
  102. _currentOptions = options;
  103. }
  104. GIDSignInCallbackSchemes *schemes =
  105. [[GIDSignInCallbackSchemes alloc] initWithClientIdentifier:options.configuration.clientID];
  106. NSURL *redirectURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@:%@",
  107. [schemes clientIdentifierScheme],
  108. kBrowserCallbackPath]];
  109. NSString *emmSupport;
  110. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  111. emmSupport = [[self class] isOperatingSystemAtLeast9] ? kEMMVersion : nil;
  112. #elif TARGET_OS_MACCATALYST || TARGET_OS_OSX
  113. emmSupport = nil;
  114. #endif // TARGET_OS_MACCATALYST || TARGET_OS_OSX
  115. NSMutableDictionary<NSString *, NSString *> *additionalParameters = [@{} mutableCopy];
  116. additionalParameters[kIncludeGrantedScopesParameter] = @"true";
  117. if (options.configuration.serverClientID) {
  118. additionalParameters[kAudienceParameter] = options.configuration.serverClientID;
  119. }
  120. if (options.loginHint) {
  121. additionalParameters[kLoginHintParameter] = options.loginHint;
  122. }
  123. if (options.configuration.hostedDomain) {
  124. additionalParameters[kHostedDomainParameter] = options.configuration.hostedDomain;
  125. }
  126. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  127. [additionalParameters addEntriesFromDictionary:
  128. [GIDEMMSupport parametersWithParameters:options.extraParams
  129. emmSupport:emmSupport
  130. isPasscodeInfoRequired:NO]];
  131. #elif TARGET_OS_OSX || TARGET_OS_MACCATALYST
  132. [additionalParameters addEntriesFromDictionary:options.extraParams];
  133. #endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
  134. additionalParameters[kSDKVersionLoggingParameter] = GIDVersion();
  135. additionalParameters[kEnvironmentLoggingParameter] = GIDEnvironment();
  136. OIDServiceConfiguration *appAuthConfiguration = [GIDSignInPreferences appAuthConfiguration];
  137. OIDAuthorizationRequest *request =
  138. [[OIDAuthorizationRequest alloc] initWithConfiguration:appAuthConfiguration
  139. clientId:options.configuration.clientID
  140. scopes:options.scopes
  141. redirectURL:redirectURL
  142. responseType:OIDResponseTypeCode
  143. additionalParameters:additionalParameters];
  144. _currentAuthorizationFlow = [OIDAuthorizationService
  145. presentAuthorizationRequest:request
  146. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  147. presentingViewController:options.presentingViewController
  148. #elif TARGET_OS_OSX
  149. presentingWindow:options.presentingWindow
  150. #endif // TARGET_OS_OSX
  151. callback:^(OIDAuthorizationResponse *_Nullable authorizationResponse,
  152. NSError *_Nullable error) {
  153. [self processAuthorizationResponse:authorizationResponse
  154. error:error
  155. emmSupport:emmSupport
  156. completion:completion];
  157. }];
  158. }
  159. - (void)authenticateNonInteractivelyWithOptions:(GIDSignInInternalOptions *)options
  160. completion:(GIDUserAuthFlowCompletion)completion {
  161. // Try retrieving an authorization object from the keychain.
  162. OIDAuthState *authState = [self loadAuthState];
  163. if (![authState isAuthorized]) {
  164. // No valid auth in keychain, per documentation/spec, notify callback of failure.
  165. NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain
  166. code:kGIDSignInErrorCodeHasNoAuthInKeychain
  167. userInfo:nil];
  168. if (options.completion) {
  169. _currentOptions = nil;
  170. dispatch_async(dispatch_get_main_queue(), ^{
  171. options.completion(nil, error);
  172. });
  173. }
  174. return;
  175. }
  176. // Complete the auth flow using saved auth in keychain.
  177. GIDAuthFlow_TEMP *authFlow = [[GIDAuthFlow_TEMP alloc] init];
  178. authFlow.authState = authState;
  179. [self maybeFetchToken:authFlow];
  180. [self addDecodeIdTokenCallback:authFlow];
  181. [self addSaveAuthCallback:authFlow];
  182. [self addCompletionCallback:authFlow completion:completion];
  183. }
  184. // Fetches the access token if necessary as part of the auth flow.
  185. - (void)maybeFetchToken:(GIDAuthFlow_TEMP *)authFlow {
  186. OIDAuthState *authState = authFlow.authState;
  187. // Do nothing if we have an auth flow error or a restored access token that isn't near expiration.
  188. if (authFlow.error ||
  189. (authState.lastTokenResponse.accessToken &&
  190. [authState.lastTokenResponse.accessTokenExpirationDate timeIntervalSinceNow] >
  191. kMinimumRestoredAccessTokenTimeToExpire)) {
  192. return;
  193. }
  194. NSMutableDictionary<NSString *, NSString *> *additionalParameters = [@{} mutableCopy];
  195. if (_currentOptions.configuration.serverClientID) {
  196. additionalParameters[kAudienceParameter] = _currentOptions.configuration.serverClientID;
  197. }
  198. if (_currentOptions.configuration.openIDRealm) {
  199. additionalParameters[kOpenIDRealmParameter] = _currentOptions.configuration.openIDRealm;
  200. }
  201. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  202. NSDictionary<NSString *, NSObject *> *params =
  203. authState.lastAuthorizationResponse.additionalParameters;
  204. NSString *passcodeInfoRequired = (NSString *)params[kEMMPasscodeInfoRequiredKeyName];
  205. [additionalParameters addEntriesFromDictionary:
  206. [GIDEMMSupport parametersWithParameters:@{}
  207. emmSupport:authFlow.emmSupport
  208. isPasscodeInfoRequired:passcodeInfoRequired.length > 0]];
  209. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  210. additionalParameters[kSDKVersionLoggingParameter] = GIDVersion();
  211. additionalParameters[kEnvironmentLoggingParameter] = GIDEnvironment();
  212. OIDTokenRequest *tokenRequest;
  213. if (!authState.lastTokenResponse.accessToken &&
  214. authState.lastAuthorizationResponse.authorizationCode) {
  215. tokenRequest = [authState.lastAuthorizationResponse
  216. tokenExchangeRequestWithAdditionalParameters:additionalParameters];
  217. } else {
  218. [additionalParameters
  219. addEntriesFromDictionary:authState.lastTokenResponse.request.additionalParameters];
  220. tokenRequest = [authState tokenRefreshRequestWithAdditionalParameters:additionalParameters];
  221. }
  222. [authFlow wait];
  223. [OIDAuthorizationService
  224. performTokenRequest:tokenRequest
  225. callback:^(OIDTokenResponse *_Nullable tokenResponse,
  226. NSError *_Nullable error) {
  227. [authState updateWithTokenResponse:tokenResponse error:error];
  228. authFlow.error = error;
  229. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  230. if (authFlow.emmSupport) {
  231. [GIDEMMSupport handleTokenFetchEMMError:error completion:^(NSError *error) {
  232. authFlow.error = error;
  233. [authFlow next];
  234. }];
  235. } else {
  236. [authFlow next];
  237. }
  238. #elif TARGET_OS_OSX || TARGET_OS_MACCATALYST
  239. [authFlow next];
  240. #endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
  241. }];
  242. }
  243. - (void)processAuthorizationResponse:(OIDAuthorizationResponse *)authorizationResponse
  244. error:(NSError *)error
  245. emmSupport:(NSString *)emmSupport
  246. completion:(GIDUserAuthFlowCompletion)completion {
  247. if (_restarting) {
  248. // The auth flow is restarting, so the work here would be performed in the next round.
  249. _restarting = NO;
  250. return;
  251. }
  252. GIDAuthFlow_TEMP *authFlow = [[GIDAuthFlow_TEMP alloc] init];
  253. authFlow.emmSupport = emmSupport;
  254. if (authorizationResponse) {
  255. if (authorizationResponse.authorizationCode.length) {
  256. authFlow.authState = [[OIDAuthState alloc]
  257. initWithAuthorizationResponse:authorizationResponse];
  258. // perform auth code exchange
  259. [self maybeFetchToken:authFlow];
  260. } else {
  261. // There was a failure, convert to appropriate error code.
  262. NSString *errorString;
  263. GIDSignInErrorCode errorCode = kGIDSignInErrorCodeUnknown;
  264. NSDictionary<NSString *, NSObject *> *params = authorizationResponse.additionalParameters;
  265. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  266. if (authFlow.emmSupport) {
  267. [authFlow wait];
  268. BOOL isEMMError = [[GIDEMMErrorHandler sharedInstance]
  269. handleErrorFromResponse:params
  270. completion:^{
  271. [authFlow next];
  272. }];
  273. if (isEMMError) {
  274. errorCode = kGIDSignInErrorCodeEMM;
  275. }
  276. }
  277. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  278. errorString = (NSString *)params[kOAuth2ErrorKeyName];
  279. if ([errorString isEqualToString:kOAuth2AccessDenied]) {
  280. errorCode = kGIDSignInErrorCodeCanceled;
  281. }
  282. authFlow.error = [self errorWithString:errorString code:errorCode];
  283. }
  284. } else {
  285. NSString *errorString = [error localizedDescription];
  286. GIDSignInErrorCode errorCode = kGIDSignInErrorCodeUnknown;
  287. if (error.code == OIDErrorCodeUserCanceledAuthorizationFlow) {
  288. // The user has canceled the flow at the iOS modal dialog.
  289. errorString = kUserCanceledError;
  290. errorCode = kGIDSignInErrorCodeCanceled;
  291. }
  292. authFlow.error = [self errorWithString:errorString code:errorCode];
  293. }
  294. [self addDecodeIdTokenCallback:authFlow];
  295. [self addSaveAuthCallback:authFlow];
  296. [self addCompletionCallback:authFlow completion:completion];
  297. }
  298. // Adds a callback to the auth flow to extract user data from the ID token where available and
  299. // make a userinfo request if necessary.
  300. - (void)addDecodeIdTokenCallback:(GIDAuthFlow_TEMP *)authFlow {
  301. __weak GIDAuthFlow_TEMP *weakAuthFlow = authFlow;
  302. [authFlow addCallback:^() {
  303. GIDAuthFlow_TEMP *handlerAuthFlow = weakAuthFlow;
  304. OIDAuthState *authState = handlerAuthFlow.authState;
  305. if (!authState || handlerAuthFlow.error) {
  306. return;
  307. }
  308. OIDIDToken *idToken =
  309. [[OIDIDToken alloc] initWithIDTokenString: authState.lastTokenResponse.idToken];
  310. // If the profile data are present in the ID token, use them.
  311. if (idToken) {
  312. handlerAuthFlow.profileData = [self profileDataWithIDToken:idToken];
  313. }
  314. // If we can't retrieve profile data from the ID token, make a userInfo request to fetch them.
  315. if (!handlerAuthFlow.profileData) {
  316. [handlerAuthFlow wait];
  317. NSURL *infoURL = [NSURL URLWithString:
  318. [NSString stringWithFormat:kUserInfoURLTemplate,
  319. [GIDSignInPreferences googleUserInfoServer],
  320. authState.lastTokenResponse.accessToken]];
  321. [self startFetchURL:infoURL
  322. fromAuthState:authState
  323. withComment:@"GIDSignIn: fetch basic profile info"
  324. withCompletionHandler:^(NSData *data, NSError *error) {
  325. if (data && !error) {
  326. NSError *jsonDeserializationError;
  327. NSDictionary<NSString *, NSString *> *profileDict =
  328. [NSJSONSerialization JSONObjectWithData:data
  329. options:NSJSONReadingMutableContainers
  330. error:&jsonDeserializationError];
  331. if (profileDict) {
  332. handlerAuthFlow.profileData = [[GIDProfileData alloc]
  333. initWithEmail:idToken.claims[kBasicProfileEmailKey]
  334. name:profileDict[kBasicProfileNameKey]
  335. givenName:profileDict[kBasicProfileGivenNameKey]
  336. familyName:profileDict[kBasicProfileFamilyNameKey]
  337. imageURL:[NSURL URLWithString:profileDict[kBasicProfilePictureKey]]];
  338. }
  339. }
  340. if (error) {
  341. handlerAuthFlow.error = error;
  342. }
  343. [handlerAuthFlow next];
  344. }];
  345. }
  346. }];
  347. }
  348. // Adds a callback to the auth flow to save the auth object to |self| and the keychain as well.
  349. - (void)addSaveAuthCallback:(GIDAuthFlow_TEMP *)authFlow {
  350. __weak GIDAuthFlow_TEMP *weakAuthFlow = authFlow;
  351. [authFlow addCallback:^() {
  352. GIDAuthFlow_TEMP *handlerAuthFlow = weakAuthFlow;
  353. OIDAuthState *authState = handlerAuthFlow.authState;
  354. if (authState && !handlerAuthFlow.error) {
  355. if (![self saveAuthState:authState]) {
  356. handlerAuthFlow.error = [self errorWithString:kKeychainError
  357. code:kGIDSignInErrorCodeKeychain];
  358. }
  359. }
  360. }];
  361. }
  362. // Adds a callback to the auth flow to complete the flow by calling the sign-in callback.
  363. - (void)addCompletionCallback:(GIDAuthFlow_TEMP *)authFlow
  364. completion:(GIDUserAuthFlowCompletion)completion {
  365. __weak GIDAuthFlow_TEMP *weakAuthFlow = authFlow;
  366. [authFlow addCallback:^() {
  367. GIDAuthFlow_TEMP *handlerAuthFlow = weakAuthFlow;
  368. if (completion) {
  369. dispatch_async(dispatch_get_main_queue(), ^{
  370. if (handlerAuthFlow.error) {
  371. completion(nil, handlerAuthFlow.error);
  372. } else {
  373. OIDAuthState *authState = handlerAuthFlow.authState;
  374. GIDProfileData *profileData = handlerAuthFlow.profileData;
  375. NSString *_Nullable serverAuthCode =
  376. [authState.lastTokenResponse.additionalParameters[@"server_code"] copy];
  377. GIDUserAuthFlowResult *authFlowResult =
  378. [[GIDUserAuthFlowResult alloc] initWithAuthState:authState
  379. profileData:profileData
  380. serverAuthCode:serverAuthCode];
  381. completion(authFlowResult, nil);
  382. }
  383. });
  384. }
  385. }];
  386. }
  387. - (void)startFetchURL:(NSURL *)URL
  388. fromAuthState:(OIDAuthState *)authState
  389. withComment:(NSString *)comment
  390. withCompletionHandler:(void (^)(NSData *, NSError *))handler {
  391. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
  392. GTMSessionFetcher *fetcher;
  393. GTMAppAuthFetcherAuthorization *authorization =
  394. [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState];
  395. id<GTMSessionFetcherServiceProtocol> fetcherService = authorization.fetcherService;
  396. if (fetcherService) {
  397. fetcher = [fetcherService fetcherWithRequest:request];
  398. } else {
  399. fetcher = [GTMSessionFetcher fetcherWithRequest:request];
  400. }
  401. fetcher.retryEnabled = YES;
  402. fetcher.maxRetryInterval = kFetcherMaxRetryInterval;
  403. fetcher.comment = comment;
  404. [fetcher beginFetchWithCompletionHandler:handler];
  405. }
  406. // Parse incoming URL from the Google Device Policy app.
  407. - (BOOL)handleDevicePolicyAppURL:(NSURL *)url {
  408. OIDURLQueryComponent *queryComponent = [[OIDURLQueryComponent alloc] initWithURL:url];
  409. NSDictionary<NSString *, NSObject<NSCopying> *> *params = queryComponent.dictionaryValue;
  410. NSObject<NSCopying> *actionParam = params[@"action"];
  411. NSString *actionString =
  412. [actionParam isKindOfClass:[NSString class]] ? (NSString *)actionParam : nil;
  413. if (![@"restart_auth" isEqualToString:actionString]) {
  414. return NO;
  415. }
  416. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  417. if (!_currentOptions.presentingViewController) {
  418. return NO;
  419. }
  420. #elif TARGET_OS_OSX
  421. if (!_currentOptions.presentingWindow) {
  422. return NO;
  423. }
  424. #endif // TARGET_OS_OSX
  425. if (!_currentAuthorizationFlow) {
  426. return NO;
  427. }
  428. _restarting = YES;
  429. [_currentAuthorizationFlow cancel];
  430. _currentAuthorizationFlow = nil;
  431. _restarting = NO;
  432. NSDictionary<NSString *, NSString *> *extraParameters = @{ kEMMRestartAuthParameter : @"1" };
  433. // In iOS 13 the presentation of ASWebAuthenticationSession needs an anchor window,
  434. // so we need to wait until the previous presentation is completely gone to ensure the right
  435. // anchor window is used here.
  436. dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
  437. (int64_t)(kPresentationDelayAfterCancel * NSEC_PER_SEC)),
  438. dispatch_get_main_queue(), ^{
  439. [self->_currentOptions optionsWithExtraParameters:extraParameters
  440. forContinuation:YES];
  441. [self authenticateInteractivelyWithOptions:self->_currentOptions
  442. completion:self->_completion];
  443. });
  444. return YES;
  445. }
  446. # pragma mark - Helpers
  447. + (BOOL)isOperatingSystemAtLeast9 {
  448. NSProcessInfo *processInfo = [NSProcessInfo processInfo];
  449. return [processInfo respondsToSelector:@selector(isOperatingSystemAtLeastVersion:)] &&
  450. [processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){.majorVersion = 9}];
  451. }
  452. // Asserts the parameters being valid.
  453. - (void)assertValidParameters {
  454. if (![_currentOptions.configuration.clientID length]) {
  455. // NOLINTNEXTLINE(google-objc-avoid-throwing-exception)
  456. [NSException raise:NSInvalidArgumentException
  457. format:@"You must specify |clientID| in |GIDConfiguration|"];
  458. }
  459. }
  460. // Assert that the presenting view controller has been set.
  461. - (void)assertValidPresentingViewController {
  462. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  463. if (!_currentOptions.presentingViewController)
  464. #elif TARGET_OS_OSX
  465. if (!_currentOptions.presentingWindow)
  466. #endif // TARGET_OS_OSX
  467. {
  468. // NOLINTNEXTLINE(google-objc-avoid-throwing-exception)
  469. [NSException raise:NSInvalidArgumentException
  470. format:@"|presentingViewController| must be set."];
  471. }
  472. }
  473. - (OIDAuthState *)loadAuthState {
  474. GTMAppAuthFetcherAuthorization *authorization =
  475. [GTMAppAuthFetcherAuthorization authorizationFromKeychainForName:kGTMAppAuthKeychainName
  476. useDataProtectionKeychain:YES];
  477. return authorization.authState;
  478. }
  479. - (BOOL)saveAuthState:(OIDAuthState *)authState {
  480. GTMAppAuthFetcherAuthorization *authorization =
  481. [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState];
  482. return [GTMAppAuthFetcherAuthorization saveAuthorization:authorization
  483. toKeychainForName:kGTMAppAuthKeychainName
  484. useDataProtectionKeychain:YES];
  485. }
  486. // Generates user profile from OIDIDToken.
  487. - (GIDProfileData *)profileDataWithIDToken:(OIDIDToken *)idToken {
  488. if (!idToken ||
  489. !idToken.claims[kBasicProfilePictureKey] ||
  490. !idToken.claims[kBasicProfileNameKey] ||
  491. !idToken.claims[kBasicProfileGivenNameKey] ||
  492. !idToken.claims[kBasicProfileFamilyNameKey]) {
  493. return nil;
  494. }
  495. return [[GIDProfileData alloc]
  496. initWithEmail:idToken.claims[kBasicProfileEmailKey]
  497. name:idToken.claims[kBasicProfileNameKey]
  498. givenName:idToken.claims[kBasicProfileGivenNameKey]
  499. familyName:idToken.claims[kBasicProfileFamilyNameKey]
  500. imageURL:[NSURL URLWithString:idToken.claims[kBasicProfilePictureKey]]];
  501. }
  502. - (NSError *)errorWithString:(NSString *)errorString code:(GIDSignInErrorCode)code {
  503. if (errorString == nil) {
  504. errorString = @"Unknown error";
  505. }
  506. NSDictionary<NSString *, NSString *> *errorDict = @{ NSLocalizedDescriptionKey : errorString };
  507. return [NSError errorWithDomain:kGIDSignInErrorDomain
  508. code:code
  509. userInfo:errorDict];
  510. }
  511. @end
  512. NS_ASSUME_NONNULL_END