GIDSignInTest.m 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114
  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 <SafariServices/SafariServices.h>
  15. #import <UIKit/UIKit.h>
  16. #import <XCTest/XCTest.h>
  17. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDAuthentication.h"
  18. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDGoogleUser.h"
  19. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDProfileData.h"
  20. #import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h"
  21. #import "GoogleSignIn/Sources/GIDGoogleUser_Private.h"
  22. #import "GoogleSignIn/Sources/GIDSignInInternalOptions.h"
  23. #import "GoogleSignIn/Sources/GIDSignIn_Private.h"
  24. #import "GoogleSignIn/Sources/GIDAuthentication_Private.h"
  25. #import "GoogleSignIn/Sources/GIDEMMErrorHandler.h"
  26. #import "GoogleSignIn/Tests/Unit/GIDFakeFetcher.h"
  27. #import "GoogleSignIn/Tests/Unit/GIDFakeFetcherService.h"
  28. #import "GoogleSignIn/Tests/Unit/GIDFakeMainBundle.h"
  29. #import "GoogleSignIn/Tests/Unit/GIDFakeSignIn.h"
  30. #import "GoogleSignIn/Tests/Unit/OIDAuthorizationResponse+Testing.h"
  31. #import "GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h"
  32. #import <AppAuth/OIDAuthState.h>
  33. #import <AppAuth/OIDAuthorizationRequest.h>
  34. #import <AppAuth/OIDAuthorizationResponse.h>
  35. #import <AppAuth/OIDAuthorizationService.h>
  36. #import <AppAuth/OIDError.h>
  37. #import <AppAuth/OIDGrantTypes.h>
  38. #import <AppAuth/OIDTokenRequest.h>
  39. #import <AppAuth/OIDTokenResponse.h>
  40. #import <AppAuth/OIDURLQueryComponent.h>
  41. #import <AppAuth/OIDAuthorizationService+IOS.h>
  42. #import <GTMAppAuth/GTMAppAuthFetcherAuthorization+Keychain.h>
  43. #import <GTMAppAuth/GTMAppAuthFetcherAuthorization.h>
  44. #import <GTMSessionFetcher/GTMSessionFetcher.h>
  45. #import <OCMock/OCMock.h>
  46. // Create a BLOCK to store the actual address for arg in param.
  47. #define SAVE_TO_ARG_BLOCK(param) [OCMArg checkWithBlock:^(id arg) {\
  48. param = arg;\
  49. return YES;\
  50. }]
  51. #define COPY_TO_ARG_BLOCK(param) [OCMArg checkWithBlock:^(id arg) {\
  52. param = [arg copy];\
  53. return YES;\
  54. }]
  55. static NSString * const kClientId = @"FakeClientID";
  56. static NSString * const kDotReversedClientId = @"FakeClientID";
  57. static NSString * const kClientId2 = @"FakeClientID2";
  58. static NSString * const kAppBundleId = @"FakeBundleID";
  59. static NSString * const kLanguage = @"FakeLanguage";
  60. static NSString * const kScope = @"FakeScope";
  61. static NSString * const kScope2 = @"FakeScope2";
  62. static NSString * const kAuthCode = @"FakeAuthCode";
  63. static NSString * const kPassword = @"FakePassword";
  64. static NSString * const kFakeKeychainName = @"FakeKeychainName";
  65. static NSString * const kUserEmail = @"FakeUserEmail";
  66. static NSString * const kVerifier = @"FakeVerifier";
  67. static NSString * const kOpenIDRealm = @"FakeRealm";
  68. static NSString * const kFakeHostedDomain = @"fakehosteddomain.com";
  69. static NSString * const kFakeUserName = @"fake username";
  70. static NSString * const kFakeUserGivenName = @"fake";
  71. static NSString * const kFakeUserFamilyName = @"username";
  72. static NSString * const kFakeUserPictureURL = @"fake_user_picture_url";
  73. static const NSTimeInterval kIDTokenExpiration = 12345;
  74. static NSString * const kContinueURL = @"com.google.UnitTests:/oauth2callback";
  75. static NSString * const kContinueURLWithClientID = @"FakeClientID:/oauth2callback";
  76. static NSString * const kWrongSchemeURL = @"wrong.app:/oauth2callback";
  77. static NSString * const kWrongPathURL = @"com.google.UnitTests:/wrong_path";
  78. static NSString * const kEMMRestartAuthURL =
  79. @"com.google.UnitTests:///emmcallback?action=restart_auth";
  80. static NSString * const kEMMWrongPathURL =
  81. @"com.google.UnitTests:///unknowcallback?action=restart_auth";
  82. static NSString * const kEMMWrongActionURL =
  83. @"com.google.UnitTests:///emmcallback?action=unrecognized";
  84. static NSString * const kDevicePolicyAppBundleID = @"com.google.DevicePolicy";
  85. static NSString * const kAppHasRunBeforeKey = @"GPP_AppHasRunBefore";
  86. static NSString * const kFingerprintKeychainName = @"fingerprint";
  87. static NSString * const kVerifierKeychainName = @"verifier";
  88. static NSString * const kVerifierKey = @"verifier";
  89. static NSString * const kOpenIDRealmKey = @"openid.realm";
  90. static NSString * const kSavedKeychainServiceName = @"saved-keychain";
  91. static NSString * const kKeychainAccountName = @"GooglePlus";
  92. static NSString * const kUserNameKey = @"name";
  93. static NSString * const kUserGivenNameKey = @"givenName";
  94. static NSString * const kUserFamilyNameKey = @"familyName";
  95. static NSString * const kUserImageKey = @"picture";
  96. static NSString * const kAppName = @"UnitTests";
  97. static NSString * const kUserIDKey = @"userID";
  98. static NSString * const kHostedDomainKey = @"hostedDomain";
  99. static NSString * const kIDTokenExpirationKey = @"idTokenExp";
  100. static NSString * const kScopeKey = @"scope";
  101. // Basic profile (Fat ID Token / userinfo endpoint) keys
  102. static NSString *const kBasicProfilePictureKey = @"picture";
  103. static NSString *const kBasicProfileNameKey = @"name";
  104. static NSString *const kBasicProfileGivenNameKey = @"given_name";
  105. static NSString *const kBasicProfileFamilyNameKey = @"family_name";
  106. static NSString * const kCustomKeychainName = @"CUSTOM_KEYCHAIN_NAME";
  107. static NSString * const kAddActivity = @"http://schemas.google.com/AddActivity";
  108. static NSString * const kErrorDomain = @"ERROR_DOMAIN";
  109. static NSInteger const kErrorCode = 212;
  110. static NSString *const kDriveScope = @"https://www.googleapis.com/auth/drive";
  111. static NSString *const kTokenURL = @"https://oauth2.googleapis.com/token";
  112. static NSString *const kFakeURL = @"http://foo.com";
  113. static NSString *const kEMMSupport = @"1";
  114. /// Unique pointer value for KVO tests.
  115. static void *kTestObserverContext = &kTestObserverContext;
  116. // This category is used to allow the test to swizzle a private method.
  117. @interface UIViewController (Testing)
  118. // This private method provides access to the window. It's declared here to avoid a warning about
  119. // an unrecognized selector in the test.
  120. - (UIWindow *)_window;
  121. @end
  122. // This class extension exposes GIDSignIn methods to our tests.
  123. @interface GIDSignIn ()
  124. // Exposing private method so we can call it to disambiguate between interactive and non-interactive
  125. // sign-in attempts for the purposes of testing the GIDSignInUIDelegate (which should not be
  126. // called in the case of a non-interactive sign in).
  127. - (void)authenticateMaybeInteractively:(BOOL)interactive withParams:(NSDictionary *)params;
  128. - (BOOL)assertValidPresentingViewContoller;
  129. @end
  130. @interface GIDSignInTest : XCTestCase <GIDSignInDelegate> {
  131. @private
  132. // Whether or not the OS version is eligible for EMM.
  133. BOOL _isEligibleForEMM;
  134. // Mock |OIDAuthState|.
  135. id _authState;
  136. // Mock |OIDTokenResponse|.
  137. id _tokenResponse;
  138. // Mock |OIDTokenRequest|.
  139. id _tokenRequest;
  140. // Mock |GTMAppAuthFetcherAuthorization|.
  141. id _authorization;
  142. // Mock |UIViewController|.
  143. id _presentingViewController;
  144. // Mock for |GIDGoogleUser|.
  145. id _user;
  146. // Mock for |GIDAuthentication|.
  147. id _authentication;
  148. // Mock for |OIDAuthorizationService|
  149. id _oidAuthorizationService;
  150. // Parameter saved from delegate call.
  151. NSError *_authError;
  152. // Whether delegate method has been called.
  153. BOOL _delegateCalled;
  154. // Fake fetcher service to emulate network requests.
  155. GIDFakeFetcherService *_fetcherService;
  156. // Fake [NSBundle mainBundle];
  157. GIDFakeMainBundle *_fakeMainBundle;
  158. // Whether |saveParamsToKeychainForName:authentication:| has been called.
  159. BOOL _keychainSaved;
  160. // Whether |removeAuthFromKeychainForName:| has been called.
  161. BOOL _keychainRemoved;
  162. // The |GIDSignIn| object being tested.
  163. GIDFakeSignIn *_signIn;
  164. // The saved authorization request.
  165. OIDAuthorizationRequest *_savedAuthorizationRequest;
  166. // The saved presentingViewController from the authorization request.
  167. UIViewController *_savedPresentingViewController;
  168. // The saved authorization callback.
  169. OIDAuthorizationCallback _savedAuthorizationCallback;
  170. // The saved token request.
  171. OIDTokenRequest *_savedTokenRequest;
  172. // The saved token request callback.
  173. OIDTokenCallback _savedTokenCallback;
  174. // Set of all |GIDSignIn| key paths which were observed to change.
  175. NSMutableSet *_changedKeyPaths;
  176. // Status returned by saveAuthorization:toKeychainForName:
  177. BOOL _saveAuthorizationReturnValue;
  178. }
  179. @end
  180. @implementation GIDSignInTest
  181. #pragma mark - Lifecycle
  182. - (void)setUp {
  183. [super setUp];
  184. _isEligibleForEMM = [UIDevice currentDevice].systemVersion.integerValue >= 9;
  185. _saveAuthorizationReturnValue = YES;
  186. // States
  187. _delegateCalled = NO;
  188. _keychainSaved = NO;
  189. _keychainRemoved = NO;
  190. _changedKeyPaths = [[NSMutableSet alloc] init];
  191. // Mocks
  192. // TODO(b/136089202): Prefer fakes over mocks.
  193. _presentingViewController = OCMStrictClassMock([UIViewController class]);
  194. _authState = OCMStrictClassMock([OIDAuthState class]);
  195. OCMStub([_authState alloc]).andReturn(_authState);
  196. OCMStub([_authState initWithAuthorizationResponse:OCMOCK_ANY]).andReturn(_authState);
  197. _tokenResponse = OCMStrictClassMock([OIDTokenResponse class]);
  198. _tokenRequest = OCMStrictClassMock([OIDTokenRequest class]);
  199. _authorization = OCMStrictClassMock([GTMAppAuthFetcherAuthorization class]);
  200. OCMStub([_authorization authorizationFromKeychainForName:OCMOCK_ANY]).andReturn(_authorization);
  201. OCMStub([_authorization alloc]).andReturn(_authorization);
  202. OCMStub([_authorization initWithAuthState:OCMOCK_ANY]).andReturn(_authorization);
  203. OCMStub([_authorization saveAuthorization:OCMOCK_ANY toKeychainForName:OCMOCK_ANY])
  204. .andDo(^(NSInvocation *invocation) {
  205. _keychainSaved = _saveAuthorizationReturnValue;
  206. [invocation setReturnValue:&_saveAuthorizationReturnValue];
  207. });
  208. OCMStub([_authorization removeAuthorizationFromKeychainForName:OCMOCK_ANY])
  209. .andDo(^(NSInvocation *invocation) {
  210. _keychainRemoved = YES;
  211. });
  212. _user = OCMStrictClassMock([GIDGoogleUser class]);
  213. _authentication = OCMStrictClassMock([GIDAuthentication class]);
  214. _oidAuthorizationService = OCMStrictClassMock([OIDAuthorizationService class]);
  215. OCMStub([_oidAuthorizationService
  216. presentAuthorizationRequest:SAVE_TO_ARG_BLOCK(_savedAuthorizationRequest)
  217. presentingViewController:SAVE_TO_ARG_BLOCK(_savedPresentingViewController)
  218. callback:COPY_TO_ARG_BLOCK(_savedAuthorizationCallback)]);
  219. OCMStub([_oidAuthorizationService performTokenRequest:SAVE_TO_ARG_BLOCK(_savedTokenRequest)
  220. callback:COPY_TO_ARG_BLOCK(_savedTokenCallback)]);
  221. // Fakes
  222. _fetcherService = [[GIDFakeFetcherService alloc] init];
  223. _fakeMainBundle = [[GIDFakeMainBundle alloc] init];
  224. [_fakeMainBundle startFakingWithBundleId:kAppBundleId clientId:kClientId];
  225. [_fakeMainBundle fakeAllSchemesSupported];
  226. // Object under test
  227. [[NSUserDefaults standardUserDefaults] setBool:YES
  228. forKey:kAppHasRunBeforeKey];
  229. _signIn = [[GIDFakeSignIn alloc] init];
  230. _signIn.delegate = self;
  231. _signIn.presentingViewController = _presentingViewController;
  232. [_signIn addObserver:self
  233. forKeyPath:NSStringFromSelector(@selector(clientID))
  234. options:0
  235. context:kTestObserverContext];
  236. [_signIn addObserver:self
  237. forKeyPath:NSStringFromSelector(@selector(currentUser))
  238. options:0
  239. context:kTestObserverContext];
  240. [_signIn startMocking];
  241. }
  242. - (void)tearDown {
  243. OCMVerifyAll(_authState);
  244. OCMVerifyAll(_tokenResponse);
  245. OCMVerifyAll(_tokenRequest);
  246. OCMVerifyAll(_authorization);
  247. OCMVerifyAll(_presentingViewController);
  248. OCMVerifyAll(_user);
  249. OCMVerifyAll(_authentication);
  250. OCMVerifyAll(_oidAuthorizationService);
  251. [_fakeMainBundle stopFaking];
  252. [_signIn stopMocking];
  253. _signIn.delegate = nil;
  254. _signIn.presentingViewController = nil;
  255. [super tearDown];
  256. [_signIn removeObserver:self
  257. forKeyPath:NSStringFromSelector(@selector(clientID))
  258. context:kTestObserverContext];
  259. [_signIn removeObserver:self
  260. forKeyPath:NSStringFromSelector(@selector(currentUser))
  261. context:kTestObserverContext];
  262. }
  263. #pragma mark - Tests
  264. - (void)testShareInstance {
  265. GIDSignIn *signIn1 = [GIDSignIn sharedInstance];
  266. GIDSignIn *signIn2 = [GIDSignIn sharedInstance];
  267. XCTAssertTrue(signIn1 == signIn2, @"shared instance must be singleton");
  268. }
  269. - (void)testDefaultScope {
  270. // Stop mocking |[GIDSignIn sharedInstance]|, since we are testing the default scopes,
  271. // which is modified in GIDFakeSignIn.
  272. [_signIn stopMocking];
  273. GIDSignIn *signIn = [GIDSignIn sharedInstance];
  274. XCTAssertTrue([[signIn scopes] count] == 0,
  275. @"there should be no default scope");
  276. [_signIn startMocking];
  277. }
  278. - (void)testHasPreviousSignIn_HasBeenAuthenticated {
  279. [[[_authorization expect] andReturn:_authState] authState];
  280. [[[_authState expect] andReturnValue:[NSNumber numberWithBool:YES]] isAuthorized];
  281. XCTAssertTrue([_signIn hasPreviousSignIn], @"should return |YES|");
  282. [_authorization verify];
  283. [_authState verify];
  284. XCTAssertFalse(_keychainRemoved, @"should not remove keychain");
  285. XCTAssertFalse(_delegateCalled, @"should not call delegate");
  286. XCTAssertNil(_authError, @"should have no error");
  287. }
  288. - (void)testHasPreviousSignIn_HasNotBeenAuthenticated {
  289. [[[_authorization expect] andReturn:_authState] authState];
  290. [[[_authState expect] andReturnValue:[NSNumber numberWithBool:NO]] isAuthorized];
  291. XCTAssertFalse([_signIn hasPreviousSignIn], @"should return |NO|");
  292. [_authorization verify];
  293. [_authState verify];
  294. XCTAssertFalse(_keychainRemoved, @"should not remove keychain");
  295. XCTAssertFalse(_delegateCalled, @"should not call delegate");
  296. }
  297. - (void)testRestorePreviousSignInWhenSignedOut {
  298. [[[_authorization expect] andReturn:_authState] authState];
  299. [[[_authState expect] andReturnValue:[NSNumber numberWithBool:NO]] isAuthorized];
  300. _delegateCalled = NO;
  301. _authError = nil;
  302. [_signIn restorePreviousSignIn];
  303. [_authorization verify];
  304. [_authState verify];
  305. XCTAssertTrue(_delegateCalled, @"should have called delegate");
  306. XCTAssertNotNil(_authError, @"error should not have been nil");
  307. XCTAssertEqual(_authError.domain,
  308. kGIDSignInErrorDomain,
  309. @"error domain should have been the sign-in error domain.");
  310. XCTAssertEqual(_authError.code,
  311. kGIDSignInErrorCodeHasNoAuthInKeychain,
  312. @"error code should have been the 'NoAuthInKeychain' error code.");
  313. }
  314. // Verifies |shouldFetchBasicProfile| is default YES.
  315. - (void)testShouldFetchBasicProfileDefault {
  316. XCTAssertTrue(_signIn.shouldFetchBasicProfile, @"shouldFetchBasicProfile should be default YES");
  317. }
  318. - (void)testOAuthLogin {
  319. [self OAuthLoginWithOptions:nil
  320. authError:nil
  321. tokenError:nil
  322. emmPasscodeInfoRequired:NO
  323. keychainError:NO
  324. restoredSignIn:NO
  325. oldAccessToken:NO
  326. modalCancel:NO];
  327. }
  328. - (void)testOAuthLogin_RestoredSignIn {
  329. [self OAuthLoginWithOptions:nil
  330. authError:nil
  331. tokenError:nil
  332. emmPasscodeInfoRequired:NO
  333. keychainError:NO
  334. restoredSignIn:YES
  335. oldAccessToken:NO
  336. modalCancel:NO];
  337. }
  338. - (void)testOAuthLogin_RestoredSignInOldAccessToken {
  339. [self OAuthLoginWithOptions:nil
  340. authError:nil
  341. tokenError:nil
  342. emmPasscodeInfoRequired:NO
  343. keychainError:NO
  344. restoredSignIn:YES
  345. oldAccessToken:YES
  346. modalCancel:NO];
  347. }
  348. - (void)testOAuthLogin_ExtraParams {
  349. GIDSignInInternalOptions *options =
  350. [GIDSignInInternalOptions optionsWithExtraParams:@{ @"gpbtn" : @"0.1" }];
  351. [self OAuthLoginWithOptions:options
  352. authError:nil
  353. tokenError:nil
  354. emmPasscodeInfoRequired:NO
  355. keychainError:NO
  356. restoredSignIn:NO
  357. oldAccessToken:NO
  358. modalCancel:NO];
  359. NSDictionary<NSString *, NSObject *> *params = _savedAuthorizationRequest.additionalParameters;
  360. XCTAssertEqualObjects(params[@"gpbtn"], @"0.1", @"extra parameter should match");
  361. }
  362. - (void)testOpenIDRealm {
  363. _signIn.openIDRealm = kOpenIDRealm;
  364. [self OAuthLoginWithOptions:nil
  365. authError:nil
  366. tokenError:nil
  367. emmPasscodeInfoRequired:NO
  368. keychainError:NO
  369. restoredSignIn:NO
  370. oldAccessToken:NO
  371. modalCancel:NO];
  372. NSDictionary<NSString *, NSString *> *params = _savedTokenRequest.additionalParameters;
  373. XCTAssertEqual(params[kOpenIDRealmKey], kOpenIDRealm, @"OpenID Realm should match.");
  374. }
  375. - (void)testOAuthLogin_LoginHint {
  376. _signIn.loginHint = kUserEmail;
  377. [self OAuthLoginWithOptions:nil
  378. authError:nil
  379. tokenError:nil
  380. emmPasscodeInfoRequired:NO
  381. keychainError:NO
  382. restoredSignIn:NO
  383. oldAccessToken:NO
  384. modalCancel:NO];
  385. NSDictionary<NSString *, NSObject *> *params = _savedAuthorizationRequest.additionalParameters;
  386. XCTAssertEqualObjects(params[@"login_hint"], kUserEmail, @"login hint should match");
  387. }
  388. - (void)testOAuthLogin_HostedDomain {
  389. _signIn.hostedDomain = kHostedDomain;
  390. [self OAuthLoginWithOptions:nil
  391. authError:nil
  392. tokenError:nil
  393. emmPasscodeInfoRequired:NO
  394. keychainError:NO
  395. restoredSignIn:NO
  396. oldAccessToken:NO
  397. modalCancel:NO];
  398. NSDictionary<NSString *, NSObject *> *params = _savedAuthorizationRequest.additionalParameters;
  399. XCTAssertEqualObjects(params[@"hd"], kHostedDomain, @"hosted domain should match");
  400. }
  401. - (void)testOAuthLogin_ConsentCanceled {
  402. [self OAuthLoginWithOptions:nil
  403. authError:@"access_denied"
  404. tokenError:nil
  405. emmPasscodeInfoRequired:NO
  406. keychainError:NO
  407. restoredSignIn:NO
  408. oldAccessToken:NO
  409. modalCancel:NO];
  410. XCTAssertTrue(_delegateCalled, @"should call delegate");
  411. XCTAssertEqual(_authError.code, kGIDSignInErrorCodeCanceled);
  412. }
  413. - (void)testOAuthLogin_ModalCanceled {
  414. [self OAuthLoginWithOptions:nil
  415. authError:nil
  416. tokenError:nil
  417. emmPasscodeInfoRequired:NO
  418. keychainError:NO
  419. restoredSignIn:NO
  420. oldAccessToken:NO
  421. modalCancel:YES];
  422. XCTAssertTrue(_delegateCalled, @"should call delegate");
  423. XCTAssertEqual(_authError.code, kGIDSignInErrorCodeCanceled);
  424. }
  425. - (void)testOAuthLogin_KeychainError {
  426. [self OAuthLoginWithOptions:nil
  427. authError:nil
  428. tokenError:nil
  429. emmPasscodeInfoRequired:NO
  430. keychainError:YES
  431. restoredSignIn:NO
  432. oldAccessToken:NO
  433. modalCancel:NO];
  434. XCTAssertFalse(_keychainSaved, @"should save to keychain");
  435. XCTAssertTrue(_delegateCalled, @"should call delegate");
  436. XCTAssertEqualObjects(_authError.domain, kGIDSignInErrorDomain);
  437. XCTAssertEqual(_authError.code, kGIDSignInErrorCodeKeychain);
  438. }
  439. - (void)testClientIDKeyValueObserving {
  440. _signIn.clientID = kClientId;
  441. XCTAssertFalse([_changedKeyPaths containsObject:NSStringFromSelector(@selector(clientID))],
  442. @"Should not notify observers when client id set to same value as before.");
  443. _signIn.clientID = kClientId2;
  444. XCTAssertTrue([_changedKeyPaths containsObject:NSStringFromSelector(@selector(clientID))],
  445. @"Should notify observers that client id changed.");
  446. }
  447. - (void)testSignOut {
  448. [_signIn signOut];
  449. XCTAssertNil(_signIn.currentUser, @"should not have a current user");
  450. XCTAssertTrue(_keychainRemoved, @"should remove keychain");
  451. XCTAssertTrue([_changedKeyPaths containsObject:NSStringFromSelector(@selector(currentUser))],
  452. @"should notify observers that signed in user changed");
  453. }
  454. - (void)testNotHandleWrongScheme {
  455. XCTAssertFalse([_signIn handleURL:[NSURL URLWithString:kWrongSchemeURL]],
  456. @"should not handle URL");
  457. XCTAssertFalse(_keychainSaved, @"should not save to keychain");
  458. XCTAssertFalse(_delegateCalled, @"should not call delegate");
  459. }
  460. - (void)testNotHandleWrongPath {
  461. XCTAssertFalse([_signIn handleURL:[NSURL URLWithString:kWrongPathURL]], @"should not handle URL");
  462. XCTAssertFalse(_keychainSaved, @"should not save to keychain");
  463. XCTAssertFalse(_delegateCalled, @"should not call delegate");
  464. }
  465. // Verifies disconnect calls delegate disconnect method with no errors if access token is present.
  466. - (void)testDisconnect_accessToken {
  467. [[[_authorization expect] andReturn:_authState] authState];
  468. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  469. [[[_tokenResponse expect] andReturn:kAccessToken] accessToken];
  470. [[[_authorization expect] andReturn:_fetcherService] fetcherService];
  471. OCMockObject *mockDelegate = OCMStrictProtocolMock(@protocol(GIDSignInDelegate));
  472. _signIn.delegate = (id <GIDSignInDelegate>)mockDelegate;
  473. [_signIn disconnect];
  474. [mockDelegate verify];
  475. [self verifyAndRevokeToken:kAccessToken delegate:mockDelegate];
  476. [_authorization verify];
  477. [_authState verify];
  478. [_tokenResponse verify];
  479. }
  480. // Verifies disconnect calls delegate disconnect method with no errors if refresh token is present.
  481. - (void)testDisconnect_refreshToken {
  482. [[[_authorization expect] andReturn:_authState] authState];
  483. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  484. [[[_tokenResponse expect] andReturn:nil] accessToken];
  485. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  486. [[[_tokenResponse expect] andReturn:kRefreshToken] refreshToken];
  487. [[[_authorization expect] andReturn:_fetcherService] fetcherService];
  488. OCMockObject *mockDelegate = OCMStrictProtocolMock(@protocol(GIDSignInDelegate));
  489. _signIn.delegate = (id <GIDSignInDelegate>)mockDelegate;
  490. [_signIn disconnect];
  491. [mockDelegate verify];
  492. [self verifyAndRevokeToken:kRefreshToken delegate:mockDelegate];
  493. [_authorization verify];
  494. [_authState verify];
  495. [_tokenResponse verify];
  496. }
  497. // Verifies disconnect errors are passed along to the delegate.
  498. - (void)testDisconnect_errors {
  499. [[[_authorization expect] andReturn:_authState] authState];
  500. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  501. [[[_tokenResponse expect] andReturn:kAccessToken] accessToken];
  502. [[[_authorization expect] andReturn:_fetcherService] fetcherService];
  503. OCMockObject *mockDelegate = OCMStrictProtocolMock(@protocol(GIDSignInDelegate));
  504. _signIn.delegate = (id <GIDSignInDelegate>)mockDelegate;
  505. [_signIn disconnect];
  506. [mockDelegate verify];
  507. XCTAssertTrue([self isFetcherStarted], @"should start fetching");
  508. // Emulate result back from server.
  509. NSError *error = [self error];
  510. [[mockDelegate expect] signIn:_signIn didDisconnectWithUser:nil withError:error];
  511. [self didFetch:nil error:error];
  512. [mockDelegate verify];
  513. [_authorization verify];
  514. [_authState verify];
  515. [_tokenResponse verify];
  516. }
  517. // Verifies disconnect calls delegate disconnect method and clear keychain with no errors if no
  518. // tokens are present.
  519. - (void)testDisconnect_noTokens {
  520. [[[_authorization expect] andReturn:_authState] authState];
  521. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  522. [[[_tokenResponse expect] andReturn:nil] accessToken];
  523. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  524. [[[_tokenResponse expect] andReturn:nil] refreshToken];
  525. OCMockObject *mockDelegate = OCMStrictProtocolMock(@protocol(GIDSignInDelegate));
  526. [[mockDelegate expect] signIn:_signIn
  527. didDisconnectWithUser:nil
  528. withError:nil];
  529. _signIn.delegate = (id <GIDSignInDelegate>)mockDelegate;
  530. [_signIn disconnect];
  531. [mockDelegate verify];
  532. XCTAssertFalse([self isFetcherStarted], @"should not fetch");
  533. XCTAssertTrue(_keychainRemoved, @"keychain should be removed");
  534. [_authorization verify];
  535. [_authState verify];
  536. [_tokenResponse verify];
  537. }
  538. - (void)testPresentingViewControllerException {
  539. _signIn.presentingViewController = nil;
  540. XCTAssertThrows([_signIn signIn]);
  541. }
  542. - (void)testNoPresentingViewControllerExceptionForSilentSignIn {
  543. [[[_authorization expect] andReturn:_authState] authState];
  544. [[[_authState expect] andReturnValue:[NSNumber numberWithBool:NO]] isAuthorized];
  545. _signIn.presentingViewController = nil;
  546. [_signIn restorePreviousSignIn];
  547. [_authorization verify];
  548. [_authState verify];
  549. }
  550. - (void)testClientIDMissingException {
  551. _signIn.clientID = nil;
  552. BOOL threw = NO;
  553. @try {
  554. [_signIn signIn];
  555. } @catch (NSException *exception) {
  556. threw = YES;
  557. XCTAssertEqualObjects(exception.description,
  558. @"You must specify |clientID| for |GIDSignIn|");
  559. } @finally {
  560. }
  561. XCTAssert(threw);
  562. }
  563. - (void)testSchemesNotSupportedException {
  564. [_fakeMainBundle fakeMissingAllSchemes];
  565. BOOL threw = NO;
  566. @try {
  567. [_signIn signIn];
  568. } @catch (NSException *exception) {
  569. threw = YES;
  570. XCTAssertEqualObjects(exception.description,
  571. @"Your app is missing support for the following URL schemes: "
  572. "fakeclientid");
  573. } @finally {
  574. }
  575. XCTAssert(threw);
  576. }
  577. #pragma mark - Tests - UI Delegate
  578. - (void)testAssertValidPresentingViewControllerWithNilPresentingViewController {
  579. _signIn.presentingViewController = nil;
  580. XCTAssertThrows([_signIn assertValidPresentingViewContoller]);
  581. }
  582. #pragma mark - Tests - GIDSignInDelegate
  583. - (void)testDisconnectWithUserWithoutAuth {
  584. [[[_authorization expect] andReturn:_authState] authState];
  585. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  586. [[[_tokenResponse expect] andReturn:nil] accessToken];
  587. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  588. [[[_tokenResponse expect] andReturn:nil] refreshToken];
  589. id signInDelegateMock = OCMStrictProtocolMock(@protocol(GIDSignInDelegate));
  590. [[signInDelegateMock expect] signIn:[OCMArg any]
  591. didDisconnectWithUser:[OCMArg any]
  592. withError:[OCMArg isNil]];
  593. _signIn.delegate = signInDelegateMock;
  594. [_signIn disconnect];
  595. [_authorization verify];
  596. [_authState verify];
  597. [_tokenResponse verify];
  598. [signInDelegateMock verify];
  599. }
  600. - (void)testDisconnectWithUserWithAccessToken {
  601. [[[_authorization expect] andReturn:_authState] authState];
  602. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  603. [[[_tokenResponse expect] andReturn:kAccessToken] accessToken];
  604. [[[_authorization expect] andReturn:_fetcherService] fetcherService];
  605. id signInDelegateMock = OCMStrictProtocolMock(@protocol(GIDSignInDelegate));
  606. [[signInDelegateMock expect] signIn:[OCMArg any]
  607. didDisconnectWithUser:[OCMArg any]
  608. withError:[OCMArg isNil]];
  609. _signIn.delegate = signInDelegateMock;
  610. [_signIn disconnect];
  611. [_fetcherService.fetchers[0] didFinishWithData:nil error:nil];
  612. [_authorization verify];
  613. [_authState verify];
  614. [_tokenResponse verify];
  615. [signInDelegateMock verify];
  616. }
  617. #pragma mark - Restarting Authentication Tests
  618. // Verifies that URL is not handled if there is no pending sign-in
  619. - (void)testRequiringPendingSignIn {
  620. BOOL result = [_signIn handleURL:[NSURL URLWithString:kEMMRestartAuthURL]];
  621. XCTAssertFalse(result);
  622. }
  623. #pragma mark - EMM tests
  624. - (void)testEmmSupportRequestParameters {
  625. [self OAuthLoginWithOptions:nil
  626. authError:nil
  627. tokenError:nil
  628. emmPasscodeInfoRequired:NO
  629. keychainError:NO
  630. restoredSignIn:NO
  631. oldAccessToken:NO
  632. modalCancel:NO];
  633. NSString *systemName = [UIDevice currentDevice].systemName;
  634. if ([systemName isEqualToString:@"iPhone OS"]) {
  635. systemName = @"iOS";
  636. }
  637. NSString *expectedOSVersion = [NSString stringWithFormat:@"%@ %@",
  638. systemName, [UIDevice currentDevice].systemVersion];
  639. NSDictionary<NSString *, NSObject *> *authParams =
  640. _savedAuthorizationRequest.additionalParameters;
  641. NSDictionary<NSString *, NSString *> *tokenParams = _savedTokenRequest.additionalParameters;
  642. if (_isEligibleForEMM) {
  643. XCTAssertEqualObjects(authParams[@"emm_support"], kEMMSupport,
  644. @"EMM support should match in auth request");
  645. XCTAssertEqualObjects(authParams[@"device_os"], expectedOSVersion,
  646. @"OS version should match in auth request");
  647. XCTAssertEqualObjects(tokenParams[@"emm_support"], kEMMSupport,
  648. @"EMM support should match in token request");
  649. XCTAssertEqualObjects(tokenParams[@"device_os"],
  650. expectedOSVersion,
  651. @"OS version should match in token request");
  652. XCTAssertNil(tokenParams[@"emm_passcode_info"],
  653. @"no passcode info should be in token request");
  654. } else {
  655. XCTAssertNil(authParams[@"emm_support"],
  656. @"EMM support should not be in auth request for unsupported OS");
  657. XCTAssertNil(authParams[@"device_os"],
  658. @"OS version should not be in auth request for unsupported OS");
  659. XCTAssertNil(tokenParams[@"emm_support"],
  660. @"EMM support should not be in token request for unsupported OS");
  661. XCTAssertNil(tokenParams[@"device_os"],
  662. @"OS version should not be in token request for unsupported OS");
  663. XCTAssertNil(tokenParams[@"emm_passcode_info"],
  664. @"passcode info should not be in token request for unsupported OS");
  665. }
  666. }
  667. - (void)testEmmPasscodeInfo {
  668. [self OAuthLoginWithOptions:nil
  669. authError:nil
  670. tokenError:nil
  671. emmPasscodeInfoRequired:YES
  672. keychainError:NO
  673. restoredSignIn:NO
  674. oldAccessToken:NO
  675. modalCancel:NO];
  676. NSDictionary<NSString *, NSString *> *tokenParams = _savedTokenRequest.additionalParameters;
  677. if (_isEligibleForEMM) {
  678. XCTAssertNotNil(tokenParams[@"emm_passcode_info"],
  679. @"passcode info should be in token request");
  680. } else {
  681. XCTAssertNil(tokenParams[@"emm_passcode_info"],
  682. @"passcode info should not be in token request for unsupported OS");
  683. }
  684. }
  685. - (void)testAuthEndpointEMMError {
  686. if (!_isEligibleForEMM) {
  687. return;
  688. }
  689. id mockEMMErrorHandler = OCMStrictClassMock([GIDEMMErrorHandler class]);
  690. [[[mockEMMErrorHandler stub] andReturn:mockEMMErrorHandler] sharedInstance];
  691. __block void (^completion)(void);
  692. NSDictionary<NSString *, NSString *> *callbackParams = @{ @"error" : @"EMM Specific Error" };
  693. [[[mockEMMErrorHandler expect] andReturnValue:@YES]
  694. handleErrorFromResponse:callbackParams completion:SAVE_TO_ARG_BLOCK(completion)];
  695. [self OAuthLoginWithOptions:nil
  696. authError:callbackParams[@"error"]
  697. tokenError:nil
  698. emmPasscodeInfoRequired:NO
  699. keychainError:NO
  700. restoredSignIn:NO
  701. oldAccessToken:NO
  702. modalCancel:NO];
  703. [mockEMMErrorHandler verify];
  704. [mockEMMErrorHandler stopMocking];
  705. completion();
  706. XCTAssertFalse(_keychainSaved, @"should not save to keychain");
  707. XCTAssertTrue(_delegateCalled, @"should call delegate");
  708. XCTAssertNotNil(_authError, @"should have error");
  709. XCTAssertEqualObjects(_authError.domain, kGIDSignInErrorDomain);
  710. XCTAssertEqual(_authError.code, kGIDSignInErrorCodeEMM);
  711. XCTAssertNil(_signIn.currentUser, @"should not have current user");
  712. }
  713. - (void)testTokenEndpointEMMError {
  714. if (!_isEligibleForEMM) {
  715. return;
  716. }
  717. __block void (^completion)(NSError *);
  718. NSDictionary *errorJSON = @{ @"error" : @"EMM Specific Error" };
  719. NSError *emmError = [NSError errorWithDomain:@"anydomain"
  720. code:12345
  721. userInfo:@{ OIDOAuthErrorFieldError : errorJSON }];
  722. [[_authentication expect] handleTokenFetchEMMError:emmError
  723. completion:SAVE_TO_ARG_BLOCK(completion)];
  724. [self OAuthLoginWithOptions:nil
  725. authError:nil
  726. tokenError:emmError
  727. emmPasscodeInfoRequired:NO
  728. keychainError:NO
  729. restoredSignIn:NO
  730. oldAccessToken:NO
  731. modalCancel:NO];
  732. NSError *handledError = [NSError errorWithDomain:kGIDSignInErrorDomain
  733. code:kGIDSignInErrorCodeEMM
  734. userInfo:emmError.userInfo];
  735. completion(handledError);
  736. [_authentication verify];
  737. XCTAssertFalse(_keychainSaved, @"should not save to keychain");
  738. XCTAssertTrue(_delegateCalled, @"should call delegate");
  739. XCTAssertNotNil(_authError, @"should have error");
  740. XCTAssertEqualObjects(_authError.domain, kGIDSignInErrorDomain);
  741. XCTAssertEqual(_authError.code, kGIDSignInErrorCodeEMM);
  742. XCTAssertNil(_signIn.currentUser, @"should not have current user");
  743. }
  744. #pragma mark - Helpers
  745. // Whether or not a fetcher has been started.
  746. - (BOOL)isFetcherStarted {
  747. NSUInteger count = _fetcherService.fetchers.count;
  748. XCTAssertTrue(count <= 1, @"Only one fetcher is supported");
  749. return !!count;
  750. }
  751. // Gets the URL being fetched.
  752. - (NSURL *)fetchedURL {
  753. return [_fetcherService.fetchers[0] requestURL];
  754. }
  755. // Emulates server returning the data as in JSON.
  756. - (void)didFetch:(id)dataObject error:(NSError *)error {
  757. NSData *data = nil;
  758. if (dataObject) {
  759. NSError *jsonError = nil;
  760. data = [NSJSONSerialization dataWithJSONObject:dataObject
  761. options:0
  762. error:&jsonError];
  763. XCTAssertNil(jsonError, @"must provide valid data");
  764. }
  765. [_fetcherService.fetchers[0] didFinishWithData:data error:error];
  766. }
  767. - (NSError *)error {
  768. return [NSError errorWithDomain:kErrorDomain code:kErrorCode userInfo:nil];
  769. }
  770. // Verifies a fetcher has started for revoking token and emulates a server response.
  771. - (void)verifyAndRevokeToken:(NSString *)token delegate:(OCMockObject *)mockDelegate {
  772. XCTAssertTrue([self isFetcherStarted], @"should start fetching");
  773. NSURL *url = [self fetchedURL];
  774. XCTAssertEqualObjects([url scheme], @"https", @"scheme must match");
  775. XCTAssertEqualObjects([url host], @"accounts.google.com", @"host must match");
  776. XCTAssertEqualObjects([url path], @"/o/oauth2/revoke", @"path must match");
  777. OIDURLQueryComponent *queryComponent = [[OIDURLQueryComponent alloc] initWithURL:url];
  778. NSDictionary<NSString *, NSObject<NSCopying> *> *params = queryComponent.dictionaryValue;
  779. XCTAssertEqualObjects([params valueForKey:@"token"], token,
  780. @"token parameter should match");
  781. // Emulate result back from server.
  782. [[mockDelegate expect] signIn:_signIn didDisconnectWithUser:nil withError:nil];
  783. [self didFetch:nil error:nil];
  784. [mockDelegate verify];
  785. XCTAssertTrue(_keychainRemoved, @"should clear saved keychain name");
  786. }
  787. // The authorization flow with parameters to control which branches to take.
  788. - (void)OAuthLoginWithOptions:(GIDSignInInternalOptions *)options
  789. authError:(NSString *)authError
  790. tokenError:(NSError *)tokenError
  791. emmPasscodeInfoRequired:(BOOL)emmPasscodeInfoRequired
  792. keychainError:(BOOL)keychainError
  793. restoredSignIn:(BOOL)restoredSignIn
  794. oldAccessToken:(BOOL)oldAccessToken
  795. modalCancel:(BOOL)modalCancel {
  796. if (restoredSignIn) {
  797. // clearAndAuthenticateWithOptions
  798. [[[_authorization expect] andReturn:_authState] authState];
  799. BOOL isAuthorized = restoredSignIn ? YES : NO;
  800. [[[_authState expect] andReturnValue:[NSNumber numberWithBool:isAuthorized]] isAuthorized];
  801. }
  802. NSDictionary<NSString *, NSString *> *additionalParameters = emmPasscodeInfoRequired ?
  803. @{ @"emm_passcode_info_required" : @"1" } : nil;
  804. OIDAuthorizationResponse *authResponse =
  805. [OIDAuthorizationResponse testInstanceWithAdditionalParameters:additionalParameters
  806. errorString:authError];
  807. OIDTokenResponse *tokenResponse =
  808. [OIDTokenResponse testInstanceWithIDToken:[OIDTokenResponse fatIDToken]
  809. accessToken:restoredSignIn ? kAccessToken : nil
  810. expiresIn:oldAccessToken ? @(300) : nil
  811. tokenRequest:nil];
  812. OIDTokenRequest *tokenRequest = [[OIDTokenRequest alloc]
  813. initWithConfiguration:authResponse.request.configuration
  814. grantType:OIDGrantTypeRefreshToken
  815. authorizationCode:nil
  816. redirectURL:nil
  817. clientID:authResponse.request.clientID
  818. clientSecret:authResponse.request.clientSecret
  819. scope:nil
  820. refreshToken:kRefreshToken
  821. codeVerifier:nil
  822. additionalParameters:tokenResponse.request.additionalParameters];
  823. if (restoredSignIn) {
  824. // maybeFetchToken
  825. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  826. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  827. if (oldAccessToken) {
  828. [[[_authState expect] andReturn:authResponse] lastAuthorizationResponse];
  829. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  830. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  831. [[[_authState expect] andReturn:tokenRequest]
  832. tokenRefreshRequestWithAdditionalParameters:[OCMArg any]];
  833. }
  834. } else {
  835. if (options) {
  836. [_signIn signInWithOptions:options];
  837. } else {
  838. [_signIn signIn];
  839. }
  840. [_authorization verify];
  841. [_authState verify];
  842. XCTAssertNotNil(_savedAuthorizationRequest);
  843. XCTAssertNotNil(_savedAuthorizationCallback);
  844. XCTAssertEqual(_savedPresentingViewController, _presentingViewController);
  845. // maybeFetchToken
  846. if (!(authError || modalCancel)) {
  847. [[[_authState expect] andReturn:nil] lastTokenResponse];
  848. [[[_authState expect] andReturn:authResponse] lastAuthorizationResponse];
  849. [[[_authState expect] andReturn:nil] lastTokenResponse];
  850. [[[_authState expect] andReturn:authResponse] lastAuthorizationResponse];
  851. [[[_authState expect] andReturn:authResponse] lastAuthorizationResponse];
  852. }
  853. // Simulate auth endpoint response
  854. if (modalCancel) {
  855. NSError *error = [NSError errorWithDomain:OIDGeneralErrorDomain
  856. code:OIDErrorCodeUserCanceledAuthorizationFlow
  857. userInfo:nil];
  858. _savedAuthorizationCallback(nil, error);
  859. } else {
  860. _savedAuthorizationCallback(authResponse, nil);
  861. }
  862. if (authError || modalCancel) {
  863. return;
  864. }
  865. [_authState verify];
  866. }
  867. if (restoredSignIn && oldAccessToken) {
  868. [_signIn restorePreviousSignIn];
  869. }
  870. if (!restoredSignIn || (restoredSignIn && oldAccessToken)) {
  871. XCTAssertNotNil(_savedTokenRequest);
  872. XCTAssertNotNil(_savedTokenCallback);
  873. // OIDTokenCallback
  874. if (tokenError) {
  875. [[_authState expect] updateWithTokenResponse:nil error:tokenError];
  876. } else {
  877. [[_authState expect] updateWithTokenResponse:[OCMArg any] error:nil];
  878. }
  879. }
  880. if (tokenError) {
  881. _savedTokenCallback(nil, tokenError);
  882. return;
  883. }
  884. // DecodeIdTokenCallback
  885. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  886. // SaveAuthCallback
  887. [[[_user stub] andReturn:_user] alloc];
  888. __block OIDAuthState *authState;
  889. __block GIDProfileData *profileData;
  890. if (keychainError) {
  891. _saveAuthorizationReturnValue = NO;
  892. } else {
  893. (void)[[[_user expect] andReturn:_user] initWithAuthState:SAVE_TO_ARG_BLOCK(authState)
  894. profileData:SAVE_TO_ARG_BLOCK(profileData)];
  895. }
  896. if (restoredSignIn && !oldAccessToken) {
  897. [_signIn restorePreviousSignIn];
  898. } else {
  899. // Simulate token endpoint response.
  900. _savedTokenCallback(tokenResponse, nil);
  901. }
  902. [_authState verify];
  903. if (keychainError) {
  904. return;
  905. }
  906. XCTAssertTrue(_keychainSaved, @"should save to keychain");
  907. XCTAssertTrue(_delegateCalled, @"should call delegate");
  908. XCTAssertNil(_authError, @"should have no error");
  909. XCTAssertNotNil(authState);
  910. // Check fat ID token decoding
  911. XCTAssertEqualObjects(profileData.name, kFatName);
  912. XCTAssertEqualObjects(profileData.givenName, kFatGivenName);
  913. XCTAssertEqualObjects(profileData.familyName, kFatFamilyName);
  914. XCTAssertTrue(profileData.hasImage);
  915. // If attempt to authenticate again, will reuse existing auth object.
  916. _delegateCalled = NO;
  917. _keychainRemoved = NO;
  918. _keychainSaved = NO;
  919. _authError = nil;
  920. [[[_user expect] andReturn:_authentication] authentication];
  921. [[[_user expect] andReturn:_authentication] authentication];
  922. __block GIDAuthenticationAction action;
  923. [[_authentication expect] doWithFreshTokens:SAVE_TO_ARG_BLOCK(action)];
  924. [_signIn restorePreviousSignIn];
  925. action(_authentication, nil);
  926. XCTAssertFalse(_keychainRemoved, @"should not remove keychain");
  927. XCTAssertFalse(_keychainSaved, @"should not save to keychain again");
  928. XCTAssertTrue(_delegateCalled, @"should call delegate");
  929. XCTAssertNil(_authError, @"should have no error");
  930. }
  931. #pragma mark - Key Value Observing
  932. - (void)observeValueForKeyPath:(NSString *)keyPath
  933. ofObject:(id)object
  934. change:(NSDictionary<NSKeyValueChangeKey, id> *)change
  935. context:(void *)context {
  936. if (context == kTestObserverContext && object == _signIn) {
  937. [_changedKeyPaths addObject:keyPath];
  938. }
  939. }
  940. #pragma mark - GIDSignInDelegate
  941. - (void)signIn:(GIDSignIn *)signIn
  942. didSignInForUser:(GIDGoogleUser *)user
  943. withError:(NSError *)error {
  944. if (!user) {
  945. XCTAssertNotNil(error, @"should have an error if user is nil");
  946. }
  947. XCTAssertFalse(_delegateCalled, @"delegate already called");
  948. _delegateCalled = YES;
  949. _authError = error;
  950. }
  951. @end