GIDSignInTest.m 60 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551
  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 <TargetConditionals.h>
  15. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  16. #import <UIKit/UIKit.h>
  17. #elif TARGET_OS_OSX
  18. #import <AppKit/AppKit.h>
  19. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  20. #import <SafariServices/SafariServices.h>
  21. #import <XCTest/XCTest.h>
  22. // Test module imports
  23. @import GoogleSignIn;
  24. @import GTMAppAuth;
  25. #import "GoogleSignIn/Sources/GIDEMMSupport.h"
  26. #import "GoogleSignIn/Sources/GIDGoogleUser_Private.h"
  27. #import "GoogleSignIn/Sources/GIDSignIn_Private.h"
  28. #import "GoogleSignIn/Sources/GIDSignInPreferences.h"
  29. #import "GoogleSignIn/Sources/GIDHTTPFetcher/Implementations/Fakes/GIDFakeHTTPFetcher.h"
  30. #import "GoogleSignIn/Sources/GIDHTTPFetcher/Implementations/GIDHTTPFetcher.h"
  31. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  32. #import "GoogleSignIn/Sources/GIDEMMErrorHandler.h"
  33. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  34. #import "GoogleSignIn/Tests/Unit/GIDFakeMainBundle.h"
  35. #import "GoogleSignIn/Tests/Unit/OIDAuthorizationResponse+Testing.h"
  36. #import "GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h"
  37. #ifdef SWIFT_PACKAGE
  38. @import AppAuth;
  39. @import GTMSessionFetcherCore;
  40. @import OCMock;
  41. #else
  42. #import <AppAuth/OIDAuthState.h>
  43. #import <AppAuth/OIDAuthorizationRequest.h>
  44. #import <AppAuth/OIDAuthorizationResponse.h>
  45. #import <AppAuth/OIDAuthorizationService.h>
  46. #import <AppAuth/OIDError.h>
  47. #import <AppAuth/OIDGrantTypes.h>
  48. #import <AppAuth/OIDTokenRequest.h>
  49. #import <AppAuth/OIDTokenResponse.h>
  50. #import <AppAuth/OIDURLQueryComponent.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 // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  56. #import <GTMSessionFetcher/GTMSessionFetcher.h>
  57. #import <OCMock/OCMock.h>
  58. #endif
  59. // Create a BLOCK to store the actual address for arg in param.
  60. #define SAVE_TO_ARG_BLOCK(param) [OCMArg checkWithBlock:^(id arg) {\
  61. param = arg;\
  62. return YES;\
  63. }]
  64. #define COPY_TO_ARG_BLOCK(param) [OCMArg checkWithBlock:^(id arg) {\
  65. param = [arg copy];\
  66. return YES;\
  67. }]
  68. static NSString * const kFakeGaiaID = @"123456789";
  69. static NSString * const kFakeIDToken = @"FakeIDToken";
  70. static NSString * const kClientId = @"FakeClientID";
  71. static NSString * const kDotReversedClientId = @"FakeClientID";
  72. static NSString * const kClientId2 = @"FakeClientID2";
  73. static NSString * const kServerClientId = @"FakeServerClientID";
  74. static NSString * const kLanguage = @"FakeLanguage";
  75. static NSString * const kScope = @"FakeScope";
  76. static NSString * const kScope2 = @"FakeScope2";
  77. static NSString * const kAuthCode = @"FakeAuthCode";
  78. static NSString * const kKeychainName = @"auth";
  79. static NSString * const kUserEmail = @"FakeUserEmail";
  80. static NSString * const kVerifier = @"FakeVerifier";
  81. static NSString * const kOpenIDRealm = @"FakeRealm";
  82. static NSString * const kFakeHostedDomain = @"fakehosteddomain.com";
  83. static NSString * const kFakeUserName = @"fake username";
  84. static NSString * const kFakeUserGivenName = @"fake";
  85. static NSString * const kFakeUserFamilyName = @"username";
  86. static NSString * const kFakeUserPictureURL = @"fake_user_picture_url";
  87. static NSString * const kContinueURL = @"com.google.UnitTests:/oauth2callback";
  88. static NSString * const kContinueURLWithClientID = @"FakeClientID:/oauth2callback";
  89. static NSString * const kWrongSchemeURL = @"wrong.app:/oauth2callback";
  90. static NSString * const kWrongPathURL = @"com.google.UnitTests:/wrong_path";
  91. static NSString * const kEMMRestartAuthURL =
  92. @"com.google.UnitTests:///emmcallback?action=restart_auth";
  93. static NSString * const kEMMWrongPathURL =
  94. @"com.google.UnitTests:///unknowcallback?action=restart_auth";
  95. static NSString * const kEMMWrongActionURL =
  96. @"com.google.UnitTests:///emmcallback?action=unrecognized";
  97. static NSString * const kDevicePolicyAppBundleID = @"com.google.DevicePolicy";
  98. static NSString * const kAppHasRunBeforeKey = @"GPP_AppHasRunBefore";
  99. static NSString * const kFingerprintKeychainName = @"fingerprint";
  100. static NSString * const kVerifierKeychainName = @"verifier";
  101. static NSString * const kVerifierKey = @"verifier";
  102. static NSString * const kOpenIDRealmKey = @"openid.realm";
  103. static NSString * const kSavedKeychainServiceName = @"saved-keychain";
  104. static NSString * const kKeychainAccountName = @"GooglePlus";
  105. static NSString * const kUserNameKey = @"name";
  106. static NSString * const kUserGivenNameKey = @"givenName";
  107. static NSString * const kUserFamilyNameKey = @"familyName";
  108. static NSString * const kUserImageKey = @"picture";
  109. static NSString * const kAppName = @"UnitTests";
  110. static NSString * const kUserIDKey = @"userID";
  111. static NSString * const kHostedDomainKey = @"hostedDomain";
  112. static NSString * const kIDTokenExpirationKey = @"idTokenExp";
  113. static NSString * const kScopeKey = @"scope";
  114. // Basic profile (Fat ID Token / userinfo endpoint) keys
  115. static NSString *const kBasicProfilePictureKey = @"picture";
  116. static NSString *const kBasicProfileNameKey = @"name";
  117. static NSString *const kBasicProfileGivenNameKey = @"given_name";
  118. static NSString *const kBasicProfileFamilyNameKey = @"family_name";
  119. static NSString * const kCustomKeychainName = @"CUSTOM_KEYCHAIN_NAME";
  120. static NSString * const kAddActivity = @"http://schemas.google.com/AddActivity";
  121. static NSString * const kErrorDomain = @"ERROR_DOMAIN";
  122. static NSInteger const kErrorCode = 212;
  123. static NSString *const kDriveScope = @"https://www.googleapis.com/auth/drive";
  124. static NSString *const kTokenURL = @"https://oauth2.googleapis.com/token";
  125. static NSString *const kFakeURL = @"http://foo.com";
  126. static NSString *const kEMMSupport = @"1";
  127. static NSString *const kGrantedScope = @"grantedScope";
  128. static NSString *const kNewScope = @"newScope";
  129. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  130. // This category is used to allow the test to swizzle a private method.
  131. @interface UIViewController (Testing)
  132. // This private method provides access to the window. It's declared here to avoid a warning about
  133. // an unrecognized selector in the test.
  134. - (UIWindow *)_window;
  135. @end
  136. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  137. // This class extension exposes GIDSignIn methods to our tests.
  138. @interface GIDSignIn ()
  139. // Exposing private method so we can call it to disambiguate between interactive and non-interactive
  140. // sign-in attempts for the purposes of testing the GIDSignInUIDelegate (which should not be
  141. // called in the case of a non-interactive sign in).
  142. - (void)authenticateMaybeInteractively:(BOOL)interactive withParams:(NSDictionary *)params;
  143. - (BOOL)assertValidPresentingViewContoller;
  144. @end
  145. @interface GIDSignInTest : XCTestCase {
  146. @private
  147. // Whether or not the OS version is eligible for EMM.
  148. BOOL _isEligibleForEMM;
  149. // Mock |OIDAuthState|.
  150. id _authState;
  151. // Mock |OIDTokenResponse|.
  152. id _tokenResponse;
  153. // Mock |OIDTokenRequest|.
  154. id _tokenRequest;
  155. // Mock |GTMAuthSession|.
  156. id _authorization;
  157. // Mock |GTMKeychainStore|.
  158. id _keychainStore;
  159. // Mock |GTMSessionFetcherServiceProtocol|.
  160. id _fetcherService;
  161. // Fake for |GIDHTTPFetcher|.
  162. GIDFakeHTTPFetcher *_httpFetcher;
  163. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  164. // Mock |UIViewController|.
  165. id _presentingViewController;
  166. #elif TARGET_OS_OSX
  167. // Mock |NSWindow|.
  168. id _presentingWindow;
  169. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  170. // Mock for |GIDGoogleUser|.
  171. id _user;
  172. // Mock for |OIDAuthorizationService|
  173. id _oidAuthorizationService;
  174. // Parameter saved from delegate call.
  175. NSError *_authError;
  176. // Whether callback block has been called.
  177. BOOL _completionCalled;
  178. // Fake [NSBundle mainBundle];
  179. GIDFakeMainBundle *_fakeMainBundle;
  180. // Whether |saveParamsToKeychainForName:authentication:| has been called.
  181. BOOL _keychainSaved;
  182. // Whether |removeAuthFromKeychainForName:| has been called.
  183. BOOL _keychainRemoved;
  184. // The |GIDSignIn| object being tested.
  185. GIDSignIn *_signIn;
  186. // The configuration to be used when testing |GIDSignIn|.
  187. GIDConfiguration *_configuration;
  188. // The login hint to be used when testing |GIDSignIn|.
  189. NSString *_hint;
  190. // The completion to be used when testing |GIDSignIn|.
  191. GIDSignInCompletion _completion;
  192. // The saved authorization request.
  193. OIDAuthorizationRequest *_savedAuthorizationRequest;
  194. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  195. // The saved presentingViewController from the authorization request.
  196. UIViewController *_savedPresentingViewController;
  197. #elif TARGET_OS_OSX
  198. // The saved presentingWindow from the authorization request.
  199. NSWindow *_savedPresentingWindow;
  200. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  201. // The saved authorization callback.
  202. OIDAuthorizationCallback _savedAuthorizationCallback;
  203. // The saved token request.
  204. OIDTokenRequest *_savedTokenRequest;
  205. // The saved token request callback.
  206. OIDTokenCallback _savedTokenCallback;
  207. // Status returned by saveAuthorization:toKeychainForName:
  208. BOOL _saveAuthorizationReturnValue;
  209. }
  210. @end
  211. @implementation GIDSignInTest
  212. #pragma mark - Lifecycle
  213. - (void)setUp {
  214. [super setUp];
  215. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  216. _isEligibleForEMM = [UIDevice currentDevice].systemVersion.integerValue >= 9;
  217. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  218. _saveAuthorizationReturnValue = YES;
  219. // States
  220. _completionCalled = NO;
  221. _keychainSaved = NO;
  222. _keychainRemoved = NO;
  223. // Mocks
  224. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  225. _presentingViewController = OCMStrictClassMock([UIViewController class]);
  226. #elif TARGET_OS_OSX
  227. _presentingWindow = OCMStrictClassMock([NSWindow class]);
  228. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  229. _authState = OCMStrictClassMock([OIDAuthState class]);
  230. OCMStub([_authState alloc]).andReturn(_authState);
  231. OCMStub([_authState initWithAuthorizationResponse:OCMOCK_ANY]).andReturn(_authState);
  232. _tokenResponse = OCMStrictClassMock([OIDTokenResponse class]);
  233. _tokenRequest = OCMStrictClassMock([OIDTokenRequest class]);
  234. _authorization = OCMStrictClassMock([GTMAuthSession class]);
  235. _keychainStore = OCMStrictClassMock([GTMKeychainStore class]);
  236. OCMStub(
  237. [_keychainStore retrieveAuthSessionWithItemName:OCMOCK_ANY error:OCMArg.anyObjectRef]
  238. ).andReturn(_authorization);
  239. OCMStub([_keychainStore retrieveAuthSessionWithError:nil]).andReturn(_authorization);
  240. OCMStub([_authorization alloc]).andReturn(_authorization);
  241. OCMStub([_authorization initWithAuthState:OCMOCK_ANY]).andReturn(_authorization);
  242. OCMStub(
  243. [_keychainStore removeAuthSessionWithError:OCMArg.anyObjectRef]
  244. ).andDo(^(NSInvocation *invocation) {
  245. self->_keychainRemoved = YES;
  246. });
  247. _user = OCMStrictClassMock([GIDGoogleUser class]);
  248. _oidAuthorizationService = OCMStrictClassMock([OIDAuthorizationService class]);
  249. OCMStub([_oidAuthorizationService
  250. presentAuthorizationRequest:SAVE_TO_ARG_BLOCK(self->_savedAuthorizationRequest)
  251. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  252. presentingViewController:SAVE_TO_ARG_BLOCK(self->_savedPresentingViewController)
  253. #elif TARGET_OS_OSX
  254. presentingWindow:SAVE_TO_ARG_BLOCK(self->_savedPresentingWindow)
  255. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  256. callback:COPY_TO_ARG_BLOCK(self->_savedAuthorizationCallback)]);
  257. OCMStub([self->_oidAuthorizationService
  258. performTokenRequest:SAVE_TO_ARG_BLOCK(self->_savedTokenRequest)
  259. callback:COPY_TO_ARG_BLOCK(self->_savedTokenCallback)]);
  260. // Fakes
  261. _fakeMainBundle = [[GIDFakeMainBundle alloc] init];
  262. [_fakeMainBundle startFakingWithClientID:kClientId];
  263. [_fakeMainBundle fakeAllSchemesSupported];
  264. // Object under test
  265. [[NSUserDefaults standardUserDefaults] setBool:YES
  266. forKey:kAppHasRunBeforeKey];
  267. _httpFetcher = [[GIDFakeHTTPFetcher alloc] init];
  268. _signIn = [[GIDSignIn alloc] initWithKeychainStore:_keychainStore
  269. httpFetcher:_httpFetcher];
  270. _hint = nil;
  271. _fetcherService = nil;
  272. __weak GIDSignInTest *weakSelf = self;
  273. _completion = ^(GIDSignInResult *_Nullable signInResult, NSError * _Nullable error) {
  274. GIDSignInTest *strongSelf = weakSelf;
  275. if (!signInResult) {
  276. XCTAssertNotNil(error, @"should have an error if the signInResult is nil");
  277. }
  278. XCTAssertFalse(strongSelf->_completionCalled, @"callback already called");
  279. strongSelf->_completionCalled = YES;
  280. strongSelf->_authError = error;
  281. };
  282. }
  283. - (void)tearDown {
  284. OCMVerifyAll(_authState);
  285. OCMVerifyAll(_tokenResponse);
  286. OCMVerifyAll(_tokenRequest);
  287. OCMVerifyAll(_authorization);
  288. OCMVerifyAll(_user);
  289. OCMVerifyAll(_oidAuthorizationService);
  290. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  291. OCMVerifyAll(_presentingViewController);
  292. #elif TARGET_OS_OSX
  293. OCMVerifyAll(_presentingWindow);
  294. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  295. [_fakeMainBundle stopFaking];
  296. [super tearDown];
  297. }
  298. #pragma mark - Tests
  299. - (void)testShareInstance {
  300. GIDSignIn *signIn1 = GIDSignIn.sharedInstance;
  301. GIDSignIn *signIn2 = GIDSignIn.sharedInstance;
  302. XCTAssertTrue(signIn1 == signIn2, @"shared instance must be singleton");
  303. }
  304. - (void)testInitPrivate {
  305. GIDSignIn *signIn = [[GIDSignIn alloc] initPrivate];
  306. XCTAssertNotNil(signIn.configuration);
  307. XCTAssertEqual(signIn.configuration.clientID, kClientId);
  308. XCTAssertNil(signIn.configuration.serverClientID);
  309. XCTAssertNil(signIn.configuration.hostedDomain);
  310. XCTAssertNil(signIn.configuration.openIDRealm);
  311. }
  312. - (void)testInitPrivate_noConfig {
  313. [_fakeMainBundle fakeWithClientID:nil
  314. serverClientID:nil
  315. hostedDomain:nil
  316. openIDRealm:nil];
  317. GIDSignIn *signIn = [[GIDSignIn alloc] initPrivate];
  318. XCTAssertNil(signIn.configuration);
  319. }
  320. - (void)testInitPrivate_fullConfig {
  321. [_fakeMainBundle fakeWithClientID:kClientId
  322. serverClientID:kServerClientId
  323. hostedDomain:kFakeHostedDomain
  324. openIDRealm:kOpenIDRealm];
  325. GIDSignIn *signIn = [[GIDSignIn alloc] initPrivate];
  326. XCTAssertNotNil(signIn.configuration);
  327. XCTAssertEqual(signIn.configuration.clientID, kClientId);
  328. XCTAssertEqual(signIn.configuration.serverClientID, kServerClientId);
  329. XCTAssertEqual(signIn.configuration.hostedDomain, kFakeHostedDomain);
  330. XCTAssertEqual(signIn.configuration.openIDRealm, kOpenIDRealm);
  331. }
  332. - (void)testInitPrivate_invalidConfig {
  333. [_fakeMainBundle fakeWithClientID:@[ @"bad", @"config", @"values" ]
  334. serverClientID:nil
  335. hostedDomain:nil
  336. openIDRealm:nil];
  337. GIDSignIn *signIn = [[GIDSignIn alloc] initPrivate];
  338. XCTAssertNil(signIn.configuration);
  339. }
  340. - (void)testRestorePreviousSignInNoRefresh_hasPreviousUser {
  341. [[[_authorization stub] andReturn:_authState] authState];
  342. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  343. [[_authorization expect] setDelegate:OCMOCK_ANY];
  344. #endif // TARGET_OS_IOS || !TARGET_OS_MACCATALYST
  345. OCMStub([_authState lastTokenResponse]).andReturn(_tokenResponse);
  346. OCMStub([_authState refreshToken]).andReturn(kRefreshToken);
  347. [[_authState expect] setStateChangeDelegate:OCMOCK_ANY];
  348. id idTokenDecoded = OCMClassMock([OIDIDToken class]);
  349. OCMStub([idTokenDecoded alloc]).andReturn(idTokenDecoded);
  350. OCMStub([idTokenDecoded initWithIDTokenString:OCMOCK_ANY]).andReturn(idTokenDecoded);
  351. OCMStub([idTokenDecoded subject]).andReturn(kFakeGaiaID);
  352. // Mock generating a GIDConfiguration when initializing GIDGoogleUser.
  353. OIDAuthorizationResponse *authResponse =
  354. [OIDAuthorizationResponse testInstanceWithAdditionalParameters:nil
  355. errorString:nil];
  356. OCMStub([_authState lastAuthorizationResponse]).andReturn(authResponse);
  357. OCMStub([_tokenResponse idToken]).andReturn(kFakeIDToken);
  358. OCMStub([_tokenResponse request]).andReturn(_tokenRequest);
  359. OCMStub([_tokenRequest additionalParameters]).andReturn(nil);
  360. OCMStub([_tokenResponse accessToken]).andReturn(kAccessToken);
  361. OCMStub([_tokenResponse accessTokenExpirationDate]).andReturn(nil);
  362. [_signIn restorePreviousSignInNoRefresh];
  363. [idTokenDecoded verify];
  364. XCTAssertEqual(_signIn.currentUser.userID, kFakeGaiaID);
  365. [idTokenDecoded stopMocking];
  366. }
  367. - (void)testRestoredPreviousSignInNoRefresh_hasNoPreviousUser {
  368. [[[_authorization expect] andReturn:nil] authState];
  369. [_signIn restorePreviousSignInNoRefresh];
  370. [_authorization verify];
  371. XCTAssertNil(_signIn.currentUser);
  372. }
  373. - (void)testHasPreviousSignIn_HasBeenAuthenticated {
  374. [[[_authorization expect] andReturn:_authState] authState];
  375. [[[_authState expect] andReturnValue:[NSNumber numberWithBool:YES]] isAuthorized];
  376. XCTAssertTrue([_signIn hasPreviousSignIn], @"should return |YES|");
  377. [_authorization verify];
  378. [_authState verify];
  379. XCTAssertFalse(_keychainRemoved, @"should not remove keychain");
  380. XCTAssertFalse(_completionCalled, @"should not call delegate");
  381. XCTAssertNil(_authError, @"should have no error");
  382. }
  383. - (void)testHasPreviousSignIn_HasNotBeenAuthenticated {
  384. [[[_authorization expect] andReturn:_authState] authState];
  385. [[[_authState expect] andReturnValue:[NSNumber numberWithBool:NO]] isAuthorized];
  386. XCTAssertFalse([_signIn hasPreviousSignIn], @"should return |NO|");
  387. [_authorization verify];
  388. [_authState verify];
  389. XCTAssertFalse(_keychainRemoved, @"should not remove keychain");
  390. XCTAssertFalse(_completionCalled, @"should not call delegate");
  391. }
  392. - (void)testRestorePreviousSignInWhenSignedOut {
  393. [[[_authorization expect] andReturn:_authState] authState];
  394. [[[_authState expect] andReturnValue:[NSNumber numberWithBool:NO]] isAuthorized];
  395. _completionCalled = NO;
  396. _authError = nil;
  397. XCTestExpectation *expectation = [self expectationWithDescription:@"Callback should be called."];
  398. [_signIn restorePreviousSignInWithCompletion:^(GIDGoogleUser *_Nullable user,
  399. NSError * _Nullable error) {
  400. [expectation fulfill];
  401. XCTAssertNotNil(error, @"error should not have been nil");
  402. XCTAssertEqual(error.domain,
  403. kGIDSignInErrorDomain,
  404. @"error domain should have been the sign-in error domain.");
  405. XCTAssertEqual(error.code,
  406. kGIDSignInErrorCodeHasNoAuthInKeychain,
  407. @"error code should have been the 'NoAuthInKeychain' error code.");
  408. }];
  409. [self waitForExpectationsWithTimeout:1 handler:nil];
  410. [_authorization verify];
  411. [_authState verify];
  412. }
  413. - (void)testNotRestorePreviousSignInWhenSignedOutAndCompletionIsNil {
  414. [[[_authorization expect] andReturn:_authState] authState];
  415. [[[_authState expect] andReturnValue:[NSNumber numberWithBool:NO]] isAuthorized];
  416. [_signIn restorePreviousSignInWithCompletion:nil];
  417. XCTAssertNil(_signIn.currentUser);
  418. }
  419. - (void)testRestorePreviousSignInWhenCompletionIsNil {
  420. [[[_authorization expect] andReturn:_authState] authState];
  421. [[_keychainStore expect] saveAuthSession:OCMOCK_ANY error:[OCMArg anyObjectRef]];
  422. [[[_authState expect] andReturnValue:[NSNumber numberWithBool:YES]] isAuthorized];
  423. OIDTokenResponse *tokenResponse =
  424. [OIDTokenResponse testInstanceWithIDToken:[OIDTokenResponse fatIDToken]
  425. accessToken:kAccessToken
  426. expiresIn:nil
  427. refreshToken:kRefreshToken
  428. tokenRequest:nil];
  429. [[[_authState stub] andReturn:tokenResponse] lastTokenResponse];
  430. // TODO: Create a real GIDGoogleUser to verify the signed in user value(#306).
  431. [[[_user stub] andReturn:_user] alloc];
  432. (void)[[[_user expect] andReturn:_user] initWithAuthState:OCMOCK_ANY
  433. profileData:OCMOCK_ANY];
  434. XCTAssertNil(_signIn.currentUser);
  435. [_signIn restorePreviousSignInWithCompletion:nil];
  436. XCTAssertNotNil(_signIn.currentUser);
  437. }
  438. - (void)testOAuthLogin {
  439. OCMStub(
  440. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  441. ).andDo(^(NSInvocation *invocation) {
  442. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  443. });
  444. [self OAuthLoginWithAddScopesFlow:NO
  445. authError:nil
  446. tokenError:nil
  447. emmPasscodeInfoRequired:NO
  448. keychainError:NO
  449. restoredSignIn:NO
  450. oldAccessToken:NO
  451. modalCancel:NO];
  452. }
  453. - (void)testOAuthLogin_RestoredSignIn {
  454. OCMStub(
  455. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  456. ).andDo(^(NSInvocation *invocation) {
  457. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  458. });
  459. [self OAuthLoginWithAddScopesFlow:NO
  460. authError:nil
  461. tokenError:nil
  462. emmPasscodeInfoRequired:NO
  463. keychainError:NO
  464. restoredSignIn:YES
  465. oldAccessToken:NO
  466. modalCancel:NO];
  467. }
  468. - (void)testOAuthLogin_RestoredSignInOldAccessToken {
  469. OCMStub(
  470. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  471. ).andDo(^(NSInvocation *invocation) {
  472. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  473. });
  474. [self OAuthLoginWithAddScopesFlow:NO
  475. authError:nil
  476. tokenError:nil
  477. emmPasscodeInfoRequired:NO
  478. keychainError:NO
  479. restoredSignIn:YES
  480. oldAccessToken:YES
  481. modalCancel:NO];
  482. }
  483. - (void)testOAuthLogin_AdditionalScopes {
  484. NSString *expectedScopeString;
  485. OCMStub(
  486. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  487. ).andDo(^(NSInvocation *invocation) {
  488. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  489. });
  490. [self OAuthLoginWithAddScopesFlow:NO
  491. authError:nil
  492. tokenError:nil
  493. emmPasscodeInfoRequired:NO
  494. keychainError:NO
  495. restoredSignIn:NO
  496. oldAccessToken:NO
  497. modalCancel:NO
  498. useAdditionalScopes:YES
  499. additionalScopes:nil];
  500. expectedScopeString = [@[ @"email", @"profile" ] componentsJoinedByString:@" "];
  501. XCTAssertEqualObjects(_savedAuthorizationRequest.scope, expectedScopeString);
  502. [self OAuthLoginWithAddScopesFlow:NO
  503. authError:nil
  504. tokenError:nil
  505. emmPasscodeInfoRequired:NO
  506. keychainError:NO
  507. restoredSignIn:NO
  508. oldAccessToken:NO
  509. modalCancel:NO
  510. useAdditionalScopes:YES
  511. additionalScopes:@[ kScope ]];
  512. expectedScopeString = [@[ kScope, @"email", @"profile" ] componentsJoinedByString:@" "];
  513. XCTAssertEqualObjects(_savedAuthorizationRequest.scope, expectedScopeString);
  514. [self OAuthLoginWithAddScopesFlow:NO
  515. authError:nil
  516. tokenError:nil
  517. emmPasscodeInfoRequired:NO
  518. keychainError:NO
  519. restoredSignIn:NO
  520. oldAccessToken:NO
  521. modalCancel:NO
  522. useAdditionalScopes:YES
  523. additionalScopes:@[ kScope, kScope2 ]];
  524. expectedScopeString = [@[ kScope, kScope2, @"email", @"profile" ] componentsJoinedByString:@" "];
  525. XCTAssertEqualObjects(_savedAuthorizationRequest.scope, expectedScopeString);
  526. }
  527. - (void)testAddScopes {
  528. // Restore the previous sign-in account. This is the preparation for adding scopes.
  529. OCMStub(
  530. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  531. ).andDo(^(NSInvocation *invocation) {
  532. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  533. });
  534. [self OAuthLoginWithAddScopesFlow:NO
  535. authError:nil
  536. tokenError:nil
  537. emmPasscodeInfoRequired:NO
  538. keychainError:NO
  539. restoredSignIn:YES
  540. oldAccessToken:NO
  541. modalCancel:NO];
  542. XCTAssertNotNil(_signIn.currentUser);
  543. id profile = OCMStrictClassMock([GIDProfileData class]);
  544. OCMStub([profile email]).andReturn(kUserEmail);
  545. // Mock for the method `addScopes`.
  546. GIDConfiguration *configuration = [[GIDConfiguration alloc] initWithClientID:kClientId
  547. serverClientID:nil
  548. hostedDomain:nil
  549. openIDRealm:kOpenIDRealm];
  550. OCMStub([_user configuration]).andReturn(configuration);
  551. OCMStub([_user profile]).andReturn(profile);
  552. OCMStub([_user grantedScopes]).andReturn(@[kGrantedScope]);
  553. [self OAuthLoginWithAddScopesFlow:YES
  554. authError:nil
  555. tokenError:nil
  556. emmPasscodeInfoRequired:NO
  557. keychainError:NO
  558. restoredSignIn:NO
  559. oldAccessToken:NO
  560. modalCancel:NO];
  561. NSArray<NSString *> *grantedScopes;
  562. NSString *grantedScopeString = _savedAuthorizationRequest.scope;
  563. if (grantedScopeString) {
  564. grantedScopeString = [grantedScopeString stringByTrimmingCharactersInSet:
  565. [NSCharacterSet whitespaceCharacterSet]];
  566. // Tokenize with space as a delimiter.
  567. NSMutableArray<NSString *> *parsedScopes =
  568. [[grantedScopeString componentsSeparatedByString:@" "] mutableCopy];
  569. // Remove empty strings.
  570. [parsedScopes removeObject:@""];
  571. grantedScopes = [parsedScopes copy];
  572. }
  573. NSArray<NSString *> *expectedScopes = @[kNewScope, kGrantedScope];
  574. XCTAssertEqualObjects(grantedScopes, expectedScopes);
  575. [_user verify];
  576. [profile verify];
  577. [profile stopMocking];
  578. }
  579. - (void)testOpenIDRealm {
  580. _signIn.configuration = [[GIDConfiguration alloc] initWithClientID:kClientId
  581. serverClientID:nil
  582. hostedDomain:nil
  583. openIDRealm:kOpenIDRealm];
  584. OCMStub(
  585. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  586. ).andDo(^(NSInvocation *invocation) {
  587. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  588. });
  589. [self OAuthLoginWithAddScopesFlow:NO
  590. authError:nil
  591. tokenError:nil
  592. emmPasscodeInfoRequired:NO
  593. keychainError:NO
  594. restoredSignIn:NO
  595. oldAccessToken:NO
  596. modalCancel:NO];
  597. NSDictionary<NSString *, NSString *> *params = _savedTokenRequest.additionalParameters;
  598. XCTAssertEqual(params[kOpenIDRealmKey], kOpenIDRealm, @"OpenID Realm should match.");
  599. }
  600. - (void)testOAuthLogin_LoginHint {
  601. _hint = kUserEmail;
  602. OCMStub(
  603. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  604. ).andDo(^(NSInvocation *invocation) {
  605. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  606. });
  607. [self OAuthLoginWithAddScopesFlow:NO
  608. authError:nil
  609. tokenError:nil
  610. emmPasscodeInfoRequired:NO
  611. keychainError:NO
  612. restoredSignIn:NO
  613. oldAccessToken:NO
  614. modalCancel:NO];
  615. NSDictionary<NSString *, NSObject *> *params = _savedAuthorizationRequest.additionalParameters;
  616. XCTAssertEqualObjects(params[@"login_hint"], kUserEmail, @"login hint should match");
  617. }
  618. - (void)testOAuthLogin_HostedDomain {
  619. _signIn.configuration = [[GIDConfiguration alloc] initWithClientID:kClientId
  620. serverClientID:nil
  621. hostedDomain:kHostedDomain
  622. openIDRealm:nil];
  623. OCMStub(
  624. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  625. ).andDo(^(NSInvocation *invocation) {
  626. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  627. });
  628. [self OAuthLoginWithAddScopesFlow:NO
  629. authError:nil
  630. tokenError:nil
  631. emmPasscodeInfoRequired:NO
  632. keychainError:NO
  633. restoredSignIn:NO
  634. oldAccessToken:NO
  635. modalCancel:NO];
  636. NSDictionary<NSString *, NSObject *> *params = _savedAuthorizationRequest.additionalParameters;
  637. XCTAssertEqualObjects(params[@"hd"], kHostedDomain, @"hosted domain should match");
  638. }
  639. - (void)testOAuthLogin_ConsentCanceled {
  640. [self OAuthLoginWithAddScopesFlow:NO
  641. authError:@"access_denied"
  642. tokenError:nil
  643. emmPasscodeInfoRequired:NO
  644. keychainError:NO
  645. restoredSignIn:NO
  646. oldAccessToken:NO
  647. modalCancel:NO];
  648. [self waitForExpectationsWithTimeout:1 handler:nil];
  649. XCTAssertTrue(_completionCalled, @"should call delegate");
  650. XCTAssertEqual(_authError.code, kGIDSignInErrorCodeCanceled);
  651. }
  652. - (void)testOAuthLogin_ModalCanceled {
  653. [self OAuthLoginWithAddScopesFlow:NO
  654. authError:nil
  655. tokenError:nil
  656. emmPasscodeInfoRequired:NO
  657. keychainError:NO
  658. restoredSignIn:NO
  659. oldAccessToken:NO
  660. modalCancel:YES];
  661. [self waitForExpectationsWithTimeout:1 handler:nil];
  662. XCTAssertTrue(_completionCalled, @"should call delegate");
  663. XCTAssertEqual(_authError.code, kGIDSignInErrorCodeCanceled);
  664. }
  665. - (void)testOAuthLogin_KeychainError {
  666. // This error is going be overidden by `-[GIDSignIn errorWithString:code:]`
  667. // We just need to fill in the error so that happens.
  668. NSError *keychainError = [NSError errorWithDomain:@"com.googleSignIn.throwAway"
  669. code:1
  670. userInfo:nil];
  671. OCMStub(
  672. [_keychainStore saveAuthSession:OCMOCK_ANY error:[OCMArg setTo:keychainError]]
  673. ).andDo(^(NSInvocation *invocation) {
  674. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  675. });
  676. [self OAuthLoginWithAddScopesFlow:NO
  677. authError:nil
  678. tokenError:nil
  679. emmPasscodeInfoRequired:NO
  680. keychainError:YES
  681. restoredSignIn:NO
  682. oldAccessToken:NO
  683. modalCancel:NO];
  684. [self waitForExpectationsWithTimeout:1 handler:nil];
  685. XCTAssertFalse(_keychainSaved, @"should save to keychain");
  686. XCTAssertTrue(_completionCalled, @"should call delegate");
  687. XCTAssertEqualObjects(_authError.domain, kGIDSignInErrorDomain);
  688. XCTAssertEqual(_authError.code, kGIDSignInErrorCodeKeychain);
  689. }
  690. - (void)testSignOut {
  691. #if TARGET_OS_IOS || !TARGET_OS_MACCATALYST
  692. // OCMStub([_authorization authState]).andReturn(_authState);
  693. #endif // TARGET_OS_IOS || !TARGET_OS_MACCATALYST
  694. OCMStub([_authorization fetcherService]).andReturn(_fetcherService);
  695. OCMStub(
  696. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  697. ).andDo(^(NSInvocation *invocation) {
  698. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  699. });
  700. // Sign in a user so that we can then sign them out.
  701. [self OAuthLoginWithAddScopesFlow:NO
  702. authError:nil
  703. tokenError:nil
  704. emmPasscodeInfoRequired:NO
  705. keychainError:NO
  706. restoredSignIn:YES
  707. oldAccessToken:NO
  708. modalCancel:NO];
  709. XCTAssertNotNil(_signIn.currentUser);
  710. [_signIn signOut];
  711. XCTAssertNil(_signIn.currentUser, @"should not have a current user");
  712. XCTAssertTrue(_keychainRemoved, @"should remove keychain");
  713. OCMVerify([_keychainStore removeAuthSessionWithError:OCMArg.anyObjectRef]);
  714. }
  715. - (void)testNotHandleWrongScheme {
  716. XCTAssertFalse([_signIn handleURL:[NSURL URLWithString:kWrongSchemeURL]],
  717. @"should not handle URL");
  718. XCTAssertFalse(_keychainSaved, @"should not save to keychain");
  719. XCTAssertFalse(_completionCalled, @"should not call delegate");
  720. }
  721. - (void)testNotHandleWrongPath {
  722. XCTAssertFalse([_signIn handleURL:[NSURL URLWithString:kWrongPathURL]], @"should not handle URL");
  723. XCTAssertFalse(_keychainSaved, @"should not save to keychain");
  724. XCTAssertFalse(_completionCalled, @"should not call delegate");
  725. }
  726. #pragma mark - Tests - disconnectWithCallback:
  727. // Verifies disconnect calls callback with no errors if access token is present.
  728. - (void)testDisconnect_accessTokenIsPresent {
  729. [[[_authorization expect] andReturn:_authState] authState];
  730. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  731. [[[_tokenResponse expect] andReturn:kAccessToken] accessToken];
  732. OCMStub([_authorization fetcherService]).andReturn(_fetcherService);
  733. NSData *data = [[NSData alloc] init];
  734. [self didFetch:data withToken:kAccessToken error:nil];
  735. XCTestExpectation *accessTokenExpectation =
  736. [self expectationWithDescription:@"Callback called with nil error"];
  737. [_signIn disconnectWithCompletion:^(NSError * _Nullable error) {
  738. XCTAssertNil(error);
  739. [accessTokenExpectation fulfill];
  740. }];
  741. [self waitForExpectationsWithTimeout:1 handler:nil];
  742. XCTAssertTrue(_keychainRemoved, @"should clear saved keychain name");
  743. }
  744. // Verifies disconnect if access token is present.
  745. - (void)testDisconnectNoCallback_accessTokenIsPresent {
  746. [[[_authorization expect] andReturn:_authState] authState];
  747. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  748. [[[_tokenResponse expect] andReturn:kAccessToken] accessToken];
  749. OCMStub([_authorization fetcherService]).andReturn(_fetcherService);
  750. NSData *data = [[NSData alloc] init];
  751. [self didFetch:data withToken:kAccessToken error:nil];
  752. [_signIn disconnectWithCompletion:nil];
  753. [self waitForExpectationsWithTimeout:1 handler:nil];
  754. XCTAssertTrue(_keychainRemoved, @"should clear saved keychain name");
  755. }
  756. // Verifies disconnect calls callback with no errors if refresh token is present.
  757. - (void)testDisconnect_refreshTokenIsPresent {
  758. [[[_authorization expect] andReturn:_authState] authState];
  759. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  760. [[[_tokenResponse expect] andReturn:nil] accessToken];
  761. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  762. [[[_tokenResponse expect] andReturn:kRefreshToken] refreshToken];
  763. OCMStub([_authorization fetcherService]).andReturn(_fetcherService);
  764. NSData *data = [[NSData alloc] init];
  765. [self didFetch:data withToken:kRefreshToken error:nil];
  766. XCTestExpectation *refreshTokenExpectation =
  767. [self expectationWithDescription:@"Callback called with nil error"];
  768. [_signIn disconnectWithCompletion:^(NSError * _Nullable error) {
  769. XCTAssertNil(error);
  770. [refreshTokenExpectation fulfill];
  771. }];
  772. [self waitForExpectationsWithTimeout:1 handler:nil];
  773. [_tokenResponse verify];
  774. XCTAssertTrue(_keychainRemoved, @"should clear saved keychain name");
  775. }
  776. // Verifies disconnect errors are passed along to the callback.
  777. - (void)testDisconnect_errors {
  778. [[[_authorization expect] andReturn:_authState] authState];
  779. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  780. [[[_tokenResponse expect] andReturn:kAccessToken] accessToken];
  781. OCMStub([_authorization fetcherService]).andReturn(_fetcherService);
  782. NSError *error = [self error];
  783. [self didFetch:nil withToken:kAccessToken error:error];
  784. XCTestExpectation *errorExpectation =
  785. [self expectationWithDescription:@"Callback called with an error"];
  786. [_signIn disconnectWithCompletion:^(NSError * _Nullable error) {
  787. XCTAssertNotNil(error);
  788. [errorExpectation fulfill];
  789. }];
  790. [self waitForExpectationsWithTimeout:1 handler:nil];
  791. }
  792. // Verifies disconnect with errors
  793. - (void)testDisconnectNoCallback_errors {
  794. [[[_authorization expect] andReturn:_authState] authState];
  795. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  796. [[[_tokenResponse expect] andReturn:kAccessToken] accessToken];
  797. OCMStub([_authorization fetcherService]).andReturn(_fetcherService);
  798. NSError *error = [self error];
  799. [self didFetch:nil withToken:kAccessToken error:error];
  800. [_signIn disconnectWithCompletion:nil];
  801. [self waitForExpectationsWithTimeout:1 handler:nil];
  802. }
  803. // Verifies disconnect calls callback with no errors and clears keychain if no tokens are present.
  804. - (void)testDisconnect_noTokens {
  805. [[[_authorization expect] andReturn:_authState] authState];
  806. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  807. [[[_tokenResponse expect] andReturn:nil] accessToken];
  808. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  809. [[[_tokenResponse expect] andReturn:nil] refreshToken];
  810. [self notDoFetch];
  811. XCTestExpectation *noTokensExpectation =
  812. [self expectationWithDescription:@"Callback called with nil error"];
  813. [_signIn disconnectWithCompletion:^(NSError * _Nullable error) {
  814. XCTAssertNil(error);
  815. [noTokensExpectation fulfill];
  816. }];
  817. [self waitForExpectations:@[noTokensExpectation] timeout:1];
  818. XCTAssertTrue(_keychainRemoved, @"keychain should be removed");
  819. }
  820. // Verifies disconnect clears keychain if no tokens are present.
  821. - (void)testDisconnectNoCallback_noTokens {
  822. [[[_authorization expect] andReturn:_authState] authState];
  823. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  824. [[[_tokenResponse expect] andReturn:nil] accessToken];
  825. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  826. [[[_tokenResponse expect] andReturn:nil] refreshToken];
  827. [self notDoFetch];
  828. [_signIn disconnectWithCompletion:nil];
  829. XCTAssertTrue(_keychainRemoved, @"keychain should be removed");
  830. }
  831. - (void)testPresentingViewControllerException {
  832. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  833. _presentingViewController = nil;
  834. #elif TARGET_OS_OSX
  835. _presentingWindow = nil;
  836. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  837. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  838. XCTAssertThrows([_signIn signInWithPresentingViewController:_presentingViewController
  839. #elif TARGET_OS_OSX
  840. XCTAssertThrows([_signIn signInWithPresentingWindow:_presentingWindow
  841. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  842. hint:_hint
  843. completion:_completion]);
  844. }
  845. - (void)testClientIDMissingException {
  846. #pragma GCC diagnostic push
  847. #pragma GCC diagnostic ignored "-Wnonnull"
  848. _signIn.configuration = [[GIDConfiguration alloc] initWithClientID:nil];
  849. #pragma GCC diagnostic pop
  850. BOOL threw = NO;
  851. @try {
  852. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  853. [_signIn signInWithPresentingViewController:_presentingViewController
  854. #elif TARGET_OS_OSX
  855. [_signIn signInWithPresentingWindow:_presentingWindow
  856. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  857. completion:nil];
  858. } @catch (NSException *exception) {
  859. threw = YES;
  860. XCTAssertEqualObjects(exception.description,
  861. @"You must specify |clientID| in |GIDConfiguration|");
  862. } @finally {
  863. }
  864. XCTAssert(threw);
  865. }
  866. - (void)testSchemesNotSupportedException {
  867. [_fakeMainBundle fakeMissingAllSchemes];
  868. BOOL threw = NO;
  869. @try {
  870. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  871. [_signIn signInWithPresentingViewController:_presentingViewController
  872. #elif TARGET_OS_OSX
  873. [_signIn signInWithPresentingWindow:_presentingWindow
  874. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  875. hint:_hint
  876. completion:_completion];
  877. } @catch (NSException *exception) {
  878. threw = YES;
  879. XCTAssertEqualObjects(exception.description,
  880. @"Your app is missing support for the following URL schemes: "
  881. "fakeclientid");
  882. } @finally {
  883. }
  884. XCTAssert(threw);
  885. }
  886. #pragma mark - Restarting Authentication Tests
  887. // Verifies that URL is not handled if there is no pending sign-in
  888. - (void)testRequiringPendingSignIn {
  889. BOOL result = [_signIn handleURL:[NSURL URLWithString:kEMMRestartAuthURL]];
  890. XCTAssertFalse(result);
  891. }
  892. #pragma mark - EMM tests
  893. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  894. - (void)testEmmSupportRequestParameters {
  895. OCMStub(
  896. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  897. ).andDo(^(NSInvocation *invocation) {
  898. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  899. });
  900. [self OAuthLoginWithAddScopesFlow:NO
  901. authError:nil
  902. tokenError:nil
  903. emmPasscodeInfoRequired:NO
  904. keychainError:NO
  905. restoredSignIn:NO
  906. oldAccessToken:NO
  907. modalCancel:NO];
  908. NSString *systemName = [UIDevice currentDevice].systemName;
  909. if ([systemName isEqualToString:@"iPhone OS"]) {
  910. systemName = @"iOS";
  911. }
  912. NSString *expectedOSVersion = [NSString stringWithFormat:@"%@ %@",
  913. systemName, [UIDevice currentDevice].systemVersion];
  914. NSDictionary<NSString *, NSObject *> *authParams =
  915. _savedAuthorizationRequest.additionalParameters;
  916. NSDictionary<NSString *, NSString *> *tokenParams = _savedTokenRequest.additionalParameters;
  917. if (_isEligibleForEMM) {
  918. XCTAssertEqualObjects(authParams[@"emm_support"], kEMMSupport,
  919. @"EMM support should match in auth request");
  920. XCTAssertEqualObjects(authParams[@"device_os"], expectedOSVersion,
  921. @"OS version should match in auth request");
  922. XCTAssertEqualObjects(tokenParams[@"emm_support"], kEMMSupport,
  923. @"EMM support should match in token request");
  924. XCTAssertEqualObjects(tokenParams[@"device_os"],
  925. expectedOSVersion,
  926. @"OS version should match in token request");
  927. XCTAssertNil(tokenParams[@"emm_passcode_info"],
  928. @"no passcode info should be in token request");
  929. } else {
  930. XCTAssertNil(authParams[@"emm_support"],
  931. @"EMM support should not be in auth request for unsupported OS");
  932. XCTAssertNil(authParams[@"device_os"],
  933. @"OS version should not be in auth request for unsupported OS");
  934. XCTAssertNil(tokenParams[@"emm_support"],
  935. @"EMM support should not be in token request for unsupported OS");
  936. XCTAssertNil(tokenParams[@"device_os"],
  937. @"OS version should not be in token request for unsupported OS");
  938. XCTAssertNil(tokenParams[@"emm_passcode_info"],
  939. @"passcode info should not be in token request for unsupported OS");
  940. }
  941. }
  942. - (void)testEmmPasscodeInfo {
  943. OCMStub(
  944. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  945. ).andDo(^(NSInvocation *invocation) {
  946. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  947. });
  948. [self OAuthLoginWithAddScopesFlow:NO
  949. authError:nil
  950. tokenError:nil
  951. emmPasscodeInfoRequired:YES
  952. keychainError:NO
  953. restoredSignIn:NO
  954. oldAccessToken:NO
  955. modalCancel:NO];
  956. NSDictionary<NSString *, NSString *> *tokenParams = _savedTokenRequest.additionalParameters;
  957. if (_isEligibleForEMM) {
  958. XCTAssertNotNil(tokenParams[@"emm_passcode_info"],
  959. @"passcode info should be in token request");
  960. } else {
  961. XCTAssertNil(tokenParams[@"emm_passcode_info"],
  962. @"passcode info should not be in token request for unsupported OS");
  963. }
  964. }
  965. - (void)testAuthEndpointEMMError {
  966. if (!_isEligibleForEMM) {
  967. return;
  968. }
  969. id mockEMMErrorHandler = OCMStrictClassMock([GIDEMMErrorHandler class]);
  970. [[[mockEMMErrorHandler stub] andReturn:mockEMMErrorHandler] sharedInstance];
  971. __block void (^completion)(void);
  972. NSDictionary<NSString *, NSString *> *callbackParams = @{ @"error" : @"EMM Specific Error" };
  973. [[[mockEMMErrorHandler expect] andReturnValue:@YES]
  974. handleErrorFromResponse:callbackParams completion:SAVE_TO_ARG_BLOCK(completion)];
  975. [self OAuthLoginWithAddScopesFlow:NO
  976. authError:callbackParams[@"error"]
  977. tokenError:nil
  978. emmPasscodeInfoRequired:NO
  979. keychainError:NO
  980. restoredSignIn:NO
  981. oldAccessToken:NO
  982. modalCancel:NO];
  983. [mockEMMErrorHandler verify];
  984. [mockEMMErrorHandler stopMocking];
  985. completion();
  986. [self waitForExpectationsWithTimeout:1 handler:nil];
  987. XCTAssertFalse(_keychainSaved, @"should not save to keychain");
  988. XCTAssertTrue(_completionCalled, @"should call delegate");
  989. XCTAssertNotNil(_authError, @"should have error");
  990. XCTAssertEqualObjects(_authError.domain, kGIDSignInErrorDomain);
  991. XCTAssertEqual(_authError.code, kGIDSignInErrorCodeEMM);
  992. XCTAssertNil(_signIn.currentUser, @"should not have current user");
  993. }
  994. - (void)testTokenEndpointEMMError {
  995. if (!_isEligibleForEMM) {
  996. return;
  997. }
  998. __block void (^completion)(NSError *);
  999. NSDictionary *errorJSON = @{ @"error" : @"EMM Specific Error" };
  1000. NSError *emmError = [NSError errorWithDomain:@"anydomain"
  1001. code:12345
  1002. userInfo:@{ OIDOAuthErrorFieldError : errorJSON }];
  1003. id emmSupport = OCMStrictClassMock([GIDEMMSupport class]);
  1004. [[emmSupport expect] handleTokenFetchEMMError:emmError
  1005. completion:SAVE_TO_ARG_BLOCK(completion)];
  1006. [self OAuthLoginWithAddScopesFlow:NO
  1007. authError:nil
  1008. tokenError:emmError
  1009. emmPasscodeInfoRequired:NO
  1010. keychainError:NO
  1011. restoredSignIn:NO
  1012. oldAccessToken:NO
  1013. modalCancel:NO];
  1014. NSError *handledError = [NSError errorWithDomain:kGIDSignInErrorDomain
  1015. code:kGIDSignInErrorCodeEMM
  1016. userInfo:emmError.userInfo];
  1017. completion(handledError);
  1018. [self waitForExpectationsWithTimeout:1 handler:nil];
  1019. [emmSupport verify];
  1020. XCTAssertFalse(_keychainSaved, @"should not save to keychain");
  1021. XCTAssertTrue(_completionCalled, @"should call delegate");
  1022. XCTAssertNotNil(_authError, @"should have error");
  1023. XCTAssertEqualObjects(_authError.domain, kGIDSignInErrorDomain);
  1024. XCTAssertEqual(_authError.code, kGIDSignInErrorCodeEMM);
  1025. XCTAssertNil(_signIn.currentUser, @"should not have current user");
  1026. }
  1027. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  1028. #pragma mark - Helpers
  1029. - (void)didFetch:(NSData *) data
  1030. withToken:(NSString *) token
  1031. error:(NSError *) error {
  1032. XCTestExpectation *fetcherExpectation =
  1033. [self expectationWithDescription:@"testBlock is invoked."];
  1034. GIDHTTPFetcherTestBlock testBlock =
  1035. ^(NSURLRequest *request, GIDHTTPFetcherFakeResponseProviderBlock responseProvider) {
  1036. [self verifyRevokeRequest:request withToken:token];
  1037. responseProvider(data, error);
  1038. [fetcherExpectation fulfill];
  1039. };
  1040. [_httpFetcher setTestBlock:testBlock];
  1041. }
  1042. - (void)notDoFetch {
  1043. GIDHTTPFetcherTestBlock testBlock =
  1044. ^(NSURLRequest *request, GIDHTTPFetcherFakeResponseProviderBlock responseProvider) {
  1045. XCTFail(@"_httpFetcher should not be invoked.");
  1046. };
  1047. [_httpFetcher setTestBlock:testBlock];
  1048. }
  1049. - (NSError *)error {
  1050. return [NSError errorWithDomain:kErrorDomain code:kErrorCode userInfo:nil];
  1051. }
  1052. - (void)verifyRevokeRequest:(NSURLRequest *)request
  1053. withToken:(NSString *)token {
  1054. NSURL *url = request.URL;
  1055. XCTAssertEqualObjects([url scheme], @"https", @"scheme must match");
  1056. XCTAssertEqualObjects([url host], @"accounts.google.com", @"host must match");
  1057. XCTAssertEqualObjects([url path], @"/o/oauth2/revoke", @"path must match");
  1058. OIDURLQueryComponent *queryComponent = [[OIDURLQueryComponent alloc] initWithURL:url];
  1059. NSDictionary<NSString *, NSObject<NSCopying> *> *params = queryComponent.dictionaryValue;
  1060. XCTAssertEqualObjects([params valueForKey:@"token"], token,
  1061. @"token parameter should match");
  1062. XCTAssertEqualObjects([params valueForKey:kSDKVersionLoggingParameter], GIDVersion(),
  1063. @"SDK version logging parameter should match");
  1064. XCTAssertEqualObjects([params valueForKey:kEnvironmentLoggingParameter], GIDEnvironment(),
  1065. @"Environment logging parameter should match");
  1066. }
  1067. - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow
  1068. authError:(NSString *)authError
  1069. tokenError:(NSError *)tokenError
  1070. emmPasscodeInfoRequired:(BOOL)emmPasscodeInfoRequired
  1071. keychainError:(BOOL)keychainError
  1072. restoredSignIn:(BOOL)restoredSignIn
  1073. oldAccessToken:(BOOL)oldAccessToken
  1074. modalCancel:(BOOL)modalCancel {
  1075. [self OAuthLoginWithAddScopesFlow:addScopesFlow
  1076. authError:authError
  1077. tokenError:tokenError
  1078. emmPasscodeInfoRequired:emmPasscodeInfoRequired
  1079. keychainError:keychainError
  1080. restoredSignIn:restoredSignIn
  1081. oldAccessToken:oldAccessToken
  1082. modalCancel:modalCancel
  1083. useAdditionalScopes:NO
  1084. additionalScopes:nil];
  1085. }
  1086. // The authorization flow with parameters to control which branches to take.
  1087. - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow
  1088. authError:(NSString *)authError
  1089. tokenError:(NSError *)tokenError
  1090. emmPasscodeInfoRequired:(BOOL)emmPasscodeInfoRequired
  1091. keychainError:(BOOL)keychainError
  1092. restoredSignIn:(BOOL)restoredSignIn
  1093. oldAccessToken:(BOOL)oldAccessToken
  1094. modalCancel:(BOOL)modalCancel
  1095. useAdditionalScopes:(BOOL)useAdditionalScopes
  1096. additionalScopes:(NSArray *)additionalScopes {
  1097. if (restoredSignIn) {
  1098. // clearAndAuthenticateWithOptions
  1099. [[[_authorization expect] andReturn:_authState] authState];
  1100. BOOL isAuthorized = restoredSignIn ? YES : NO;
  1101. [[[_authState expect] andReturnValue:[NSNumber numberWithBool:isAuthorized]] isAuthorized];
  1102. }
  1103. NSDictionary<NSString *, NSString *> *additionalParameters = emmPasscodeInfoRequired ?
  1104. @{ @"emm_passcode_info_required" : @"1" } : nil;
  1105. OIDAuthorizationResponse *authResponse =
  1106. [OIDAuthorizationResponse testInstanceWithAdditionalParameters:additionalParameters
  1107. errorString:authError];
  1108. OIDTokenResponse *tokenResponse =
  1109. [OIDTokenResponse testInstanceWithIDToken:[OIDTokenResponse fatIDToken]
  1110. accessToken:restoredSignIn ? kAccessToken : nil
  1111. expiresIn:oldAccessToken ? @(300) : nil
  1112. refreshToken:kRefreshToken
  1113. tokenRequest:nil];
  1114. OIDTokenRequest *tokenRequest = [[OIDTokenRequest alloc]
  1115. initWithConfiguration:authResponse.request.configuration
  1116. grantType:OIDGrantTypeRefreshToken
  1117. authorizationCode:nil
  1118. redirectURL:nil
  1119. clientID:authResponse.request.clientID
  1120. clientSecret:authResponse.request.clientSecret
  1121. scope:nil
  1122. refreshToken:kRefreshToken
  1123. codeVerifier:nil
  1124. additionalParameters:tokenResponse.request.additionalParameters];
  1125. if (restoredSignIn) {
  1126. // maybeFetchToken
  1127. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  1128. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  1129. if (oldAccessToken) {
  1130. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  1131. // Corresponds to EMM support
  1132. [[[_authState expect] andReturn:authResponse] lastAuthorizationResponse];
  1133. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  1134. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  1135. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  1136. [[[_authState expect] andReturn:tokenRequest]
  1137. tokenRefreshRequestWithAdditionalParameters:[OCMArg any]];
  1138. }
  1139. } else {
  1140. XCTestExpectation *newAccessTokenExpectation =
  1141. [self expectationWithDescription:@"Callback called"];
  1142. GIDSignInCompletion completion = ^(GIDSignInResult *_Nullable signInResult,
  1143. NSError * _Nullable error) {
  1144. [newAccessTokenExpectation fulfill];
  1145. if (signInResult) {
  1146. XCTAssertEqualObjects(signInResult.serverAuthCode, kServerAuthCode);
  1147. } else {
  1148. XCTAssertNotNil(error, @"Should have an error if the signInResult is nil");
  1149. }
  1150. XCTAssertFalse(self->_completionCalled, @"callback already called");
  1151. self->_completionCalled = YES;
  1152. self->_authError = error;
  1153. };
  1154. if (addScopesFlow) {
  1155. [_signIn addScopes:@[kNewScope]
  1156. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  1157. presentingViewController:_presentingViewController
  1158. #elif TARGET_OS_OSX
  1159. presentingWindow:_presentingWindow
  1160. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  1161. completion:completion];
  1162. } else {
  1163. if (useAdditionalScopes) {
  1164. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  1165. [_signIn signInWithPresentingViewController:_presentingViewController
  1166. #elif TARGET_OS_OSX
  1167. [_signIn signInWithPresentingWindow:_presentingWindow
  1168. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  1169. hint:_hint
  1170. additionalScopes:additionalScopes
  1171. completion:completion];
  1172. } else {
  1173. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  1174. [_signIn signInWithPresentingViewController:_presentingViewController
  1175. #elif TARGET_OS_OSX
  1176. [_signIn signInWithPresentingWindow:_presentingWindow
  1177. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  1178. hint:_hint
  1179. completion:completion];
  1180. }
  1181. }
  1182. [_authorization verify];
  1183. [_authState verify];
  1184. XCTAssertNotNil(_savedAuthorizationRequest);
  1185. NSDictionary<NSString *, NSObject *> *params = _savedAuthorizationRequest.additionalParameters;
  1186. XCTAssertEqualObjects(params[@"include_granted_scopes"], @"true");
  1187. XCTAssertEqualObjects(params[kSDKVersionLoggingParameter], GIDVersion());
  1188. XCTAssertEqualObjects(params[kEnvironmentLoggingParameter], GIDEnvironment());
  1189. XCTAssertNotNil(_savedAuthorizationCallback);
  1190. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  1191. XCTAssertEqual(_savedPresentingViewController, _presentingViewController);
  1192. #elif TARGET_OS_OSX
  1193. XCTAssertEqual(_savedPresentingWindow, _presentingWindow);
  1194. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  1195. // maybeFetchToken
  1196. if (!(authError || modalCancel)) {
  1197. [[[_authState expect] andReturn:nil] lastTokenResponse];
  1198. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  1199. // Corresponds to EMM support
  1200. [[[_authState expect] andReturn:authResponse] lastAuthorizationResponse];
  1201. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  1202. [[[_authState expect] andReturn:nil] lastTokenResponse];
  1203. [[[_authState expect] andReturn:authResponse] lastAuthorizationResponse];
  1204. [[[_authState expect] andReturn:authResponse] lastAuthorizationResponse];
  1205. }
  1206. // Simulate auth endpoint response
  1207. if (modalCancel) {
  1208. NSError *error = [NSError errorWithDomain:OIDGeneralErrorDomain
  1209. code:OIDErrorCodeUserCanceledAuthorizationFlow
  1210. userInfo:nil];
  1211. _savedAuthorizationCallback(nil, error);
  1212. } else {
  1213. _savedAuthorizationCallback(authResponse, nil);
  1214. }
  1215. if (authError || modalCancel) {
  1216. return;
  1217. }
  1218. [_authState verify];
  1219. }
  1220. if (restoredSignIn && oldAccessToken) {
  1221. XCTestExpectation *callbackShouldBeCalledExpectation =
  1222. [self expectationWithDescription:@"Callback should be called"];
  1223. [_signIn restorePreviousSignInWithCompletion:^(GIDGoogleUser * _Nullable user,
  1224. NSError * _Nullable error) {
  1225. [callbackShouldBeCalledExpectation fulfill];
  1226. XCTAssertNil(error, @"should have no error");
  1227. }];
  1228. }
  1229. if (!restoredSignIn || (restoredSignIn && oldAccessToken)) {
  1230. XCTAssertNotNil(_savedTokenRequest);
  1231. XCTAssertNotNil(_savedTokenCallback);
  1232. // OIDTokenCallback
  1233. if (tokenError) {
  1234. [[_authState expect] updateWithTokenResponse:nil error:tokenError];
  1235. } else {
  1236. [[_authState expect] updateWithTokenResponse:[OCMArg any] error:nil];
  1237. }
  1238. }
  1239. if (tokenError) {
  1240. _savedTokenCallback(nil, tokenError);
  1241. return;
  1242. }
  1243. // DecodeIdTokenCallback
  1244. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  1245. // SaveAuthCallback
  1246. __block OIDAuthState *authState;
  1247. __block OIDTokenResponse *updatedTokenResponse;
  1248. __block OIDAuthorizationResponse *updatedAuthorizationResponse;
  1249. __block GIDProfileData *profileData;
  1250. if (keychainError) {
  1251. _saveAuthorizationReturnValue = NO;
  1252. } else {
  1253. if (addScopesFlow) {
  1254. [[[_authState expect] andReturn:authResponse] lastAuthorizationResponse];
  1255. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  1256. [[_user expect] updateWithTokenResponse:SAVE_TO_ARG_BLOCK(updatedTokenResponse)
  1257. authorizationResponse:SAVE_TO_ARG_BLOCK(updatedAuthorizationResponse)
  1258. profileData:SAVE_TO_ARG_BLOCK(profileData)];
  1259. } else {
  1260. [[[_user expect] andReturn:_user] alloc];
  1261. (void)[[[_user expect] andReturn:_user] initWithAuthState:SAVE_TO_ARG_BLOCK(authState)
  1262. profileData:SAVE_TO_ARG_BLOCK(profileData)];
  1263. }
  1264. }
  1265. // CompletionCallback - mock server auth code parsing
  1266. if (!keychainError) {
  1267. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  1268. }
  1269. if (restoredSignIn && !oldAccessToken) {
  1270. XCTestExpectation *restoredSignInExpectation = [self expectationWithDescription:@"Callback should be called"];
  1271. [_signIn restorePreviousSignInWithCompletion:^(GIDGoogleUser * _Nullable user,
  1272. NSError * _Nullable error) {
  1273. [restoredSignInExpectation fulfill];
  1274. XCTAssertNil(error, @"should have no error");
  1275. }];
  1276. } else {
  1277. // Simulate token endpoint response.
  1278. _savedTokenCallback(tokenResponse, nil);
  1279. }
  1280. if (keychainError) {
  1281. return;
  1282. }
  1283. [self waitForExpectationsWithTimeout:1 handler:nil];
  1284. [_authState verify];
  1285. XCTAssertTrue(_keychainSaved, @"should save to keychain");
  1286. if (addScopesFlow) {
  1287. XCTAssertNotNil(updatedTokenResponse);
  1288. XCTAssertNotNil(updatedAuthorizationResponse);
  1289. } else {
  1290. XCTAssertNotNil(authState);
  1291. }
  1292. // Check fat ID token decoding
  1293. XCTAssertEqualObjects(profileData.name, kFatName);
  1294. XCTAssertEqualObjects(profileData.givenName, kFatGivenName);
  1295. XCTAssertEqualObjects(profileData.familyName, kFatFamilyName);
  1296. XCTAssertTrue(profileData.hasImage);
  1297. // If attempt to authenticate again, will reuse existing auth object.
  1298. _completionCalled = NO;
  1299. _keychainRemoved = NO;
  1300. _keychainSaved = NO;
  1301. _authError = nil;
  1302. __block GIDGoogleUserCompletion completion;
  1303. [[_user expect] refreshTokensIfNeededWithCompletion:SAVE_TO_ARG_BLOCK(completion)];
  1304. XCTestExpectation *restorePreviousSignInExpectation =
  1305. [self expectationWithDescription:@"Callback should be called"];
  1306. [_signIn restorePreviousSignInWithCompletion:^(GIDGoogleUser * _Nullable user,
  1307. NSError * _Nullable error) {
  1308. [restorePreviousSignInExpectation fulfill];
  1309. XCTAssertNil(error, @"should have no error");
  1310. }];
  1311. completion(_user, nil);
  1312. [self waitForExpectationsWithTimeout:1 handler:nil];
  1313. XCTAssertFalse(_keychainRemoved, @"should not remove keychain");
  1314. XCTAssertFalse(_keychainSaved, @"should not save to keychain again");
  1315. if (restoredSignIn) {
  1316. // Ignore the return value
  1317. OCMVerify((void)[_keychainStore retrieveAuthSessionWithError:OCMArg.anyObjectRef]);
  1318. OCMVerify([_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]);
  1319. }
  1320. }
  1321. @end