GIDSignIn.m 41 KB

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