GIDSignIn.m 49 KB

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