GIDUserAuthFlowController.m 25 KB

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