GIDSignIn.m 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072
  1. // Copyright 2021 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h"
  15. #import "GoogleSignIn/Sources/GIDSignIn_Private.h"
  16. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDConfiguration.h"
  17. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h"
  18. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDProfileData.h"
  19. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDUserAuth.h"
  20. #import "GoogleSignIn/Sources/GIDAuthentication.h"
  21. #import "GoogleSignIn/Sources/GIDSignInInternalOptions.h"
  22. #import "GoogleSignIn/Sources/GIDSignInPreferences.h"
  23. #import "GoogleSignIn/Sources/GIDCallbackQueue.h"
  24. #import "GoogleSignIn/Sources/GIDScopes.h"
  25. #import "GoogleSignIn/Sources/GIDSignInCallbackSchemes.h"
  26. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  27. #import "GoogleSignIn/Sources/GIDAuthStateMigration.h"
  28. #import "GoogleSignIn/Sources/GIDEMMErrorHandler.h"
  29. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  30. #import "GoogleSignIn/Sources/GIDGoogleUser_Private.h"
  31. #import "GoogleSignIn/Sources/GIDProfileData_Private.h"
  32. #import "GoogleSignIn/Sources/GIDUserAuth_Private.h"
  33. #ifdef SWIFT_PACKAGE
  34. @import AppAuth;
  35. @import GTMAppAuth;
  36. @import GTMSessionFetcherCore;
  37. #else
  38. #import <AppAuth/OIDAuthState.h>
  39. #import <AppAuth/OIDAuthorizationRequest.h>
  40. #import <AppAuth/OIDAuthorizationResponse.h>
  41. #import <AppAuth/OIDAuthorizationService.h>
  42. #import <AppAuth/OIDError.h>
  43. #import <AppAuth/OIDExternalUserAgentSession.h>
  44. #import <AppAuth/OIDIDToken.h>
  45. #import <AppAuth/OIDResponseTypes.h>
  46. #import <AppAuth/OIDServiceConfiguration.h>
  47. #import <AppAuth/OIDTokenRequest.h>
  48. #import <AppAuth/OIDTokenResponse.h>
  49. #import <AppAuth/OIDURLQueryComponent.h>
  50. #import <GTMAppAuth/GTMAppAuthFetcherAuthorization+Keychain.h>
  51. #import <GTMAppAuth/GTMAppAuthFetcherAuthorization.h>
  52. #import <GTMSessionFetcher/GTMSessionFetcher.h>
  53. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  54. #import <AppAuth/OIDAuthorizationService+IOS.h>
  55. #elif TARGET_OS_OSX
  56. #import <AppAuth/OIDAuthorizationService+Mac.h>
  57. #endif
  58. #endif
  59. NS_ASSUME_NONNULL_BEGIN
  60. // The name of the query parameter used for logging the restart of auth from EMM callback.
  61. static NSString *const kEMMRestartAuthParameter = @"emmres";
  62. // The URL template for the authorization endpoint.
  63. static NSString *const kAuthorizationURLTemplate = @"https://%@/o/oauth2/v2/auth";
  64. // The URL template for the token endpoint.
  65. static NSString *const kTokenURLTemplate = @"https://%@/token";
  66. // The URL template for the URL to get user info.
  67. static NSString *const kUserInfoURLTemplate = @"https://%@/oauth2/v3/userinfo?access_token=%@";
  68. // The URL template for the URL to revoke the token.
  69. static NSString *const kRevokeTokenURLTemplate = @"https://%@/o/oauth2/revoke?token=%@";
  70. // Expected path in the URL scheme to be handled.
  71. static NSString *const kBrowserCallbackPath = @"/oauth2callback";
  72. // Expected path for EMM callback.
  73. static NSString *const kEMMCallbackPath = @"/emmcallback";
  74. // The EMM support version
  75. static NSString *const kEMMVersion = @"1";
  76. // The error code for Google Identity.
  77. NSErrorDomain const kGIDSignInErrorDomain = @"com.google.GIDSignIn";
  78. // Keychain constants for saving state in the authentication flow.
  79. static NSString *const kGTMAppAuthKeychainName = @"auth";
  80. // Basic profile (Fat ID Token / userinfo endpoint) keys
  81. static NSString *const kBasicProfileEmailKey = @"email";
  82. static NSString *const kBasicProfilePictureKey = @"picture";
  83. static NSString *const kBasicProfileNameKey = @"name";
  84. static NSString *const kBasicProfileGivenNameKey = @"given_name";
  85. static NSString *const kBasicProfileFamilyNameKey = @"family_name";
  86. // Parameters in the callback URL coming back from browser.
  87. static NSString *const kAuthorizationCodeKeyName = @"code";
  88. static NSString *const kOAuth2ErrorKeyName = @"error";
  89. static NSString *const kOAuth2AccessDenied = @"access_denied";
  90. static NSString *const kEMMPasscodeInfoRequiredKeyName = @"emm_passcode_info_required";
  91. // Error string for unavailable keychain.
  92. static NSString *const kKeychainError = @"keychain error";
  93. // Error string for user cancelations.
  94. static NSString *const kUserCanceledError = @"The user canceled the sign-in flow.";
  95. // User preference key to detect fresh install of the app.
  96. static NSString *const kAppHasRunBeforeKey = @"GID_AppHasRunBefore";
  97. // Maximum retry interval in seconds for the fetcher.
  98. static const NSTimeInterval kFetcherMaxRetryInterval = 15.0;
  99. // The delay before the new sign-in flow can be presented after the existing one is cancelled.
  100. static const NSTimeInterval kPresentationDelayAfterCancel = 1.0;
  101. // Parameters for the auth and token exchange endpoints.
  102. static NSString *const kAudienceParameter = @"audience";
  103. // See b/11669751 .
  104. static NSString *const kOpenIDRealmParameter = @"openid.realm";
  105. static NSString *const kIncludeGrantedScopesParameter = @"include_granted_scopes";
  106. static NSString *const kLoginHintParameter = @"login_hint";
  107. static NSString *const kHostedDomainParameter = @"hd";
  108. // Minimum time to expiration for a restored access token.
  109. static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
  110. // The callback queue used for authentication flow.
  111. @interface GIDAuthFlow : GIDCallbackQueue
  112. @property(nonatomic, strong, nullable) OIDAuthState *authState;
  113. @property(nonatomic, strong, nullable) NSError *error;
  114. @property(nonatomic, copy, nullable) NSString *emmSupport;
  115. @property(nonatomic, nullable) GIDProfileData *profileData;
  116. @end
  117. @implementation GIDAuthFlow
  118. @end
  119. @implementation GIDSignIn {
  120. // This value is used when sign-in flows are resumed via the handling of a URL. Its value is
  121. // set when a sign-in flow is begun via |signInWithOptions:| when the options passed don't
  122. // represent a sign in continuation.
  123. GIDSignInInternalOptions *_currentOptions;
  124. // AppAuth configuration object.
  125. OIDServiceConfiguration *_appAuthConfiguration;
  126. // AppAuth external user-agent session state.
  127. id<OIDExternalUserAgentSession> _currentAuthorizationFlow;
  128. // Flag to indicate that the auth flow is restarting.
  129. BOOL _restarting;
  130. }
  131. #pragma mark - Public methods
  132. - (BOOL)handleURL:(NSURL *)url {
  133. // Check if the callback path matches the expected one for a URL from Safari/Chrome/SafariVC.
  134. if ([url.path isEqual:kBrowserCallbackPath]) {
  135. if ([_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:url]) {
  136. _currentAuthorizationFlow = nil;
  137. return YES;
  138. }
  139. return NO;
  140. }
  141. // Check if the callback path matches the expected one for a URL from Google Device Policy app.
  142. if ([url.path isEqual:kEMMCallbackPath]) {
  143. return [self handleDevicePolicyAppURL:url];
  144. }
  145. return NO;
  146. }
  147. - (BOOL)hasPreviousSignIn {
  148. if ([_currentUser.authentication.authState isAuthorized]) {
  149. return YES;
  150. }
  151. OIDAuthState *authState = [self loadAuthState];
  152. return [authState isAuthorized];
  153. }
  154. - (void)restorePreviousSignInWithCompletion:(nullable void (^)(GIDGoogleUser *_Nullable user,
  155. NSError *_Nullable error))completion {
  156. [self signInWithOptions:[GIDSignInInternalOptions silentOptionsWithCompletion:
  157. ^(GIDUserAuth *userAuth, NSError *error) {
  158. if (userAuth) {
  159. completion(userAuth.user, nil);
  160. } else {
  161. completion(nil, error);
  162. }
  163. }]];
  164. }
  165. - (BOOL)restorePreviousSignInNoRefresh {
  166. if (_currentUser) {
  167. return YES;
  168. }
  169. // Try retrieving an authorization object from the keychain.
  170. OIDAuthState *authState = [self loadAuthState];
  171. if (!authState) {
  172. return NO;
  173. }
  174. // Restore current user without refreshing the access token.
  175. OIDIDToken *idToken =
  176. [[OIDIDToken alloc] initWithIDTokenString:authState.lastTokenResponse.idToken];
  177. GIDProfileData *profileData = [self profileDataWithIDToken:idToken];
  178. GIDGoogleUser *user = [[GIDGoogleUser alloc] initWithAuthState:authState profileData:profileData];
  179. [self setCurrentUserWithKVO:user];
  180. return YES;
  181. }
  182. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  183. - (void)signInWithConfiguration:(GIDConfiguration *)configuration
  184. presentingViewController:(UIViewController *)presentingViewController
  185. hint:(nullable NSString *)hint
  186. completion:(nullable GIDUserAuthCompletion)completion {
  187. GIDSignInInternalOptions *options =
  188. [GIDSignInInternalOptions defaultOptionsWithConfiguration:configuration
  189. presentingViewController:presentingViewController
  190. loginHint:hint
  191. addScopesFlow:NO
  192. completion:completion];
  193. [self signInWithOptions:options];
  194. }
  195. - (void)signInWithConfiguration:(GIDConfiguration *)configuration
  196. presentingViewController:(UIViewController *)presentingViewController
  197. hint:(nullable NSString *)hint
  198. additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
  199. completion:(nullable GIDUserAuthCompletion)completion {
  200. GIDSignInInternalOptions *options =
  201. [GIDSignInInternalOptions defaultOptionsWithConfiguration:configuration
  202. presentingViewController:presentingViewController
  203. loginHint:hint
  204. addScopesFlow:NO
  205. scopes:additionalScopes
  206. completion:completion];
  207. [self signInWithOptions:options];
  208. }
  209. - (void)signInWithConfiguration:(GIDConfiguration *)configuration
  210. presentingViewController:(UIViewController *)presentingViewController
  211. completion:(nullable GIDUserAuthCompletion)completion {
  212. [self signInWithConfiguration:configuration
  213. presentingViewController:presentingViewController
  214. hint:nil
  215. completion:completion];
  216. }
  217. - (void)addScopes:(NSArray<NSString *> *)scopes
  218. presentingViewController:(UIViewController *)presentingViewController
  219. completion:(nullable GIDUserAuthCompletion)completion {
  220. // A currentUser must be available in order to complete this flow.
  221. if (!self.currentUser) {
  222. // No currentUser is set, notify callback of failure.
  223. NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain
  224. code:kGIDSignInErrorCodeNoCurrentUser
  225. userInfo:nil];
  226. if (completion) {
  227. dispatch_async(dispatch_get_main_queue(), ^{
  228. completion(nil, error);
  229. });
  230. }
  231. return;
  232. }
  233. GIDConfiguration *configuration = self.currentUser.configuration;
  234. GIDSignInInternalOptions *options =
  235. [GIDSignInInternalOptions defaultOptionsWithConfiguration:configuration
  236. presentingViewController:presentingViewController
  237. loginHint:self.currentUser.profile.email
  238. addScopesFlow:YES
  239. completion:completion];
  240. NSSet<NSString *> *requestedScopes = [NSSet setWithArray:scopes];
  241. NSMutableSet<NSString *> *grantedScopes =
  242. [NSMutableSet setWithArray:self.currentUser.grantedScopes];
  243. // Check to see if all requested scopes have already been granted.
  244. if ([requestedScopes isSubsetOfSet:grantedScopes]) {
  245. // All requested scopes have already been granted, notify callback of failure.
  246. NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain
  247. code:kGIDSignInErrorCodeScopesAlreadyGranted
  248. userInfo:nil];
  249. if (completion) {
  250. dispatch_async(dispatch_get_main_queue(), ^{
  251. completion(nil, error);
  252. });
  253. }
  254. return;
  255. }
  256. // Use the union of granted and requested scopes.
  257. [grantedScopes unionSet:requestedScopes];
  258. options.scopes = [grantedScopes allObjects];
  259. [self signInWithOptions:options];
  260. }
  261. #elif TARGET_OS_OSX
  262. - (void)signInWithConfiguration:(GIDConfiguration *)configuration
  263. presentingWindow:(NSWindow *)presentingWindow
  264. hint:(nullable NSString *)hint
  265. completion:(nullable GIDUserAuthCompletion)completion {
  266. GIDSignInInternalOptions *options =
  267. [GIDSignInInternalOptions defaultOptionsWithConfiguration:configuration
  268. presentingWindow:presentingWindow
  269. loginHint:hint
  270. addScopesFlow:NO
  271. completion:completion];
  272. [self signInWithOptions:options];
  273. }
  274. - (void)signInWithConfiguration:(GIDConfiguration *)configuration
  275. presentingWindow:(NSWindow *)presentingWindow
  276. completion:(nullable GIDUserAuthCompletion)completion {
  277. [self signInWithConfiguration:configuration
  278. presentingWindow:presentingWindow
  279. hint:nil
  280. completion:completion];
  281. }
  282. - (void)signInWithConfiguration:(GIDConfiguration *)configuration
  283. presentingWindow:(NSWindow *)presentingWindow
  284. hint:(nullable NSString *)hint
  285. additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
  286. completion:(nullable GIDUserAuthCompletion)completion {
  287. GIDSignInInternalOptions *options =
  288. [GIDSignInInternalOptions defaultOptionsWithConfiguration:configuration
  289. presentingWindow:presentingWindow
  290. loginHint:hint
  291. addScopesFlow:NO
  292. scopes:additionalScopes
  293. completion:completion];
  294. [self signInWithOptions:options];
  295. }
  296. - (void)addScopes:(NSArray<NSString *> *)scopes
  297. presentingWindow:(NSWindow *)presentingWindow
  298. completion:(nullable GIDUserAuthCompletion)completion {
  299. // A currentUser must be available in order to complete this flow.
  300. if (!self.currentUser) {
  301. // No currentUser is set, notify callback of failure.
  302. NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain
  303. code:kGIDSignInErrorCodeNoCurrentUser
  304. userInfo:nil];
  305. if (completion) {
  306. dispatch_async(dispatch_get_main_queue(), ^{
  307. completion(nil, error);
  308. });
  309. }
  310. return;
  311. }
  312. GIDConfiguration *configuration = self.currentUser.configuration;
  313. GIDSignInInternalOptions *options =
  314. [GIDSignInInternalOptions defaultOptionsWithConfiguration:configuration
  315. presentingWindow:presentingWindow
  316. loginHint:self.currentUser.profile.email
  317. addScopesFlow:YES
  318. completion:completion];
  319. NSSet<NSString *> *requestedScopes = [NSSet setWithArray:scopes];
  320. NSMutableSet<NSString *> *grantedScopes =
  321. [NSMutableSet setWithArray:self.currentUser.grantedScopes];
  322. // Check to see if all requested scopes have already been granted.
  323. if ([requestedScopes isSubsetOfSet:grantedScopes]) {
  324. // All requested scopes have already been granted, notify callback of failure.
  325. NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain
  326. code:kGIDSignInErrorCodeScopesAlreadyGranted
  327. userInfo:nil];
  328. if (completion) {
  329. dispatch_async(dispatch_get_main_queue(), ^{
  330. completion(nil, error);
  331. });
  332. }
  333. return;
  334. }
  335. // Use the union of granted and requested scopes.
  336. [grantedScopes unionSet:requestedScopes];
  337. options.scopes = [grantedScopes allObjects];
  338. [self signInWithOptions:options];
  339. }
  340. #endif // TARGET_OS_OSX
  341. - (void)signOut {
  342. // Clear the current user if there is one.
  343. if (_currentUser) {
  344. [self willChangeValueForKey:NSStringFromSelector(@selector(currentUser))];
  345. _currentUser = nil;
  346. [self didChangeValueForKey:NSStringFromSelector(@selector(currentUser))];
  347. }
  348. // Remove all state from the keychain.
  349. [self removeAllKeychainEntries];
  350. }
  351. - (void)disconnectWithCompletion:(nullable GIDDisconnectCompletion)completion {
  352. GIDGoogleUser *user = _currentUser;
  353. OIDAuthState *authState = user.authentication.authState;
  354. if (!authState) {
  355. // Even the user is not signed in right now, we still need to remove any token saved in the
  356. // keychain.
  357. authState = [self loadAuthState];
  358. }
  359. // Either access or refresh token would work, but we won't have access token if the auth is
  360. // retrieved from keychain.
  361. NSString *token = authState.lastTokenResponse.accessToken;
  362. if (!token) {
  363. token = authState.lastTokenResponse.refreshToken;
  364. }
  365. if (!token) {
  366. [self signOut];
  367. // Nothing to do here, consider the operation successful.
  368. if (completion) {
  369. dispatch_async(dispatch_get_main_queue(), ^{
  370. completion(nil);
  371. });
  372. }
  373. return;
  374. }
  375. NSString *revokeURLString = [NSString stringWithFormat:kRevokeTokenURLTemplate,
  376. [GIDSignInPreferences googleAuthorizationServer], token];
  377. // Append logging parameter
  378. revokeURLString = [NSString stringWithFormat:@"%@&%@=%@&%@=%@",
  379. revokeURLString,
  380. kSDKVersionLoggingParameter,
  381. GIDVersion(),
  382. kEnvironmentLoggingParameter,
  383. GIDEnvironment()];
  384. NSURL *revokeURL = [NSURL URLWithString:revokeURLString];
  385. [self startFetchURL:revokeURL
  386. fromAuthState:authState
  387. withComment:@"GIDSignIn: revoke tokens"
  388. withCompletionHandler:^(NSData *data, NSError *error) {
  389. // Revoking an already revoked token seems always successful, which helps us here.
  390. if (!error) {
  391. [self signOut];
  392. }
  393. if (completion) {
  394. dispatch_async(dispatch_get_main_queue(), ^{
  395. completion(error);
  396. });
  397. }
  398. }];
  399. }
  400. #pragma mark - Custom getters and setters
  401. + (GIDSignIn *)sharedInstance {
  402. static dispatch_once_t once;
  403. static GIDSignIn *sharedInstance;
  404. dispatch_once(&once, ^{
  405. sharedInstance = [[self alloc] initPrivate];
  406. });
  407. return sharedInstance;
  408. }
  409. #pragma mark - Private methods
  410. - (id)initPrivate {
  411. self = [super init];
  412. if (self) {
  413. // Check to see if the 3P app is being run for the first time after a fresh install.
  414. BOOL isFreshInstall = [self isFreshInstall];
  415. // If this is a fresh install, ensure that any pre-existing keychain data is purged.
  416. if (isFreshInstall) {
  417. [self removeAllKeychainEntries];
  418. }
  419. NSString *authorizationEnpointURL = [NSString stringWithFormat:kAuthorizationURLTemplate,
  420. [GIDSignInPreferences googleAuthorizationServer]];
  421. NSString *tokenEndpointURL = [NSString stringWithFormat:kTokenURLTemplate,
  422. [GIDSignInPreferences googleTokenServer]];
  423. _appAuthConfiguration = [[OIDServiceConfiguration alloc]
  424. initWithAuthorizationEndpoint:[NSURL URLWithString:authorizationEnpointURL]
  425. tokenEndpoint:[NSURL URLWithString:tokenEndpointURL]];
  426. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  427. // Perform migration of auth state from old (before 5.0) versions of the SDK if needed.
  428. [GIDAuthStateMigration migrateIfNeededWithTokenURL:_appAuthConfiguration.tokenEndpoint
  429. callbackPath:kBrowserCallbackPath
  430. keychainName:kGTMAppAuthKeychainName
  431. isFreshInstall:isFreshInstall];
  432. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  433. }
  434. return self;
  435. }
  436. // Does sanity check for parameters and then authenticates if necessary.
  437. - (void)signInWithOptions:(GIDSignInInternalOptions *)options {
  438. // Options for continuation are not the options we want to cache. The purpose of caching the
  439. // options in the first place is to provide continuation flows with a starting place from which to
  440. // derive suitable options for the continuation!
  441. if (!options.continuation) {
  442. _currentOptions = options;
  443. }
  444. if (options.interactive) {
  445. // Explicitly throw exception for missing client ID here. This must come before
  446. // scheme check because schemes rely on reverse client IDs.
  447. [self assertValidParameters];
  448. [self assertValidPresentingViewController];
  449. // If the application does not support the required URL schemes tell the developer so.
  450. GIDSignInCallbackSchemes *schemes =
  451. [[GIDSignInCallbackSchemes alloc] initWithClientIdentifier:options.configuration.clientID];
  452. NSArray<NSString *> *unsupportedSchemes = [schemes unsupportedSchemes];
  453. if (unsupportedSchemes.count != 0) {
  454. // NOLINTNEXTLINE(google-objc-avoid-throwing-exception)
  455. [NSException raise:NSInvalidArgumentException
  456. format:@"Your app is missing support for the following URL schemes: %@",
  457. [unsupportedSchemes componentsJoinedByString:@", "]];
  458. }
  459. }
  460. // If this is a non-interactive flow, use cached authentication if possible.
  461. if (!options.interactive && _currentUser.authentication) {
  462. [_currentUser.authentication doWithFreshTokens:^(OIDAuthState *unused, NSError *error) {
  463. if (error) {
  464. [self authenticateWithOptions:options];
  465. } else {
  466. if (options.completion) {
  467. self->_currentOptions = nil;
  468. dispatch_async(dispatch_get_main_queue(), ^{
  469. GIDUserAuth *userAuth = [[GIDUserAuth alloc] initWithGoogleUser:self->_currentUser serverAuthCode:nil];
  470. options.completion(userAuth, nil);
  471. });
  472. }
  473. }
  474. }];
  475. } else {
  476. [self authenticateWithOptions:options];
  477. }
  478. }
  479. #pragma mark - Authentication flow
  480. - (void)authenticateInteractivelyWithOptions:(GIDSignInInternalOptions *)options {
  481. GIDSignInCallbackSchemes *schemes =
  482. [[GIDSignInCallbackSchemes alloc] initWithClientIdentifier:options.configuration.clientID];
  483. NSURL *redirectURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@:%@",
  484. [schemes clientIdentifierScheme],
  485. kBrowserCallbackPath]];
  486. NSString *emmSupport;
  487. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  488. emmSupport = [[self class] isOperatingSystemAtLeast9] ? kEMMVersion : nil;
  489. #elif TARGET_OS_MACCATALYST || TARGET_OS_OSX
  490. emmSupport = nil;
  491. #endif // TARGET_OS_MACCATALYST || TARGET_OS_OSX
  492. NSMutableDictionary<NSString *, NSString *> *additionalParameters = [@{} mutableCopy];
  493. additionalParameters[kIncludeGrantedScopesParameter] = @"true";
  494. if (options.configuration.serverClientID) {
  495. additionalParameters[kAudienceParameter] = options.configuration.serverClientID;
  496. }
  497. if (options.loginHint) {
  498. additionalParameters[kLoginHintParameter] = options.loginHint;
  499. }
  500. if (options.configuration.hostedDomain) {
  501. additionalParameters[kHostedDomainParameter] = options.configuration.hostedDomain;
  502. }
  503. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  504. [additionalParameters addEntriesFromDictionary:
  505. [GIDAuthentication parametersWithParameters:options.extraParams
  506. emmSupport:emmSupport
  507. isPasscodeInfoRequired:NO]];
  508. #elif TARGET_OS_OSX || TARGET_OS_MACCATALYST
  509. [additionalParameters addEntriesFromDictionary:options.extraParams];
  510. #endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
  511. additionalParameters[kSDKVersionLoggingParameter] = GIDVersion();
  512. additionalParameters[kEnvironmentLoggingParameter] = GIDEnvironment();
  513. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  514. OIDAuthorizationRequest *request =
  515. [[OIDAuthorizationRequest alloc] initWithConfiguration:_appAuthConfiguration
  516. clientId:options.configuration.clientID
  517. scopes:options.scopes
  518. redirectURL:redirectURL
  519. responseType:OIDResponseTypeCode
  520. additionalParameters:additionalParameters];
  521. _currentAuthorizationFlow = [OIDAuthorizationService
  522. presentAuthorizationRequest:request
  523. presentingViewController:options.presentingViewController
  524. callback:^(OIDAuthorizationResponse *_Nullable authorizationResponse,
  525. NSError *_Nullable error) {
  526. [self processAuthorizationResponse:authorizationResponse
  527. error:error
  528. emmSupport:emmSupport];
  529. }];
  530. #elif TARGET_OS_OSX
  531. OIDAuthorizationRequest *request =
  532. [[OIDAuthorizationRequest alloc] initWithConfiguration:_appAuthConfiguration
  533. clientId:options.configuration.clientID
  534. clientSecret:@""
  535. scopes:options.scopes
  536. redirectURL:redirectURL
  537. responseType:OIDResponseTypeCode
  538. additionalParameters:additionalParameters];
  539. _currentAuthorizationFlow = [OIDAuthorizationService
  540. presentAuthorizationRequest:request
  541. presentingWindow:options.presentingWindow
  542. callback:^(OIDAuthorizationResponse *_Nullable authorizationResponse,
  543. NSError *_Nullable error) {
  544. [self processAuthorizationResponse:authorizationResponse
  545. error:error
  546. emmSupport:emmSupport];
  547. }];
  548. #endif // TARGET_OS_OSX
  549. }
  550. - (void)processAuthorizationResponse:(OIDAuthorizationResponse *)authorizationResponse
  551. error:(NSError *)error
  552. emmSupport:(NSString *)emmSupport{
  553. if (_restarting) {
  554. // The auth flow is restarting, so the work here would be performed in the next round.
  555. _restarting = NO;
  556. return;
  557. }
  558. GIDAuthFlow *authFlow = [[GIDAuthFlow alloc] init];
  559. authFlow.emmSupport = emmSupport;
  560. if (authorizationResponse) {
  561. if (authorizationResponse.authorizationCode.length) {
  562. authFlow.authState = [[OIDAuthState alloc]
  563. initWithAuthorizationResponse:authorizationResponse];
  564. // perform auth code exchange
  565. [self maybeFetchToken:authFlow fallback:nil];
  566. } else {
  567. // There was a failure, convert to appropriate error code.
  568. NSString *errorString;
  569. GIDSignInErrorCode errorCode = kGIDSignInErrorCodeUnknown;
  570. NSDictionary<NSString *, NSObject *> *params = authorizationResponse.additionalParameters;
  571. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  572. if (authFlow.emmSupport) {
  573. [authFlow wait];
  574. BOOL isEMMError = [[GIDEMMErrorHandler sharedInstance]
  575. handleErrorFromResponse:params
  576. completion:^{
  577. [authFlow next];
  578. }];
  579. if (isEMMError) {
  580. errorCode = kGIDSignInErrorCodeEMM;
  581. }
  582. }
  583. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  584. errorString = (NSString *)params[kOAuth2ErrorKeyName];
  585. if ([errorString isEqualToString:kOAuth2AccessDenied]) {
  586. errorCode = kGIDSignInErrorCodeCanceled;
  587. }
  588. authFlow.error = [self errorWithString:errorString code:errorCode];
  589. }
  590. } else {
  591. NSString *errorString = [error localizedDescription];
  592. GIDSignInErrorCode errorCode = kGIDSignInErrorCodeUnknown;
  593. if (error.code == OIDErrorCodeUserCanceledAuthorizationFlow) {
  594. // The user has canceled the flow at the iOS modal dialog.
  595. errorString = kUserCanceledError;
  596. errorCode = kGIDSignInErrorCodeCanceled;
  597. }
  598. authFlow.error = [self errorWithString:errorString code:errorCode];
  599. }
  600. [self addDecodeIdTokenCallback:authFlow];
  601. [self addSaveAuthCallback:authFlow];
  602. [self addCompletionCallback:authFlow];
  603. }
  604. // Perform authentication with the provided options.
  605. - (void)authenticateWithOptions:(GIDSignInInternalOptions *)options {
  606. // If this is an interactive flow, we're not going to try to restore any saved auth state.
  607. if (options.interactive) {
  608. [self authenticateInteractivelyWithOptions:options];
  609. return;
  610. }
  611. // Try retrieving an authorization object from the keychain.
  612. OIDAuthState *authState = [self loadAuthState];
  613. if (![authState isAuthorized]) {
  614. // No valid auth in keychain, per documentation/spec, notify callback of failure.
  615. NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain
  616. code:kGIDSignInErrorCodeHasNoAuthInKeychain
  617. userInfo:nil];
  618. if (options.completion) {
  619. _currentOptions = nil;
  620. dispatch_async(dispatch_get_main_queue(), ^{
  621. options.completion(nil, error);
  622. });
  623. }
  624. return;
  625. }
  626. // Complete the auth flow using saved auth in keychain.
  627. GIDAuthFlow *authFlow = [[GIDAuthFlow alloc] init];
  628. authFlow.authState = authState;
  629. [self maybeFetchToken:authFlow fallback:options.interactive ? ^() {
  630. [self authenticateInteractivelyWithOptions:options];
  631. } : nil];
  632. [self addDecodeIdTokenCallback:authFlow];
  633. [self addSaveAuthCallback:authFlow];
  634. [self addCompletionCallback:authFlow];
  635. }
  636. // Fetches the access token if necessary as part of the auth flow. If |fallback|
  637. // is provided, call it instead of continuing the auth flow in case of error.
  638. - (void)maybeFetchToken:(GIDAuthFlow *)authFlow fallback:(nullable void (^)(void))fallback {
  639. OIDAuthState *authState = authFlow.authState;
  640. // Do nothing if we have an auth flow error or a restored access token that isn't near expiration.
  641. if (authFlow.error ||
  642. (authState.lastTokenResponse.accessToken &&
  643. [authState.lastTokenResponse.accessTokenExpirationDate timeIntervalSinceNow] >
  644. kMinimumRestoredAccessTokenTimeToExpire)) {
  645. return;
  646. }
  647. NSMutableDictionary<NSString *, NSString *> *additionalParameters = [@{} mutableCopy];
  648. if (_currentOptions.configuration.serverClientID) {
  649. additionalParameters[kAudienceParameter] = _currentOptions.configuration.serverClientID;
  650. }
  651. if (_currentOptions.configuration.openIDRealm) {
  652. additionalParameters[kOpenIDRealmParameter] = _currentOptions.configuration.openIDRealm;
  653. }
  654. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  655. NSDictionary<NSString *, NSObject *> *params =
  656. authState.lastAuthorizationResponse.additionalParameters;
  657. NSString *passcodeInfoRequired = (NSString *)params[kEMMPasscodeInfoRequiredKeyName];
  658. [additionalParameters addEntriesFromDictionary:
  659. [GIDAuthentication parametersWithParameters:@{}
  660. emmSupport:authFlow.emmSupport
  661. isPasscodeInfoRequired:passcodeInfoRequired.length > 0]];
  662. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  663. additionalParameters[kSDKVersionLoggingParameter] = GIDVersion();
  664. additionalParameters[kEnvironmentLoggingParameter] = GIDEnvironment();
  665. OIDTokenRequest *tokenRequest;
  666. if (!authState.lastTokenResponse.accessToken &&
  667. authState.lastAuthorizationResponse.authorizationCode) {
  668. tokenRequest = [authState.lastAuthorizationResponse
  669. tokenExchangeRequestWithAdditionalParameters:additionalParameters];
  670. } else {
  671. [additionalParameters
  672. addEntriesFromDictionary:authState.lastTokenResponse.request.additionalParameters];
  673. tokenRequest = [authState tokenRefreshRequestWithAdditionalParameters:additionalParameters];
  674. }
  675. [authFlow wait];
  676. [OIDAuthorizationService
  677. performTokenRequest:tokenRequest
  678. callback:^(OIDTokenResponse *_Nullable tokenResponse,
  679. NSError *_Nullable error) {
  680. [authState updateWithTokenResponse:tokenResponse error:error];
  681. authFlow.error = error;
  682. if (!tokenResponse.accessToken || error) {
  683. if (fallback) {
  684. [authFlow reset];
  685. fallback();
  686. return;
  687. }
  688. }
  689. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  690. if (authFlow.emmSupport) {
  691. [GIDAuthentication handleTokenFetchEMMError:error completion:^(NSError *error) {
  692. authFlow.error = error;
  693. [authFlow next];
  694. }];
  695. } else {
  696. [authFlow next];
  697. }
  698. #elif TARGET_OS_OSX || TARGET_OS_MACCATALYST
  699. [authFlow next];
  700. #endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
  701. }];
  702. }
  703. // Adds a callback to the auth flow to save the auth object to |self| and the keychain as well.
  704. - (void)addSaveAuthCallback:(GIDAuthFlow *)authFlow {
  705. __weak GIDAuthFlow *weakAuthFlow = authFlow;
  706. [authFlow addCallback:^() {
  707. GIDAuthFlow *handlerAuthFlow = weakAuthFlow;
  708. OIDAuthState *authState = handlerAuthFlow.authState;
  709. if (authState && !handlerAuthFlow.error) {
  710. if (![self saveAuthState:authState]) {
  711. handlerAuthFlow.error = [self errorWithString:kKeychainError
  712. code:kGIDSignInErrorCodeKeychain];
  713. return;
  714. }
  715. if (self->_currentOptions.addScopesFlow) {
  716. [self->_currentUser updateAuthState:authState
  717. profileData:handlerAuthFlow.profileData
  718. notifyTokenChanges:YES];
  719. } else {
  720. GIDGoogleUser *user = [[GIDGoogleUser alloc] initWithAuthState:authState
  721. profileData:handlerAuthFlow.profileData];
  722. [self setCurrentUserWithKVO:user];
  723. }
  724. }
  725. }];
  726. }
  727. // Adds a callback to the auth flow to extract user data from the ID token where available and
  728. // make a userinfo request if necessary.
  729. - (void)addDecodeIdTokenCallback:(GIDAuthFlow *)authFlow {
  730. __weak GIDAuthFlow *weakAuthFlow = authFlow;
  731. [authFlow addCallback:^() {
  732. GIDAuthFlow *handlerAuthFlow = weakAuthFlow;
  733. OIDAuthState *authState = handlerAuthFlow.authState;
  734. if (!authState || handlerAuthFlow.error) {
  735. return;
  736. }
  737. OIDIDToken *idToken =
  738. [[OIDIDToken alloc] initWithIDTokenString: authState.lastTokenResponse.idToken];
  739. // If the profile data are present in the ID token, use them.
  740. if (idToken) {
  741. handlerAuthFlow.profileData = [self profileDataWithIDToken:idToken];
  742. }
  743. // If we can't retrieve profile data from the ID token, make a userInfo request to fetch them.
  744. if (!handlerAuthFlow.profileData) {
  745. [handlerAuthFlow wait];
  746. NSURL *infoURL = [NSURL URLWithString:
  747. [NSString stringWithFormat:kUserInfoURLTemplate,
  748. [GIDSignInPreferences googleUserInfoServer],
  749. authState.lastTokenResponse.accessToken]];
  750. [self startFetchURL:infoURL
  751. fromAuthState:authState
  752. withComment:@"GIDSignIn: fetch basic profile info"
  753. withCompletionHandler:^(NSData *data, NSError *error) {
  754. if (data && !error) {
  755. NSError *jsonDeserializationError;
  756. NSDictionary<NSString *, NSString *> *profileDict =
  757. [NSJSONSerialization JSONObjectWithData:data
  758. options:NSJSONReadingMutableContainers
  759. error:&jsonDeserializationError];
  760. if (profileDict) {
  761. handlerAuthFlow.profileData = [[GIDProfileData alloc]
  762. initWithEmail:idToken.claims[kBasicProfileEmailKey]
  763. name:profileDict[kBasicProfileNameKey]
  764. givenName:profileDict[kBasicProfileGivenNameKey]
  765. familyName:profileDict[kBasicProfileFamilyNameKey]
  766. imageURL:[NSURL URLWithString:profileDict[kBasicProfilePictureKey]]];
  767. }
  768. }
  769. if (error) {
  770. handlerAuthFlow.error = error;
  771. }
  772. [handlerAuthFlow next];
  773. }];
  774. }
  775. }];
  776. }
  777. // Adds a callback to the auth flow to complete the flow by calling the sign-in callback.
  778. - (void)addCompletionCallback:(GIDAuthFlow *)authFlow {
  779. __weak GIDAuthFlow *weakAuthFlow = authFlow;
  780. [authFlow addCallback:^() {
  781. GIDAuthFlow *handlerAuthFlow = weakAuthFlow;
  782. if (self->_currentOptions.completion) {
  783. GIDUserAuthCompletion completion = self->_currentOptions.completion;
  784. self->_currentOptions = nil;
  785. dispatch_async(dispatch_get_main_queue(), ^{
  786. if (handlerAuthFlow.error) {
  787. completion(nil, handlerAuthFlow.error);
  788. } else {
  789. OIDAuthState *authState = handlerAuthFlow.authState;
  790. NSString *_Nullable serverAuthCode = [authState.lastTokenResponse.additionalParameters[@"server_code"] copy];
  791. GIDUserAuth *userAuth = [[GIDUserAuth alloc] initWithGoogleUser:self->_currentUser serverAuthCode:serverAuthCode];
  792. completion(userAuth, nil);
  793. }
  794. });
  795. }
  796. }];
  797. }
  798. - (void)startFetchURL:(NSURL *)URL
  799. fromAuthState:(OIDAuthState *)authState
  800. withComment:(NSString *)comment
  801. withCompletionHandler:(void (^)(NSData *, NSError *))handler {
  802. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
  803. GTMSessionFetcher *fetcher;
  804. GTMAppAuthFetcherAuthorization *authorization =
  805. [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState];
  806. id<GTMSessionFetcherServiceProtocol> fetcherService = authorization.fetcherService;
  807. if (fetcherService) {
  808. fetcher = [fetcherService fetcherWithRequest:request];
  809. } else {
  810. fetcher = [GTMSessionFetcher fetcherWithRequest:request];
  811. }
  812. fetcher.retryEnabled = YES;
  813. fetcher.maxRetryInterval = kFetcherMaxRetryInterval;
  814. fetcher.comment = comment;
  815. [fetcher beginFetchWithCompletionHandler:handler];
  816. }
  817. // Parse incoming URL from the Google Device Policy app.
  818. - (BOOL)handleDevicePolicyAppURL:(NSURL *)url {
  819. OIDURLQueryComponent *queryComponent = [[OIDURLQueryComponent alloc] initWithURL:url];
  820. NSDictionary<NSString *, NSObject<NSCopying> *> *params = queryComponent.dictionaryValue;
  821. NSObject<NSCopying> *actionParam = params[@"action"];
  822. NSString *actionString =
  823. [actionParam isKindOfClass:[NSString class]] ? (NSString *)actionParam : nil;
  824. if (![@"restart_auth" isEqualToString:actionString]) {
  825. return NO;
  826. }
  827. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  828. if (!_currentOptions.presentingViewController) {
  829. return NO;
  830. }
  831. #elif TARGET_OS_OSX
  832. if (!_currentOptions.presentingWindow) {
  833. return NO;
  834. }
  835. #endif // TARGET_OS_OSX
  836. if (!_currentAuthorizationFlow) {
  837. return NO;
  838. }
  839. _restarting = YES;
  840. [_currentAuthorizationFlow cancel];
  841. _currentAuthorizationFlow = nil;
  842. _restarting = NO;
  843. NSDictionary<NSString *, NSString *> *extraParameters = @{ kEMMRestartAuthParameter : @"1" };
  844. // In iOS 13 the presentation of ASWebAuthenticationSession needs an anchor window,
  845. // so we need to wait until the previous presentation is completely gone to ensure the right
  846. // anchor window is used here.
  847. dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
  848. (int64_t)(kPresentationDelayAfterCancel * NSEC_PER_SEC)),
  849. dispatch_get_main_queue(), ^{
  850. [self signInWithOptions:[self->_currentOptions optionsWithExtraParameters:extraParameters
  851. forContinuation:YES]];
  852. });
  853. return YES;
  854. }
  855. #pragma mark - Key-Value Observing
  856. // Override |NSObject(NSKeyValueObservingCustomization)| method in order to provide custom KVO
  857. // notifications for the |currentUser| property.
  858. + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
  859. if ([key isEqual:NSStringFromSelector(@selector(currentUser))]) {
  860. return NO;
  861. }
  862. return [super automaticallyNotifiesObserversForKey:key];
  863. }
  864. #pragma mark - Helpers
  865. - (NSError *)errorWithString:(NSString *)errorString code:(GIDSignInErrorCode)code {
  866. if (errorString == nil) {
  867. errorString = @"Unknown error";
  868. }
  869. NSDictionary<NSString *, NSString *> *errorDict = @{ NSLocalizedDescriptionKey : errorString };
  870. return [NSError errorWithDomain:kGIDSignInErrorDomain
  871. code:code
  872. userInfo:errorDict];
  873. }
  874. + (BOOL)isOperatingSystemAtLeast9 {
  875. NSProcessInfo *processInfo = [NSProcessInfo processInfo];
  876. return [processInfo respondsToSelector:@selector(isOperatingSystemAtLeastVersion:)] &&
  877. [processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){.majorVersion = 9}];
  878. }
  879. // Asserts the parameters being valid.
  880. - (void)assertValidParameters {
  881. if (![_currentOptions.configuration.clientID length]) {
  882. // NOLINTNEXTLINE(google-objc-avoid-throwing-exception)
  883. [NSException raise:NSInvalidArgumentException
  884. format:@"You must specify |clientID| in |GIDConfiguration|"];
  885. }
  886. }
  887. // Assert that the presenting view controller has been set.
  888. - (void)assertValidPresentingViewController {
  889. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  890. if (!_currentOptions.presentingViewController) {
  891. #elif TARGET_OS_OSX
  892. if (!_currentOptions.presentingWindow) {
  893. #endif // TARGET_OS_OSX
  894. // NOLINTNEXTLINE(google-objc-avoid-throwing-exception)
  895. [NSException raise:NSInvalidArgumentException
  896. format:@"|presentingViewController| must be set."];
  897. }
  898. }
  899. // Checks whether or not this is the first time the app runs.
  900. - (BOOL)isFreshInstall {
  901. NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  902. if ([defaults boolForKey:kAppHasRunBeforeKey]) {
  903. return NO;
  904. }
  905. [defaults setBool:YES forKey:kAppHasRunBeforeKey];
  906. return YES;
  907. }
  908. - (void)removeAllKeychainEntries {
  909. [GTMAppAuthFetcherAuthorization removeAuthorizationFromKeychainForName:kGTMAppAuthKeychainName
  910. useDataProtectionKeychain:YES];
  911. }
  912. - (BOOL)saveAuthState:(OIDAuthState *)authState {
  913. GTMAppAuthFetcherAuthorization *authorization =
  914. [[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState];
  915. return [GTMAppAuthFetcherAuthorization saveAuthorization:authorization
  916. toKeychainForName:kGTMAppAuthKeychainName
  917. useDataProtectionKeychain:YES];
  918. }
  919. - (OIDAuthState *)loadAuthState {
  920. GTMAppAuthFetcherAuthorization *authorization =
  921. [GTMAppAuthFetcherAuthorization authorizationFromKeychainForName:kGTMAppAuthKeychainName
  922. useDataProtectionKeychain:YES];
  923. return authorization.authState;
  924. }
  925. // Generates user profile from OIDIDToken.
  926. - (GIDProfileData *)profileDataWithIDToken:(OIDIDToken *)idToken {
  927. if (!idToken ||
  928. !idToken.claims[kBasicProfilePictureKey] ||
  929. !idToken.claims[kBasicProfileNameKey] ||
  930. !idToken.claims[kBasicProfileGivenNameKey] ||
  931. !idToken.claims[kBasicProfileFamilyNameKey]) {
  932. return nil;
  933. }
  934. return [[GIDProfileData alloc]
  935. initWithEmail:idToken.claims[kBasicProfileEmailKey]
  936. name:idToken.claims[kBasicProfileNameKey]
  937. givenName:idToken.claims[kBasicProfileGivenNameKey]
  938. familyName:idToken.claims[kBasicProfileFamilyNameKey]
  939. imageURL:[NSURL URLWithString:idToken.claims[kBasicProfilePictureKey]]];
  940. }
  941. // Set currentUser making appropriate KVO calls.
  942. - (void)setCurrentUserWithKVO:(GIDGoogleUser *_Nullable)user {
  943. [self willChangeValueForKey:NSStringFromSelector(@selector(currentUser))];
  944. _currentUser = user;
  945. [self didChangeValueForKey:NSStringFromSelector(@selector(currentUser))];
  946. }
  947. @end
  948. NS_ASSUME_NONNULL_END