GIDSignInTest.m 43 KB

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