GIDSignIn.m 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353
  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/GIDAuthStateMigration/GIDAuthStateMigration.h"
  21. #import "GoogleSignIn/Sources/GIDEMMSupport.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. #import "GoogleSignIn/Sources/GIDTokenClaimsInternalOptions.h"
  28. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  29. #import <AppCheckCore/GACAppCheckToken.h>
  30. #import "GoogleSignIn/Sources/GIDAppCheck/Implementations/GIDAppCheck.h"
  31. #import "GoogleSignIn/Sources/GIDAppCheck/UI/GIDActivityIndicatorViewController.h"
  32. #import "GoogleSignIn/Sources/GIDEMMErrorHandler.h"
  33. #import "GoogleSignIn/Sources/GIDTimedLoader/GIDTimedLoader.h"
  34. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  35. #import "GoogleSignIn/Sources/GIDGoogleUser_Private.h"
  36. #import "GoogleSignIn/Sources/GIDProfileData_Private.h"
  37. #import "GoogleSignIn/Sources/GIDSignInResult_Private.h"
  38. @import GTMAppAuth;
  39. #ifdef SWIFT_PACKAGE
  40. @import AppAuth;
  41. @import GTMSessionFetcherCore;
  42. #else
  43. #import <AppAuth/OIDAuthState.h>
  44. #import <AppAuth/OIDAuthorizationRequest.h>
  45. #import <AppAuth/OIDAuthorizationResponse.h>
  46. #import <AppAuth/OIDAuthorizationService.h>
  47. #import <AppAuth/OIDError.h>
  48. #import <AppAuth/OIDExternalUserAgentSession.h>
  49. #import <AppAuth/OIDIDToken.h>
  50. #import <AppAuth/OIDResponseTypes.h>
  51. #import <AppAuth/OIDServiceConfiguration.h>
  52. #import <AppAuth/OIDTokenRequest.h>
  53. #import <AppAuth/OIDTokenResponse.h>
  54. #import <AppAuth/OIDURLQueryComponent.h>
  55. #import <GTMSessionFetcher/GTMSessionFetcher.h>
  56. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  57. #import <AppAuth/OIDAuthorizationService+IOS.h>
  58. #elif TARGET_OS_OSX
  59. #import <AppAuth/OIDAuthorizationService+Mac.h>
  60. #endif
  61. #endif
  62. NS_ASSUME_NONNULL_BEGIN
  63. // The name of the query parameter used for logging the restart of auth from EMM callback.
  64. static NSString *const kEMMRestartAuthParameter = @"emmres";
  65. // The URL template for the authorization endpoint.
  66. static NSString *const kAuthorizationURLTemplate = @"https://%@/o/oauth2/v2/auth";
  67. // The URL template for the token endpoint.
  68. static NSString *const kTokenURLTemplate = @"https://%@/token";
  69. // The URL template for the URL to get user info.
  70. static NSString *const kUserInfoURLTemplate = @"https://%@/oauth2/v3/userinfo?access_token=%@";
  71. // The URL template for the URL to revoke the token.
  72. static NSString *const kRevokeTokenURLTemplate = @"https://%@/o/oauth2/revoke?token=%@";
  73. // Expected path in the URL scheme to be handled.
  74. static NSString *const kBrowserCallbackPath = @"/oauth2callback";
  75. // Expected path for EMM callback.
  76. static NSString *const kEMMCallbackPath = @"/emmcallback";
  77. // The EMM support version
  78. static NSString *const kEMMVersion = @"1";
  79. // The error code for Google Identity.
  80. NSErrorDomain const kGIDSignInErrorDomain = @"com.google.GIDSignIn";
  81. // Keychain constants for saving state in the authentication flow.
  82. static NSString *const kGTMAppAuthKeychainName = @"auth";
  83. // Basic profile (Fat ID Token / userinfo endpoint) keys
  84. static NSString *const kBasicProfileEmailKey = @"email";
  85. static NSString *const kBasicProfilePictureKey = @"picture";
  86. static NSString *const kBasicProfileNameKey = @"name";
  87. static NSString *const kBasicProfileGivenNameKey = @"given_name";
  88. static NSString *const kBasicProfileFamilyNameKey = @"family_name";
  89. // Parameters in the callback URL coming back from browser.
  90. static NSString *const kAuthorizationCodeKeyName = @"code";
  91. static NSString *const kOAuth2ErrorKeyName = @"error";
  92. static NSString *const kOAuth2AccessDenied = @"access_denied";
  93. static NSString *const kEMMPasscodeInfoRequiredKeyName = @"emm_passcode_info_required";
  94. // Error string for unavailable keychain.
  95. static NSString *const kKeychainError = @"keychain error";
  96. // Error string for user cancelations.
  97. static NSString *const kUserCanceledError = @"The user canceled the sign-in flow.";
  98. // User preference key to detect fresh install of the app.
  99. static NSString *const kAppHasRunBeforeKey = @"GID_AppHasRunBefore";
  100. // Maximum retry interval in seconds for the fetcher.
  101. static const NSTimeInterval kFetcherMaxRetryInterval = 15.0;
  102. // The delay before the new sign-in flow can be presented after the existing one is cancelled.
  103. static const NSTimeInterval kPresentationDelayAfterCancel = 1.0;
  104. // Parameters for the auth and token exchange endpoints.
  105. static NSString *const kAudienceParameter = @"audience";
  106. // See b/11669751 .
  107. static NSString *const kOpenIDRealmParameter = @"openid.realm";
  108. static NSString *const kIncludeGrantedScopesParameter = @"include_granted_scopes";
  109. static NSString *const kLoginHintParameter = @"login_hint";
  110. static NSString *const kHostedDomainParameter = @"hd";
  111. // Parameter for requesting the token claims.
  112. static NSString *const ktokenClaimsParameter = @"claims";
  113. // Parameters for auth and token exchange endpoints using App Attest.
  114. static NSString *const kClientAssertionParameter = @"client_assertion";
  115. static NSString *const kClientAssertionTypeParameter = @"client_assertion_type";
  116. static NSString *const kClientAssertionTypeParameterValue =
  117. @"urn:ietf:params:oauth:client-assertion-type:appcheck";
  118. // Minimum time to expiration for a restored access token.
  119. static const NSTimeInterval kMinimumRestoredAccessTokenTimeToExpire = 600.0;
  120. // Info.plist config keys
  121. static NSString *const kConfigClientIDKey = @"GIDClientID";
  122. static NSString *const kConfigServerClientIDKey = @"GIDServerClientID";
  123. static NSString *const kConfigHostedDomainKey = @"GIDHostedDomain";
  124. static NSString *const kConfigOpenIDRealmKey = @"GIDOpenIDRealm";
  125. // The callback queue used for authentication flow.
  126. @interface GIDAuthFlow : GIDCallbackQueue
  127. @property(nonatomic, strong, nullable) OIDAuthState *authState;
  128. @property(nonatomic, strong, nullable) NSError *error;
  129. @property(nonatomic, copy, nullable) NSString *emmSupport;
  130. @property(nonatomic, nullable) GIDProfileData *profileData;
  131. @end
  132. @implementation GIDAuthFlow
  133. @end
  134. @implementation GIDSignIn {
  135. // This value is used when sign-in flows are resumed via the handling of a URL. Its value is
  136. // set when a sign-in flow is begun via |signInWithOptions:| when the options passed don't
  137. // represent a sign in continuation.
  138. GIDSignInInternalOptions *_currentOptions;
  139. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  140. GIDAppCheck *_appCheck API_AVAILABLE(ios(14));
  141. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  142. // AppAuth configuration object.
  143. OIDServiceConfiguration *_appAuthConfiguration;
  144. // AppAuth external user-agent session state.
  145. id<OIDExternalUserAgentSession> _currentAuthorizationFlow;
  146. // Flag to indicate that the auth flow is restarting.
  147. BOOL _restarting;
  148. // Keychain manager for GTMAppAuth
  149. GTMKeychainStore *_keychainStore;
  150. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  151. // The class used to manage presenting the loading screen for fetching app check tokens.
  152. GIDTimedLoader *_timedLoader;
  153. // Flag indicating developer's intent to use App Check.
  154. BOOL _configureAppCheckCalled;
  155. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  156. }
  157. #pragma mark - Public methods
  158. // Handles the custom scheme URL opened by SFSafariViewController or the Device Policy App.
  159. //
  160. // For SFSafariViewController invoked via AppAuth, this method is used on iOS 10.
  161. // For the Device Policy App (EMM flow) this method is used on all iOS versions.
  162. - (BOOL)handleURL:(NSURL *)url {
  163. // Check if the callback path matches the expected one for a URL from Safari/Chrome/SafariVC.
  164. if ([url.path isEqual:kBrowserCallbackPath]) {
  165. if ([_currentAuthorizationFlow resumeExternalUserAgentFlowWithURL:url]) {
  166. _currentAuthorizationFlow = nil;
  167. return YES;
  168. }
  169. return NO;
  170. }
  171. // Check if the callback path matches the expected one for a URL from Google Device Policy app.
  172. if ([url.path isEqual:kEMMCallbackPath]) {
  173. return [self handleDevicePolicyAppURL:url];
  174. }
  175. return NO;
  176. }
  177. - (BOOL)hasPreviousSignIn {
  178. if ([_currentUser.authState isAuthorized]) {
  179. return YES;
  180. }
  181. OIDAuthState *authState = [self loadAuthState];
  182. return [authState isAuthorized];
  183. }
  184. - (void)restorePreviousSignInWithCompletion:(nullable void (^)(GIDGoogleUser *_Nullable user,
  185. NSError *_Nullable error))completion {
  186. [self signInWithOptions:[GIDSignInInternalOptions silentOptionsWithCompletion:
  187. ^(GIDSignInResult *signInResult, NSError *error) {
  188. if (!completion) {
  189. return;
  190. }
  191. if (signInResult) {
  192. completion(signInResult.user, nil);
  193. } else {
  194. completion(nil, error);
  195. }
  196. }]];
  197. }
  198. - (BOOL)restorePreviousSignInNoRefresh {
  199. if (_currentUser) {
  200. return YES;
  201. }
  202. // Try retrieving an authorization object from the keychain.
  203. OIDAuthState *authState = [self loadAuthState];
  204. if (!authState) {
  205. return NO;
  206. }
  207. // Restore current user without refreshing the access token.
  208. OIDIDToken *idToken =
  209. [[OIDIDToken alloc] initWithIDTokenString:authState.lastTokenResponse.idToken];
  210. GIDProfileData *profileData = [self profileDataWithIDToken:idToken];
  211. GIDGoogleUser *user = [[GIDGoogleUser alloc] initWithAuthState:authState profileData:profileData];
  212. self.currentUser = user;
  213. return YES;
  214. }
  215. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  216. - (void)signInWithPresentingViewController:(UIViewController *)presentingViewController
  217. hint:(nullable NSString *)hint
  218. completion:(nullable GIDSignInCompletion)completion {
  219. GIDSignInInternalOptions *options =
  220. [GIDSignInInternalOptions defaultOptionsWithConfiguration:_configuration
  221. presentingViewController:presentingViewController
  222. loginHint:hint
  223. addScopesFlow:NO
  224. completion:completion];
  225. [self signInWithOptions:options];
  226. }
  227. - (void)signInWithPresentingViewController:(UIViewController *)presentingViewController
  228. hint:(nullable NSString *)hint
  229. additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
  230. completion:(nullable GIDSignInCompletion)completion {
  231. [self signInWithPresentingViewController:presentingViewController
  232. hint:hint
  233. additionalScopes:additionalScopes
  234. nonce:nil
  235. completion:completion];
  236. }
  237. - (void)signInWithPresentingViewController:(UIViewController *)presentingViewController
  238. hint:(nullable NSString *)hint
  239. additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
  240. nonce:(nullable NSString *)nonce
  241. completion:(nullable GIDSignInCompletion)completion {
  242. [self signInWithPresentingViewController:presentingViewController
  243. hint:hint
  244. additionalScopes:additionalScopes
  245. nonce:nonce
  246. tokenClaims:nil
  247. completion:completion];
  248. }
  249. - (void)signInWithPresentingViewController:(UIViewController *)presentingViewController
  250. tokenClaims:(nullable NSSet<GIDTokenClaim *> *)tokenClaims
  251. completion:(nullable GIDSignInCompletion)completion {
  252. [self signInWithPresentingViewController:presentingViewController
  253. hint:nil
  254. tokenClaims:tokenClaims
  255. completion:completion];
  256. }
  257. - (void)signInWithPresentingViewController:(UIViewController *)presentingViewController
  258. hint:(nullable NSString *)hint
  259. tokenClaims:(nullable NSSet<GIDTokenClaim *> *)tokenClaims
  260. completion:(nullable GIDSignInCompletion)completion {
  261. [self signInWithPresentingViewController:presentingViewController
  262. hint:hint
  263. additionalScopes:@[]
  264. tokenClaims:tokenClaims
  265. completion:completion];
  266. }
  267. - (void)signInWithPresentingViewController:(UIViewController *)presentingViewController
  268. hint:(nullable NSString *)hint
  269. additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
  270. tokenClaims:(nullable NSSet<GIDTokenClaim *> *)tokenClaims
  271. completion:(nullable GIDSignInCompletion)completion {
  272. [self signInWithPresentingViewController:presentingViewController
  273. hint:hint
  274. additionalScopes:additionalScopes
  275. nonce:nil
  276. tokenClaims:tokenClaims
  277. completion:completion];
  278. }
  279. - (void)signInWithPresentingViewController:(UIViewController *)presentingViewController
  280. hint:(nullable NSString *)hint
  281. additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
  282. nonce:(nullable NSString *)nonce
  283. tokenClaims:(nullable NSSet<GIDTokenClaim *> *)tokenClaims
  284. completion:(nullable GIDSignInCompletion)completion {
  285. GIDSignInInternalOptions *options =
  286. [GIDSignInInternalOptions defaultOptionsWithConfiguration:_configuration
  287. presentingViewController:presentingViewController
  288. loginHint:hint
  289. addScopesFlow:NO
  290. scopes:additionalScopes
  291. nonce:nonce
  292. tokenClaims:tokenClaims
  293. completion:completion];
  294. [self signInWithOptions:options];
  295. }
  296. - (void)signInWithPresentingViewController:(UIViewController *)presentingViewController
  297. completion:(nullable GIDSignInCompletion)completion {
  298. [self signInWithPresentingViewController:presentingViewController
  299. hint:nil
  300. completion:completion];
  301. }
  302. - (void)addScopes:(NSArray<NSString *> *)scopes
  303. presentingViewController:(UIViewController *)presentingViewController
  304. completion:(nullable GIDSignInCompletion)completion {
  305. GIDConfiguration *configuration = self.currentUser.configuration;
  306. GIDSignInInternalOptions *options =
  307. [GIDSignInInternalOptions defaultOptionsWithConfiguration:configuration
  308. presentingViewController:presentingViewController
  309. loginHint:self.currentUser.profile.email
  310. addScopesFlow:YES
  311. completion:completion];
  312. NSSet<NSString *> *requestedScopes = [NSSet setWithArray:scopes];
  313. NSMutableSet<NSString *> *grantedScopes =
  314. [NSMutableSet setWithArray:self.currentUser.grantedScopes];
  315. // Check to see if all requested scopes have already been granted.
  316. if ([requestedScopes isSubsetOfSet:grantedScopes]) {
  317. // All requested scopes have already been granted, notify callback of failure.
  318. NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain
  319. code:kGIDSignInErrorCodeScopesAlreadyGranted
  320. userInfo:nil];
  321. if (completion) {
  322. dispatch_async(dispatch_get_main_queue(), ^{
  323. completion(nil, error);
  324. });
  325. }
  326. return;
  327. }
  328. // Use the union of granted and requested scopes.
  329. [grantedScopes unionSet:requestedScopes];
  330. options.scopes = [grantedScopes allObjects];
  331. [self signInWithOptions:options];
  332. }
  333. #elif TARGET_OS_OSX
  334. - (void)signInWithPresentingWindow:(NSWindow *)presentingWindow
  335. hint:(nullable NSString *)hint
  336. completion:(nullable GIDSignInCompletion)completion {
  337. GIDSignInInternalOptions *options =
  338. [GIDSignInInternalOptions defaultOptionsWithConfiguration:_configuration
  339. presentingWindow:presentingWindow
  340. loginHint:hint
  341. addScopesFlow:NO
  342. completion:completion];
  343. [self signInWithOptions:options];
  344. }
  345. - (void)signInWithPresentingWindow:(NSWindow *)presentingWindow
  346. completion:(nullable GIDSignInCompletion)completion {
  347. [self signInWithPresentingWindow:presentingWindow
  348. hint:nil
  349. completion:completion];
  350. }
  351. - (void)signInWithPresentingWindow:(NSWindow *)presentingWindow
  352. hint:(nullable NSString *)hint
  353. additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
  354. completion:(nullable GIDSignInCompletion)completion {
  355. [self signInWithPresentingWindow:presentingWindow
  356. hint:hint
  357. additionalScopes:additionalScopes
  358. nonce:nil
  359. completion:completion];
  360. }
  361. - (void)signInWithPresentingWindow:(NSWindow *)presentingWindow
  362. hint:(nullable NSString *)hint
  363. additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
  364. nonce:(nullable NSString *)nonce
  365. completion:(nullable GIDSignInCompletion)completion {
  366. [self signInWithPresentingWindow:presentingWindow
  367. hint:hint
  368. additionalScopes:additionalScopes
  369. nonce:nonce
  370. tokenClaims:nil
  371. completion:completion];
  372. }
  373. - (void)signInWithPresentingWindow:(NSWindow *)presentingWindow
  374. tokenClaims:(nullable NSSet<GIDTokenClaim *> *)tokenClaims
  375. completion:(nullable GIDSignInCompletion)completion {
  376. [self signInWithPresentingWindow:presentingWindow
  377. hint:nil
  378. tokenClaims:tokenClaims
  379. completion:completion];
  380. }
  381. - (void)signInWithPresentingWindow:(NSWindow *)presentingWindow
  382. hint:(nullable NSString *)hint
  383. tokenClaims:(nullable NSSet<GIDTokenClaim *> *)tokenClaims
  384. completion:(nullable GIDSignInCompletion)completion {
  385. [self signInWithPresentingWindow:presentingWindow
  386. hint:hint
  387. additionalScopes:@[]
  388. tokenClaims:tokenClaims
  389. completion:completion];
  390. }
  391. - (void)signInWithPresentingWindow:(NSWindow *)presentingWindow
  392. hint:(nullable NSString *)hint
  393. additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
  394. tokenClaims:(nullable NSSet<GIDTokenClaim *> *)tokenClaims
  395. completion:(nullable GIDSignInCompletion)completion {
  396. [self signInWithPresentingWindow:presentingWindow
  397. hint:hint
  398. additionalScopes:additionalScopes
  399. nonce:nil
  400. tokenClaims:tokenClaims
  401. completion:completion];
  402. }
  403. - (void)signInWithPresentingWindow:(NSWindow *)presentingWindow
  404. hint:(nullable NSString *)hint
  405. additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
  406. nonce:(nullable NSString *)nonce
  407. tokenClaims:(nullable NSSet<GIDTokenClaim *> *)tokenClaims
  408. completion:(nullable GIDSignInCompletion)completion {
  409. GIDSignInInternalOptions *options =
  410. [GIDSignInInternalOptions defaultOptionsWithConfiguration:_configuration
  411. presentingWindow:presentingWindow
  412. loginHint:hint
  413. addScopesFlow:NO
  414. scopes:additionalScopes
  415. nonce:nonce
  416. tokenClaims:tokenClaims
  417. completion:completion];
  418. [self signInWithOptions:options];
  419. }
  420. - (void)addScopes:(NSArray<NSString *> *)scopes
  421. presentingWindow:(NSWindow *)presentingWindow
  422. completion:(nullable GIDSignInCompletion)completion {
  423. GIDConfiguration *configuration = self.currentUser.configuration;
  424. GIDSignInInternalOptions *options =
  425. [GIDSignInInternalOptions defaultOptionsWithConfiguration:configuration
  426. presentingWindow:presentingWindow
  427. loginHint:self.currentUser.profile.email
  428. addScopesFlow:YES
  429. completion:completion];
  430. NSSet<NSString *> *requestedScopes = [NSSet setWithArray:scopes];
  431. NSMutableSet<NSString *> *grantedScopes =
  432. [NSMutableSet setWithArray:self.currentUser.grantedScopes];
  433. // Check to see if all requested scopes have already been granted.
  434. if ([requestedScopes isSubsetOfSet:grantedScopes]) {
  435. // All requested scopes have already been granted, notify callback of failure.
  436. NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain
  437. code:kGIDSignInErrorCodeScopesAlreadyGranted
  438. userInfo:nil];
  439. if (completion) {
  440. dispatch_async(dispatch_get_main_queue(), ^{
  441. completion(nil, error);
  442. });
  443. }
  444. return;
  445. }
  446. // Use the union of granted and requested scopes.
  447. [grantedScopes unionSet:requestedScopes];
  448. options.scopes = [grantedScopes allObjects];
  449. [self signInWithOptions:options];
  450. }
  451. #endif // TARGET_OS_OSX
  452. - (void)signOut {
  453. // Clear the current user if there is one.
  454. if (_currentUser) {
  455. self.currentUser = nil;
  456. }
  457. // Remove all state from the keychain.
  458. [self removeAllKeychainEntries];
  459. }
  460. - (void)disconnectWithCompletion:(nullable GIDDisconnectCompletion)completion {
  461. OIDAuthState *authState = _currentUser.authState;
  462. if (!authState) {
  463. // Even the user is not signed in right now, we still need to remove any token saved in the
  464. // keychain.
  465. authState = [self loadAuthState];
  466. }
  467. // Either access or refresh token would work, but we won't have access token if the auth is
  468. // retrieved from keychain.
  469. NSString *token = authState.lastTokenResponse.accessToken;
  470. if (!token) {
  471. token = authState.lastTokenResponse.refreshToken;
  472. }
  473. if (!token) {
  474. [self signOut];
  475. // Nothing to do here, consider the operation successful.
  476. if (completion) {
  477. dispatch_async(dispatch_get_main_queue(), ^{
  478. completion(nil);
  479. });
  480. }
  481. return;
  482. }
  483. NSString *revokeURLString = [NSString stringWithFormat:kRevokeTokenURLTemplate,
  484. [GIDSignInPreferences googleAuthorizationServer], token];
  485. // Append logging parameter
  486. revokeURLString = [NSString stringWithFormat:@"%@&%@=%@&%@=%@",
  487. revokeURLString,
  488. kSDKVersionLoggingParameter,
  489. GIDVersion(),
  490. kEnvironmentLoggingParameter,
  491. GIDEnvironment()];
  492. NSURL *revokeURL = [NSURL URLWithString:revokeURLString];
  493. [self startFetchURL:revokeURL
  494. fromAuthState:authState
  495. withComment:@"GIDSignIn: revoke tokens"
  496. withCompletionHandler:^(NSData *data, NSError *error) {
  497. // Revoking an already revoked token seems always successful, which helps us here.
  498. if (!error) {
  499. [self signOut];
  500. }
  501. if (completion) {
  502. dispatch_async(dispatch_get_main_queue(), ^{
  503. completion(error);
  504. });
  505. }
  506. }];
  507. }
  508. #pragma mark - Custom getters and setters
  509. + (GIDSignIn *)sharedInstance {
  510. static dispatch_once_t once;
  511. static GIDSignIn *sharedInstance;
  512. dispatch_once(&once, ^{
  513. GTMKeychainStore *keychainStore =
  514. [[GTMKeychainStore alloc] initWithItemName:kGTMAppAuthKeychainName];
  515. GIDAuthStateMigration *authStateMigrationService =
  516. [[GIDAuthStateMigration alloc] initWithKeychainStore:keychainStore];
  517. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  518. if (@available(iOS 14.0, *)) {
  519. GIDAppCheck *appCheck = [GIDAppCheck appCheckUsingAppAttestProvider];
  520. sharedInstance = [[self alloc] initWithKeychainStore:keychainStore
  521. authStateMigrationService:authStateMigrationService
  522. appCheck:appCheck];
  523. }
  524. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  525. if (!sharedInstance) {
  526. sharedInstance = [[self alloc] initWithKeychainStore:keychainStore
  527. authStateMigrationService:authStateMigrationService];
  528. }
  529. });
  530. return sharedInstance;
  531. }
  532. #pragma mark - Configuring and pre-warming
  533. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  534. - (void)configureWithCompletion:(nullable void (^)(NSError * _Nullable))completion {
  535. @synchronized(self) {
  536. _configureAppCheckCalled = YES;
  537. [_appCheck prepareForAppCheckWithCompletion:^(NSError * _Nullable error) {
  538. if (completion) {
  539. completion(error);
  540. }
  541. }];
  542. }
  543. }
  544. - (void)configureDebugProviderWithAPIKey:(NSString *)APIKey
  545. completion:(nullable void (^)(NSError * _Nullable))completion {
  546. @synchronized(self) {
  547. _appCheck = [GIDAppCheck appCheckUsingDebugProviderWithAPIKey:APIKey];
  548. [_appCheck prepareForAppCheckWithCompletion:^(NSError * _Nullable error) {
  549. if (completion) {
  550. completion(error);
  551. }
  552. }];
  553. }
  554. }
  555. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  556. #pragma mark - Private methods
  557. - (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore
  558. authStateMigrationService:(GIDAuthStateMigration *)authStateMigrationService {
  559. self = [super init];
  560. if (self) {
  561. _keychainStore = keychainStore;
  562. // Get the bundle of the current executable.
  563. NSBundle *bundle = NSBundle.mainBundle;
  564. // If we have a bundle, try to set the active configuration from the bundle's Info.plist.
  565. if (bundle) {
  566. _configuration = [GIDSignIn configurationFromBundle:bundle];
  567. }
  568. // Check to see if the 3P app is being run for the first time after a fresh install.
  569. BOOL isFreshInstall = [self isFreshInstall];
  570. NSString *authorizationEnpointURL = [NSString stringWithFormat:kAuthorizationURLTemplate,
  571. [GIDSignInPreferences googleAuthorizationServer]];
  572. NSString *tokenEndpointURL = [NSString stringWithFormat:kTokenURLTemplate,
  573. [GIDSignInPreferences googleTokenServer]];
  574. _appAuthConfiguration = [[OIDServiceConfiguration alloc]
  575. initWithAuthorizationEndpoint:[NSURL URLWithString:authorizationEnpointURL]
  576. tokenEndpoint:[NSURL URLWithString:tokenEndpointURL]];
  577. // Perform migration of auth state from old versions of the SDK if needed.
  578. [authStateMigrationService migrateIfNeededWithTokenURL:_appAuthConfiguration.tokenEndpoint
  579. callbackPath:kBrowserCallbackPath
  580. isFreshInstall:isFreshInstall];
  581. }
  582. return self;
  583. }
  584. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  585. - (instancetype)initWithKeychainStore:(GTMKeychainStore *)keychainStore
  586. authStateMigrationService:(GIDAuthStateMigration *)authStateMigrationService
  587. appCheck:(GIDAppCheck *)appCheck {
  588. self = [self initWithKeychainStore:keychainStore
  589. authStateMigrationService:authStateMigrationService];
  590. if (self) {
  591. _appCheck = appCheck;
  592. _configureAppCheckCalled = NO;
  593. }
  594. return self;
  595. }
  596. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  597. // Does sanity check for parameters and then authenticates if necessary.
  598. - (void)signInWithOptions:(GIDSignInInternalOptions *)options {
  599. // Options for continuation are not the options we want to cache. The purpose of caching the
  600. // options in the first place is to provide continuation flows with a starting place from which to
  601. // derive suitable options for the continuation!
  602. if (!options.continuation) {
  603. _currentOptions = options;
  604. }
  605. if (options.interactive) {
  606. // Ensure that a configuration has been provided.
  607. if (!_configuration) {
  608. // NOLINTNEXTLINE(google-objc-avoid-throwing-exception)
  609. [NSException raise:NSInvalidArgumentException
  610. format:@"No active configuration. Make sure GIDClientID is set in Info.plist."];
  611. return;
  612. }
  613. // Explicitly throw exception for missing client ID here. This must come before
  614. // scheme check because schemes rely on reverse client IDs.
  615. [self assertValidParameters];
  616. [self assertValidPresentingViewController];
  617. // If the application does not support the required URL schemes tell the developer so.
  618. GIDSignInCallbackSchemes *schemes =
  619. [[GIDSignInCallbackSchemes alloc] initWithClientIdentifier:options.configuration.clientID];
  620. NSArray<NSString *> *unsupportedSchemes = [schemes unsupportedSchemes];
  621. if (unsupportedSchemes.count != 0) {
  622. // NOLINTNEXTLINE(google-objc-avoid-throwing-exception)
  623. [NSException raise:NSInvalidArgumentException
  624. format:@"Your app is missing support for the following URL schemes: %@",
  625. [unsupportedSchemes componentsJoinedByString:@", "]];
  626. }
  627. }
  628. // If this is a non-interactive flow, use cached authentication if possible.
  629. if (!options.interactive && _currentUser) {
  630. [_currentUser refreshTokensIfNeededWithCompletion:^(GIDGoogleUser *unused, NSError *error) {
  631. if (error) {
  632. [self authenticateWithOptions:options];
  633. } else {
  634. if (options.completion) {
  635. self->_currentOptions = nil;
  636. dispatch_async(dispatch_get_main_queue(), ^{
  637. GIDSignInResult *signInResult =
  638. [[GIDSignInResult alloc] initWithGoogleUser:self->_currentUser serverAuthCode:nil];
  639. options.completion(signInResult, nil);
  640. });
  641. }
  642. }
  643. }];
  644. } else {
  645. // Validating tokenClaims and json serialization of the tokenClaims before proceeding with
  646. // the new interactive authentication.
  647. if (options.tokenClaims) {
  648. NSError *claimsError = nil;
  649. NSString *tokenClaimsAsJSON =
  650. [GIDTokenClaimsInternalOptions validatedJSONStringForClaims:options.tokenClaims
  651. error:&claimsError];
  652. if (!tokenClaimsAsJSON && claimsError) {
  653. if (options.completion) {
  654. self->_currentOptions = nil; // clean up the existng state.
  655. dispatch_async(dispatch_get_main_queue(), ^{
  656. options.completion(nil, claimsError);
  657. });
  658. }
  659. return;
  660. }
  661. options.tokenClaimsAsJSON = tokenClaimsAsJSON;
  662. }
  663. [self authenticateWithOptions:options];
  664. }
  665. }
  666. #pragma mark - Authentication flow
  667. - (void)authenticateInteractivelyWithOptions:(GIDSignInInternalOptions *)options {
  668. NSString *emmSupport;
  669. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  670. emmSupport = [[self class] isOperatingSystemAtLeast9] ? kEMMVersion : nil;
  671. #elif TARGET_OS_MACCATALYST || TARGET_OS_OSX
  672. emmSupport = nil;
  673. #endif // TARGET_OS_MACCATALYST || TARGET_OS_OSX
  674. [self authorizationRequestWithOptions:options
  675. completion:^(OIDAuthorizationRequest * _Nullable request,
  676. NSError * _Nullable error) {
  677. self->_currentAuthorizationFlow =
  678. [OIDAuthorizationService presentAuthorizationRequest:request
  679. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  680. presentingViewController:options.presentingViewController
  681. #elif TARGET_OS_OSX
  682. presentingWindow:options.presentingWindow
  683. #endif // TARGET_OS_OSX
  684. callback:
  685. ^(OIDAuthorizationResponse *_Nullable authorizationResponse,
  686. NSError *_Nullable error) {
  687. [self processAuthorizationResponse:authorizationResponse
  688. error:error
  689. emmSupport:emmSupport];
  690. }];
  691. }];
  692. }
  693. - (void)authorizationRequestWithOptions:(GIDSignInInternalOptions *)options completion:
  694. (void (^)(OIDAuthorizationRequest *_Nullable request, NSError *_Nullable error))completion {
  695. BOOL shouldCreateAuthRequest = YES;
  696. NSMutableDictionary<NSString *, NSString *> *additionalParameters =
  697. [self additionalParametersFromOptions:options];
  698. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  699. if (@available(iOS 14.0, *)) {
  700. // Only use `_appCheck` (created via singleton `+[GIDSignIn sharedInstance]` call) if
  701. // `GIDAppCheck` has been successfully prepared OR if the developer has attempted to configure.
  702. // If former is false and the latter true, then preparation step failed for some reason; we
  703. // still want to try to pass along the app check token (it just may take longer since the
  704. // pre-warm step failed).
  705. if ([_appCheck isPrepared] || _configureAppCheckCalled) {
  706. shouldCreateAuthRequest = NO;
  707. UIViewController *presentingVC = options.presentingViewController;
  708. if (!_timedLoader) {
  709. _timedLoader = [[GIDTimedLoader alloc] initWithPresentingViewController:presentingVC];
  710. }
  711. [_timedLoader startTiming];
  712. [self->_appCheck getLimitedUseTokenWithCompletion:^(GACAppCheckToken * _Nullable token,
  713. NSError * _Nullable error) {
  714. if (token) {
  715. additionalParameters[kClientAssertionTypeParameter] = kClientAssertionTypeParameterValue;
  716. additionalParameters[kClientAssertionParameter] = token.token;
  717. }
  718. #if DEBUG
  719. if (error) {
  720. NSLog(@"[Google Sign-In iOS]: Error retrieving App Check limited use token: %@", error);
  721. }
  722. #endif
  723. OIDAuthorizationRequest *request = [self authorizationRequestWithOptions:options
  724. additionalParameters:additionalParameters];
  725. if (self->_timedLoader.animationStatus == GIDTimedLoaderAnimationStatusAnimating) {
  726. [self->_timedLoader stopTimingWithCompletion:^{
  727. completion(request, error);
  728. }];
  729. } else {
  730. completion(request, error);
  731. }
  732. }];
  733. }
  734. }
  735. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  736. if (shouldCreateAuthRequest) {
  737. OIDAuthorizationRequest *request = [self authorizationRequestWithOptions:options
  738. additionalParameters:additionalParameters];
  739. completion(request, nil);
  740. }
  741. }
  742. - (OIDAuthorizationRequest *)
  743. authorizationRequestWithOptions:(GIDSignInInternalOptions *)options
  744. additionalParameters:(NSDictionary<NSString *, NSString *> *)additionalParameters {
  745. OIDAuthorizationRequest *request;
  746. if (options.nonce) {
  747. request = [[OIDAuthorizationRequest alloc] initWithConfiguration:_appAuthConfiguration
  748. clientId:options.configuration.clientID
  749. scopes:options.scopes
  750. redirectURL:[self redirectURLWithOptions:options]
  751. responseType:OIDResponseTypeCode
  752. nonce:options.nonce
  753. additionalParameters:additionalParameters];
  754. } else {
  755. request = [[OIDAuthorizationRequest alloc] initWithConfiguration:_appAuthConfiguration
  756. clientId:options.configuration.clientID
  757. scopes:options.scopes
  758. redirectURL:[self redirectURLWithOptions:options]
  759. responseType:OIDResponseTypeCode
  760. additionalParameters:additionalParameters];
  761. }
  762. return request;
  763. }
  764. - (NSMutableDictionary<NSString *, NSString *> *)
  765. additionalParametersFromOptions:(GIDSignInInternalOptions *)options {
  766. NSString *emmSupport;
  767. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  768. emmSupport = [[self class] isOperatingSystemAtLeast9] ? kEMMVersion : nil;
  769. #elif TARGET_OS_MACCATALYST || TARGET_OS_OSX
  770. emmSupport = nil;
  771. #endif // TARGET_OS_MACCATALYST || TARGET_OS_OSX
  772. NSMutableDictionary<NSString *, NSString *> *additionalParameters =
  773. [[NSMutableDictionary alloc] init];
  774. additionalParameters[kIncludeGrantedScopesParameter] = @"true";
  775. if (options.configuration.serverClientID) {
  776. additionalParameters[kAudienceParameter] = options.configuration.serverClientID;
  777. }
  778. if (options.loginHint) {
  779. additionalParameters[kLoginHintParameter] = options.loginHint;
  780. }
  781. if (options.configuration.hostedDomain) {
  782. additionalParameters[kHostedDomainParameter] = options.configuration.hostedDomain;
  783. }
  784. if (options.tokenClaimsAsJSON) {
  785. additionalParameters[ktokenClaimsParameter] = options.tokenClaimsAsJSON;
  786. }
  787. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  788. [additionalParameters addEntriesFromDictionary:
  789. [GIDEMMSupport parametersWithParameters:options.extraParams
  790. emmSupport:emmSupport
  791. isPasscodeInfoRequired:NO]];
  792. #elif TARGET_OS_OSX || TARGET_OS_MACCATALYST
  793. [additionalParameters addEntriesFromDictionary:options.extraParams];
  794. #endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
  795. additionalParameters[kSDKVersionLoggingParameter] = GIDVersion();
  796. additionalParameters[kEnvironmentLoggingParameter] = GIDEnvironment();
  797. return additionalParameters;
  798. }
  799. - (NSURL *)redirectURLWithOptions:(GIDSignInInternalOptions *)options {
  800. GIDSignInCallbackSchemes *schemes =
  801. [[GIDSignInCallbackSchemes alloc] initWithClientIdentifier:options.configuration.clientID];
  802. NSURL *redirectURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@:%@",
  803. [schemes clientIdentifierScheme],
  804. kBrowserCallbackPath]];
  805. return redirectURL;
  806. }
  807. - (void)processAuthorizationResponse:(OIDAuthorizationResponse *)authorizationResponse
  808. error:(NSError *)error
  809. emmSupport:(NSString *)emmSupport {
  810. if (_restarting) {
  811. // The auth flow is restarting, so the work here would be performed in the next round.
  812. _restarting = NO;
  813. return;
  814. }
  815. GIDAuthFlow *authFlow = [[GIDAuthFlow alloc] init];
  816. authFlow.emmSupport = emmSupport;
  817. if (authorizationResponse) {
  818. if (authorizationResponse.authorizationCode.length) {
  819. authFlow.authState = [[OIDAuthState alloc]
  820. initWithAuthorizationResponse:authorizationResponse];
  821. // perform auth code exchange
  822. [self maybeFetchToken:authFlow];
  823. } else {
  824. // There was a failure, convert to appropriate error code.
  825. NSString *errorString;
  826. GIDSignInErrorCode errorCode = kGIDSignInErrorCodeUnknown;
  827. NSDictionary<NSString *, NSObject *> *params = authorizationResponse.additionalParameters;
  828. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  829. if (authFlow.emmSupport) {
  830. [authFlow wait];
  831. BOOL isEMMError = [[GIDEMMErrorHandler sharedInstance]
  832. handleErrorFromResponse:params
  833. completion:^{
  834. [authFlow next];
  835. }];
  836. if (isEMMError) {
  837. errorCode = kGIDSignInErrorCodeEMM;
  838. }
  839. }
  840. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  841. errorString = (NSString *)params[kOAuth2ErrorKeyName];
  842. if ([errorString isEqualToString:kOAuth2AccessDenied]) {
  843. errorCode = kGIDSignInErrorCodeCanceled;
  844. }
  845. authFlow.error = [self errorWithString:errorString code:errorCode];
  846. }
  847. } else {
  848. NSString *errorString = [error localizedDescription];
  849. GIDSignInErrorCode errorCode = kGIDSignInErrorCodeUnknown;
  850. if (error.code == OIDErrorCodeUserCanceledAuthorizationFlow ||
  851. error.code == OIDErrorCodeProgramCanceledAuthorizationFlow) {
  852. // The user has canceled the flow at the iOS modal dialog.
  853. errorString = kUserCanceledError;
  854. errorCode = kGIDSignInErrorCodeCanceled;
  855. }
  856. authFlow.error = [self errorWithString:errorString code:errorCode];
  857. }
  858. [self addDecodeIdTokenCallback:authFlow];
  859. [self addSaveAuthCallback:authFlow];
  860. [self addCompletionCallback:authFlow];
  861. }
  862. // Perform authentication with the provided options.
  863. - (void)authenticateWithOptions:(GIDSignInInternalOptions *)options {
  864. // If this is an interactive flow, we're not going to try to restore any saved auth state.
  865. if (options.interactive) {
  866. [self authenticateInteractivelyWithOptions:options];
  867. return;
  868. }
  869. // Try retrieving an authorization object from the keychain.
  870. OIDAuthState *authState = [self loadAuthState];
  871. if (![authState isAuthorized]) {
  872. // No valid auth in keychain, per documentation/spec, notify callback of failure.
  873. NSError *error = [NSError errorWithDomain:kGIDSignInErrorDomain
  874. code:kGIDSignInErrorCodeHasNoAuthInKeychain
  875. userInfo:nil];
  876. if (options.completion) {
  877. _currentOptions = nil;
  878. dispatch_async(dispatch_get_main_queue(), ^{
  879. options.completion(nil, error);
  880. });
  881. }
  882. return;
  883. }
  884. // Complete the auth flow using saved auth in keychain.
  885. GIDAuthFlow *authFlow = [[GIDAuthFlow alloc] init];
  886. authFlow.authState = authState;
  887. [self maybeFetchToken:authFlow];
  888. [self addDecodeIdTokenCallback:authFlow];
  889. [self addSaveAuthCallback:authFlow];
  890. [self addCompletionCallback:authFlow];
  891. }
  892. // Fetches the access token if necessary as part of the auth flow.
  893. - (void)maybeFetchToken:(GIDAuthFlow *)authFlow {
  894. OIDAuthState *authState = authFlow.authState;
  895. // Do nothing if we have an auth flow error or a restored access token that isn't near expiration.
  896. if (authFlow.error ||
  897. (authState.lastTokenResponse.accessToken &&
  898. [authState.lastTokenResponse.accessTokenExpirationDate timeIntervalSinceNow] >
  899. kMinimumRestoredAccessTokenTimeToExpire)) {
  900. return;
  901. }
  902. NSMutableDictionary<NSString *, NSString *> *additionalParameters = [@{} mutableCopy];
  903. if (_currentOptions.configuration.serverClientID) {
  904. additionalParameters[kAudienceParameter] = _currentOptions.configuration.serverClientID;
  905. }
  906. if (_currentOptions.configuration.openIDRealm) {
  907. additionalParameters[kOpenIDRealmParameter] = _currentOptions.configuration.openIDRealm;
  908. }
  909. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  910. NSDictionary<NSString *, NSObject *> *params =
  911. authState.lastAuthorizationResponse.additionalParameters;
  912. NSString *passcodeInfoRequired = (NSString *)params[kEMMPasscodeInfoRequiredKeyName];
  913. [additionalParameters addEntriesFromDictionary:
  914. [GIDEMMSupport parametersWithParameters:@{}
  915. emmSupport:authFlow.emmSupport
  916. isPasscodeInfoRequired:passcodeInfoRequired.length > 0]];
  917. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  918. additionalParameters[kSDKVersionLoggingParameter] = GIDVersion();
  919. additionalParameters[kEnvironmentLoggingParameter] = GIDEnvironment();
  920. OIDTokenRequest *tokenRequest;
  921. if (!authState.lastTokenResponse.accessToken &&
  922. authState.lastAuthorizationResponse.authorizationCode) {
  923. tokenRequest = [authState.lastAuthorizationResponse
  924. tokenExchangeRequestWithAdditionalParameters:additionalParameters];
  925. } else {
  926. [additionalParameters
  927. addEntriesFromDictionary:authState.lastTokenResponse.request.additionalParameters];
  928. tokenRequest = [authState tokenRefreshRequestWithAdditionalParameters:additionalParameters];
  929. }
  930. [authFlow wait];
  931. [OIDAuthorizationService performTokenRequest:tokenRequest
  932. originalAuthorizationResponse:authFlow.authState.lastAuthorizationResponse
  933. callback:^(OIDTokenResponse *_Nullable tokenResponse,
  934. NSError *_Nullable error) {
  935. [authState updateWithTokenResponse:tokenResponse error:error];
  936. authFlow.error = error;
  937. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  938. if (authFlow.emmSupport) {
  939. [GIDEMMSupport handleTokenFetchEMMError:error completion:^(NSError *error) {
  940. authFlow.error = error;
  941. [authFlow next];
  942. }];
  943. } else {
  944. [authFlow next];
  945. }
  946. #elif TARGET_OS_OSX || TARGET_OS_MACCATALYST
  947. [authFlow next];
  948. #endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
  949. }];
  950. }
  951. // Adds a callback to the auth flow to save the auth object to |self| and the keychain as well.
  952. - (void)addSaveAuthCallback:(GIDAuthFlow *)authFlow {
  953. __weak GIDAuthFlow *weakAuthFlow = authFlow;
  954. [authFlow addCallback:^() {
  955. GIDAuthFlow *handlerAuthFlow = weakAuthFlow;
  956. OIDAuthState *authState = handlerAuthFlow.authState;
  957. if (authState && !handlerAuthFlow.error) {
  958. if (![self saveAuthState:authState]) {
  959. handlerAuthFlow.error = [self errorWithString:kKeychainError
  960. code:kGIDSignInErrorCodeKeychain];
  961. return;
  962. }
  963. if (self->_currentOptions.addScopesFlow) {
  964. [self->_currentUser updateWithTokenResponse:authState.lastTokenResponse
  965. authorizationResponse:authState.lastAuthorizationResponse
  966. profileData:handlerAuthFlow.profileData];
  967. } else {
  968. GIDGoogleUser *user = [[GIDGoogleUser alloc] initWithAuthState:authState
  969. profileData:handlerAuthFlow.profileData];
  970. self.currentUser = user;
  971. }
  972. }
  973. }];
  974. }
  975. // Adds a callback to the auth flow to extract user data from the ID token where available and
  976. // make a userinfo request if necessary.
  977. - (void)addDecodeIdTokenCallback:(GIDAuthFlow *)authFlow {
  978. __weak GIDAuthFlow *weakAuthFlow = authFlow;
  979. [authFlow addCallback:^() {
  980. GIDAuthFlow *handlerAuthFlow = weakAuthFlow;
  981. OIDAuthState *authState = handlerAuthFlow.authState;
  982. if (!authState || handlerAuthFlow.error) {
  983. return;
  984. }
  985. OIDIDToken *idToken =
  986. [[OIDIDToken alloc] initWithIDTokenString: authState.lastTokenResponse.idToken];
  987. // If the profile data are present in the ID token, use them.
  988. if (idToken) {
  989. handlerAuthFlow.profileData = [self profileDataWithIDToken:idToken];
  990. }
  991. // If we can't retrieve profile data from the ID token, make a userInfo request to fetch them.
  992. if (!handlerAuthFlow.profileData) {
  993. [handlerAuthFlow wait];
  994. NSURL *infoURL = [NSURL URLWithString:
  995. [NSString stringWithFormat:kUserInfoURLTemplate,
  996. [GIDSignInPreferences googleUserInfoServer],
  997. authState.lastTokenResponse.accessToken]];
  998. [self startFetchURL:infoURL
  999. fromAuthState:authState
  1000. withComment:@"GIDSignIn: fetch basic profile info"
  1001. withCompletionHandler:^(NSData *data, NSError *error) {
  1002. if (data && !error) {
  1003. NSError *jsonDeserializationError;
  1004. NSDictionary<NSString *, NSString *> *profileDict =
  1005. [NSJSONSerialization JSONObjectWithData:data
  1006. options:NSJSONReadingMutableContainers
  1007. error:&jsonDeserializationError];
  1008. if (profileDict) {
  1009. handlerAuthFlow.profileData = [[GIDProfileData alloc]
  1010. initWithEmail:idToken.claims[kBasicProfileEmailKey]
  1011. name:profileDict[kBasicProfileNameKey]
  1012. givenName:profileDict[kBasicProfileGivenNameKey]
  1013. familyName:profileDict[kBasicProfileFamilyNameKey]
  1014. imageURL:[NSURL URLWithString:profileDict[kBasicProfilePictureKey]]];
  1015. }
  1016. }
  1017. if (error) {
  1018. handlerAuthFlow.error = error;
  1019. }
  1020. [handlerAuthFlow next];
  1021. }];
  1022. }
  1023. }];
  1024. }
  1025. // Adds a callback to the auth flow to complete the flow by calling the sign-in callback.
  1026. - (void)addCompletionCallback:(GIDAuthFlow *)authFlow {
  1027. __weak GIDAuthFlow *weakAuthFlow = authFlow;
  1028. [authFlow addCallback:^() {
  1029. GIDAuthFlow *handlerAuthFlow = weakAuthFlow;
  1030. if (self->_currentOptions.completion) {
  1031. GIDSignInCompletion completion = self->_currentOptions.completion;
  1032. self->_currentOptions = nil;
  1033. dispatch_async(dispatch_get_main_queue(), ^{
  1034. if (handlerAuthFlow.error) {
  1035. completion(nil, handlerAuthFlow.error);
  1036. } else {
  1037. OIDAuthState *authState = handlerAuthFlow.authState;
  1038. NSString *_Nullable serverAuthCode =
  1039. [authState.lastTokenResponse.additionalParameters[@"server_code"] copy];
  1040. GIDSignInResult *signInResult =
  1041. [[GIDSignInResult alloc] initWithGoogleUser:self->_currentUser
  1042. serverAuthCode:serverAuthCode];
  1043. completion(signInResult, nil);
  1044. }
  1045. });
  1046. }
  1047. }];
  1048. }
  1049. - (void)startFetchURL:(NSURL *)URL
  1050. fromAuthState:(OIDAuthState *)authState
  1051. withComment:(NSString *)comment
  1052. withCompletionHandler:(void (^)(NSData *, NSError *))handler {
  1053. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
  1054. GTMSessionFetcher *fetcher;
  1055. GTMAuthSession *authorization = [[GTMAuthSession alloc] initWithAuthState:authState];
  1056. id<GTMSessionFetcherServiceProtocol> fetcherService = authorization.fetcherService;
  1057. if (fetcherService) {
  1058. fetcher = [fetcherService fetcherWithRequest:request];
  1059. } else {
  1060. fetcher = [GTMSessionFetcher fetcherWithRequest:request];
  1061. }
  1062. fetcher.retryEnabled = YES;
  1063. fetcher.maxRetryInterval = kFetcherMaxRetryInterval;
  1064. fetcher.comment = comment;
  1065. [fetcher beginFetchWithCompletionHandler:handler];
  1066. }
  1067. // Parse incoming URL from the Google Device Policy app.
  1068. - (BOOL)handleDevicePolicyAppURL:(NSURL *)url {
  1069. OIDURLQueryComponent *queryComponent = [[OIDURLQueryComponent alloc] initWithURL:url];
  1070. NSDictionary<NSString *, NSObject<NSCopying> *> *params = queryComponent.dictionaryValue;
  1071. NSObject<NSCopying> *actionParam = params[@"action"];
  1072. NSString *actionString =
  1073. [actionParam isKindOfClass:[NSString class]] ? (NSString *)actionParam : nil;
  1074. if (![@"restart_auth" isEqualToString:actionString]) {
  1075. return NO;
  1076. }
  1077. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  1078. if (!_currentOptions.presentingViewController) {
  1079. return NO;
  1080. }
  1081. #elif TARGET_OS_OSX
  1082. if (!_currentOptions.presentingWindow) {
  1083. return NO;
  1084. }
  1085. #endif // TARGET_OS_OSX
  1086. if (!_currentAuthorizationFlow) {
  1087. return NO;
  1088. }
  1089. _restarting = YES;
  1090. [_currentAuthorizationFlow cancel];
  1091. _currentAuthorizationFlow = nil;
  1092. _restarting = NO;
  1093. NSDictionary<NSString *, NSString *> *extraParameters = @{ kEMMRestartAuthParameter : @"1" };
  1094. // In iOS 13 the presentation of ASWebAuthenticationSession needs an anchor window,
  1095. // so we need to wait until the previous presentation is completely gone to ensure the right
  1096. // anchor window is used here.
  1097. dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
  1098. (int64_t)(kPresentationDelayAfterCancel * NSEC_PER_SEC)),
  1099. dispatch_get_main_queue(), ^{
  1100. [self signInWithOptions:[self->_currentOptions optionsWithExtraParameters:extraParameters
  1101. forContinuation:YES]];
  1102. });
  1103. return YES;
  1104. }
  1105. #pragma mark - Helpers
  1106. - (NSError *)errorWithString:(NSString *)errorString code:(GIDSignInErrorCode)code {
  1107. if (errorString == nil) {
  1108. errorString = @"Unknown error";
  1109. }
  1110. NSDictionary<NSString *, NSString *> *errorDict = @{ NSLocalizedDescriptionKey : errorString };
  1111. return [NSError errorWithDomain:kGIDSignInErrorDomain
  1112. code:code
  1113. userInfo:errorDict];
  1114. }
  1115. + (BOOL)isOperatingSystemAtLeast9 {
  1116. NSProcessInfo *processInfo = [NSProcessInfo processInfo];
  1117. return [processInfo respondsToSelector:@selector(isOperatingSystemAtLeastVersion:)] &&
  1118. [processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){.majorVersion = 9}];
  1119. }
  1120. // Asserts the parameters being valid.
  1121. - (void)assertValidParameters {
  1122. if (![_currentOptions.configuration.clientID length]) {
  1123. // NOLINTNEXTLINE(google-objc-avoid-throwing-exception)
  1124. [NSException raise:NSInvalidArgumentException
  1125. format:@"You must specify |clientID| in |GIDConfiguration|"];
  1126. }
  1127. }
  1128. // Assert that the presenting view controller has been set.
  1129. - (void)assertValidPresentingViewController {
  1130. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  1131. if (!_currentOptions.presentingViewController)
  1132. #elif TARGET_OS_OSX
  1133. if (!_currentOptions.presentingWindow)
  1134. #endif // TARGET_OS_OSX
  1135. {
  1136. // NOLINTNEXTLINE(google-objc-avoid-throwing-exception)
  1137. [NSException raise:NSInvalidArgumentException
  1138. format:@"|presentingViewController| must be set."];
  1139. }
  1140. }
  1141. // Checks whether or not this is the first time the app runs.
  1142. - (BOOL)isFreshInstall {
  1143. NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  1144. if ([defaults boolForKey:kAppHasRunBeforeKey]) {
  1145. return NO;
  1146. }
  1147. [defaults setBool:YES forKey:kAppHasRunBeforeKey];
  1148. return YES;
  1149. }
  1150. - (void)removeAllKeychainEntries {
  1151. [_keychainStore removeAuthSessionWithError:nil];
  1152. }
  1153. - (BOOL)saveAuthState:(OIDAuthState *)authState {
  1154. GTMAuthSession *authorization = [[GTMAuthSession alloc] initWithAuthState:authState];
  1155. NSError *error;
  1156. [_keychainStore saveAuthSession:authorization error:&error];
  1157. return error == nil;
  1158. }
  1159. - (OIDAuthState *)loadAuthState {
  1160. GTMAuthSession *authorization = [_keychainStore retrieveAuthSessionWithError:nil];
  1161. return authorization.authState;
  1162. }
  1163. // Generates user profile from OIDIDToken.
  1164. - (GIDProfileData *)profileDataWithIDToken:(OIDIDToken *)idToken {
  1165. if (!idToken ||
  1166. !idToken.claims[kBasicProfilePictureKey] ||
  1167. !idToken.claims[kBasicProfileNameKey] ||
  1168. !idToken.claims[kBasicProfileGivenNameKey] ||
  1169. !idToken.claims[kBasicProfileFamilyNameKey]) {
  1170. return nil;
  1171. }
  1172. return [[GIDProfileData alloc]
  1173. initWithEmail:idToken.claims[kBasicProfileEmailKey]
  1174. name:idToken.claims[kBasicProfileNameKey]
  1175. givenName:idToken.claims[kBasicProfileGivenNameKey]
  1176. familyName:idToken.claims[kBasicProfileFamilyNameKey]
  1177. imageURL:[NSURL URLWithString:idToken.claims[kBasicProfilePictureKey]]];
  1178. }
  1179. // Try to retrieve a configuration value from an |NSBundle|'s Info.plist for a given key.
  1180. + (nullable NSString *)configValueFromBundle:(NSBundle *)bundle forKey:(NSString *)key {
  1181. NSString *value;
  1182. id configValue = [bundle objectForInfoDictionaryKey:key];
  1183. if ([configValue isKindOfClass:[NSString class]]) {
  1184. value = configValue;
  1185. }
  1186. return value;
  1187. }
  1188. // Try to generate a |GIDConfiguration| from an |NSBundle|'s Info.plist.
  1189. + (nullable GIDConfiguration *)configurationFromBundle:(NSBundle *)bundle {
  1190. GIDConfiguration *configuration;
  1191. // Retrieve any valid config parameters from the bundle's Info.plist.
  1192. NSString *clientID = [GIDSignIn configValueFromBundle:bundle forKey:kConfigClientIDKey];
  1193. NSString *serverClientID = [GIDSignIn configValueFromBundle:bundle
  1194. forKey:kConfigServerClientIDKey];
  1195. NSString *hostedDomain = [GIDSignIn configValueFromBundle:bundle forKey:kConfigHostedDomainKey];
  1196. NSString *openIDRealm = [GIDSignIn configValueFromBundle:bundle forKey:kConfigOpenIDRealmKey];
  1197. // If we have at least a client ID, try to construct a configuration.
  1198. if (clientID) {
  1199. configuration = [[GIDConfiguration alloc] initWithClientID:clientID
  1200. serverClientID:serverClientID
  1201. hostedDomain:hostedDomain
  1202. openIDRealm:openIDRealm];
  1203. }
  1204. return configuration;
  1205. }
  1206. @end
  1207. NS_ASSUME_NONNULL_END