GIDSignInTest.m 47 KB

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