GIDSignInTest.m 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209
  1. // Copyright 2021 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import <TargetConditionals.h>
  15. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  16. #import <UIKit/UIKit.h>
  17. #elif TARGET_OS_OSX
  18. #import <AppKit/AppKit.h>
  19. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  20. #import <SafariServices/SafariServices.h>
  21. #import <XCTest/XCTest.h>
  22. // Test module imports
  23. @import GoogleSignIn;
  24. #import "GoogleSignIn/Sources/GIDAuthorizationFlowProcessor/API/GIDAuthorizationFlowProcessor.h"
  25. #import "GoogleSignIn/Sources/GIDAuthorizationFlowProcessor/Implementations/Fakes/GIDFakeAuthorizationFlowProcessor.h"
  26. #import "GoogleSignIn/Sources/GIDEMMSupport.h"
  27. #import "GoogleSignIn/Sources/GIDGoogleUser_Private.h"
  28. #import "GoogleSignIn/Sources/GIDSignIn_Private.h"
  29. #import "GoogleSignIn/Sources/GIDSignInPreferences.h"
  30. #import "GoogleSignIn/Sources/GIDKeychainHandler/Implementations/Fakes/GIDFakeKeychainHandler.h"
  31. #import "GoogleSignIn/Sources/GIDHTTPFetcher/Implementations/Fakes/GIDFakeHTTPFetcher.h"
  32. #import "GoogleSignIn/Sources/GIDHTTPFetcher/Implementations/GIDHTTPFetcher.h"
  33. #import "GoogleSignIn/Sources/GIDProfileDataFetcher/API/GIDProfileDataFetcher.h"
  34. #import "GoogleSignIn/Sources/GIDProfileDataFetcher/Implementations/Fakes/GIDFakeProfileDataFetcher.h"
  35. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  36. #import "GoogleSignIn/Sources/GIDEMMErrorHandler.h"
  37. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  38. #import "GoogleSignIn/Tests/Unit/GIDFakeMainBundle.h"
  39. #import "GoogleSignIn/Tests/Unit/GIDProfileData+Testing.h"
  40. #import "GoogleSignIn/Tests/Unit/OIDAuthorizationResponse+Testing.h"
  41. #import "GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h"
  42. #ifdef SWIFT_PACKAGE
  43. @import AppAuth;
  44. @import GTMAppAuth;
  45. @import GTMSessionFetcherCore;
  46. @import OCMock;
  47. #else
  48. #import <AppAuth/OIDAuthState.h>
  49. #import <AppAuth/OIDAuthorizationRequest.h>
  50. #import <AppAuth/OIDAuthorizationResponse.h>
  51. #import <AppAuth/OIDAuthorizationService.h>
  52. #import <AppAuth/OIDError.h>
  53. #import <AppAuth/OIDGrantTypes.h>
  54. #import <AppAuth/OIDTokenRequest.h>
  55. #import <AppAuth/OIDTokenResponse.h>
  56. #import <AppAuth/OIDURLQueryComponent.h>
  57. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  58. #import <AppAuth/OIDAuthorizationService+IOS.h>
  59. #elif TARGET_OS_OSX
  60. #import <AppAuth/OIDAuthorizationService+Mac.h>
  61. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  62. #import <GTMAppAuth/GTMAppAuthFetcherAuthorization+Keychain.h>
  63. #import <GTMAppAuth/GTMAppAuthFetcherAuthorization.h>
  64. #import <GTMSessionFetcher/GTMSessionFetcher.h>
  65. #import <OCMock/OCMock.h>
  66. #endif
  67. // Create a BLOCK to store the actual address for arg in param.
  68. #define SAVE_TO_ARG_BLOCK(param) [OCMArg checkWithBlock:^(id arg) {\
  69. param = arg;\
  70. return YES;\
  71. }]
  72. #define COPY_TO_ARG_BLOCK(param) [OCMArg checkWithBlock:^(id arg) {\
  73. param = [arg copy];\
  74. return YES;\
  75. }]
  76. static NSString * const kFakeGaiaID = @"123456789";
  77. static NSString * const kFakeIDToken = @"FakeIDToken";
  78. static NSString * const kClientId = @"FakeClientID";
  79. static NSString * const kDotReversedClientId = @"FakeClientID";
  80. static NSString * const kClientId2 = @"FakeClientID2";
  81. static NSString * const kServerClientId = @"FakeServerClientID";
  82. static NSString * const kLanguage = @"FakeLanguage";
  83. static NSString * const kAuthCode = @"FakeAuthCode";
  84. static NSString * const kKeychainName = @"auth";
  85. static NSString * const kUserEmail = @"FakeUserEmail";
  86. static NSString * const kVerifier = @"FakeVerifier";
  87. static NSString * const kOpenIDRealm = @"FakeRealm";
  88. static NSString * const kFakeHostedDomain = @"fakehosteddomain.com";
  89. static NSString * const kFakeUserName = @"fake username";
  90. static NSString * const kFakeUserGivenName = @"fake";
  91. static NSString * const kFakeUserFamilyName = @"username";
  92. static NSString * const kFakeUserPictureURL = @"fake_user_picture_url";
  93. static NSString * const kRightPathURL = @"com.google.UnitTests:/oauth2callback";
  94. static NSString * const kWrongPathURL = @"com.google.UnitTests:/wrong_path";
  95. static NSString * const kEMMRestartAuthURL =
  96. @"com.google.UnitTests:///emmcallback?action=restart_auth";
  97. static NSString * const kEMMWrongPathURL =
  98. @"com.google.UnitTests:///unknowcallback?action=restart_auth";
  99. static NSString * const kEMMWrongActionURL =
  100. @"com.google.UnitTests:///emmcallback?action=unrecognized";
  101. static NSString * const kDevicePolicyAppBundleID = @"com.google.DevicePolicy";
  102. static NSString * const kAppHasRunBeforeKey = @"GPP_AppHasRunBefore";
  103. static NSString * const kFingerprintKeychainName = @"fingerprint";
  104. static NSString * const kVerifierKeychainName = @"verifier";
  105. static NSString * const kVerifierKey = @"verifier";
  106. static NSString * const kOpenIDRealmKey = @"openid.realm";
  107. static NSString * const kSavedKeychainServiceName = @"saved-keychain";
  108. static NSString * const kKeychainAccountName = @"GooglePlus";
  109. static NSString * const kUserNameKey = @"name";
  110. static NSString * const kUserGivenNameKey = @"givenName";
  111. static NSString * const kUserFamilyNameKey = @"familyName";
  112. static NSString * const kUserImageKey = @"picture";
  113. static NSString * const kAppName = @"UnitTests";
  114. static NSString * const kUserIDKey = @"userID";
  115. static NSString * const kHostedDomainKey = @"hostedDomain";
  116. static NSString * const kIDTokenExpirationKey = @"idTokenExp";
  117. static NSString * const kCustomKeychainName = @"CUSTOM_KEYCHAIN_NAME";
  118. static NSString * const kAddActivity = @"http://schemas.google.com/AddActivity";
  119. static NSString * const kErrorDomain = @"ERROR_DOMAIN";
  120. static NSInteger const kErrorCode = 212;
  121. static NSString *const kDriveScope = @"https://www.googleapis.com/auth/drive";
  122. static NSString *const kTokenURL = @"https://oauth2.googleapis.com/token";
  123. static NSString *const kFakeURL = @"http://foo.com";
  124. static NSString *const kNewScope = @"newScope";
  125. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  126. // This category is used to allow the test to swizzle a private method.
  127. @interface UIViewController (Testing)
  128. // This private method provides access to the window. It's declared here to avoid a warning about
  129. // an unrecognized selector in the test.
  130. - (UIWindow *)_window;
  131. @end
  132. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  133. // This class extension exposes GIDSignIn methods to our tests.
  134. @interface GIDSignIn ()
  135. // Exposing private method so we can call it to disambiguate between interactive and non-interactive
  136. // sign-in attempts for the purposes of testing the GIDSignInUIDelegate (which should not be
  137. // called in the case of a non-interactive sign in).
  138. - (void)authenticateMaybeInteractively:(BOOL)interactive withParams:(NSDictionary *)params;
  139. - (BOOL)assertValidPresentingViewContoller;
  140. @end
  141. @interface GIDSignInTest : XCTestCase {
  142. @private
  143. // Whether or not the OS version is eligible for EMM.
  144. BOOL _isEligibleForEMM;
  145. // Mock `OIDAuthState`.
  146. id _authState;
  147. // Mock `OIDTokenResponse`.
  148. id _tokenResponse;
  149. // Mock `OIDTokenRequest`.
  150. id _tokenRequest;
  151. // Mock `GTMAppAuthFetcherAuthorization`.
  152. id _authorization;
  153. // Fake for `GIDKeychainHandler`.
  154. GIDFakeKeychainHandler *_keychainHandler;
  155. // Fake for `GIDHTTPFetcher`.
  156. GIDFakeHTTPFetcher *_httpFetcher;
  157. // Fake for `GIDAuthorizationFlowProcessor`.
  158. GIDFakeAuthorizationFlowProcessor *_authorizationFlowProcessor;
  159. // Fake for `GIDProfileDataFetcher`.
  160. GIDFakeProfileDataFetcher *_profileDataFetcher;
  161. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  162. // Mock `UIViewController`.
  163. id _presentingViewController;
  164. #elif TARGET_OS_OSX
  165. // Mock `NSWindow`.
  166. id _presentingWindow;
  167. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  168. // Mock for `GIDGoogleUser`.
  169. id _user;
  170. // Mock for `OIDAuthorizationService`.
  171. id _oidAuthorizationService;
  172. // Parameter saved from delegate call.
  173. NSError *_authError;
  174. // Whether callback block has been called.
  175. BOOL _completionCalled;
  176. // Fake [NSBundle mainBundle];
  177. GIDFakeMainBundle *_fakeMainBundle;
  178. // The `GIDSignIn` object being tested.
  179. GIDSignIn *_signIn;
  180. // The configuration to be used when testing `GIDSignIn`.
  181. GIDConfiguration *_configuration;
  182. // The completion to be used when testing `GIDSignIn`.
  183. GIDSignInCompletion _completion;
  184. // The saved token request.
  185. OIDTokenRequest *_savedTokenRequest;
  186. // The saved token request callback.
  187. OIDTokenCallback _savedTokenCallback;
  188. }
  189. @end
  190. @implementation GIDSignInTest
  191. #pragma mark - Lifecycle
  192. - (void)setUp {
  193. [super setUp];
  194. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  195. _isEligibleForEMM = [UIDevice currentDevice].systemVersion.integerValue >= 9;
  196. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  197. // States
  198. _completionCalled = NO;
  199. // Mocks
  200. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  201. _presentingViewController = OCMStrictClassMock([UIViewController class]);
  202. #elif TARGET_OS_OSX
  203. _presentingWindow = OCMStrictClassMock([NSWindow class]);
  204. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  205. _authState = OCMStrictClassMock([OIDAuthState class]);
  206. OCMStub([_authState alloc]).andReturn(_authState);
  207. OCMStub([_authState initWithAuthorizationResponse:OCMOCK_ANY]).andReturn(_authState);
  208. _tokenResponse = OCMStrictClassMock([OIDTokenResponse class]);
  209. _tokenRequest = OCMStrictClassMock([OIDTokenRequest class]);
  210. _authorization = OCMStrictClassMock([GTMAppAuthFetcherAuthorization class]);
  211. OCMStub([_authorization alloc]).andReturn(_authorization);
  212. OCMStub([_authorization initWithAuthState:OCMOCK_ANY]).andReturn(_authorization);
  213. _user = OCMStrictClassMock([GIDGoogleUser class]);
  214. _oidAuthorizationService = OCMStrictClassMock([OIDAuthorizationService class]);
  215. OCMStub([self->_oidAuthorizationService
  216. performTokenRequest:SAVE_TO_ARG_BLOCK(self->_savedTokenRequest)
  217. callback:COPY_TO_ARG_BLOCK(self->_savedTokenCallback)]);
  218. // Fakes
  219. _fakeMainBundle = [[GIDFakeMainBundle alloc] init];
  220. [_fakeMainBundle startFakingWithClientID:kClientId];
  221. [_fakeMainBundle fakeAllSchemesSupported];
  222. // Object under test
  223. [[NSUserDefaults standardUserDefaults] setBool:YES
  224. forKey:kAppHasRunBeforeKey];
  225. _keychainHandler = [[GIDFakeKeychainHandler alloc] init];
  226. _httpFetcher = [[GIDFakeHTTPFetcher alloc] init];
  227. _profileDataFetcher = [[GIDFakeProfileDataFetcher alloc] init];
  228. _authorizationFlowProcessor = [[GIDFakeAuthorizationFlowProcessor alloc] init];
  229. _signIn = [[GIDSignIn alloc] initWithKeychainHandler:_keychainHandler
  230. httpFetcher:_httpFetcher
  231. profileDataFetcher:_profileDataFetcher
  232. authorizationFlowProcessor:_authorizationFlowProcessor];
  233. __weak GIDSignInTest *weakSelf = self;
  234. _completion = ^(GIDSignInResult *_Nullable signInResult, NSError * _Nullable error) {
  235. GIDSignInTest *strongSelf = weakSelf;
  236. if (!signInResult) {
  237. XCTAssertNotNil(error, @"should have an error if the signInResult is nil");
  238. }
  239. XCTAssertFalse(strongSelf->_completionCalled, @"callback already called");
  240. strongSelf->_completionCalled = YES;
  241. strongSelf->_authError = error;
  242. };
  243. }
  244. - (void)tearDown {
  245. OCMVerifyAll(_authState);
  246. OCMVerifyAll(_tokenResponse);
  247. OCMVerifyAll(_tokenRequest);
  248. OCMVerifyAll(_user);
  249. OCMVerifyAll(_oidAuthorizationService);
  250. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  251. OCMVerifyAll(_presentingViewController);
  252. #elif TARGET_OS_OSX
  253. OCMVerifyAll(_presentingWindow);
  254. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  255. [_fakeMainBundle stopFaking];
  256. [super tearDown];
  257. }
  258. #pragma mark - Tests
  259. - (void)testShareInstance {
  260. GIDSignIn *signIn1 = GIDSignIn.sharedInstance;
  261. GIDSignIn *signIn2 = GIDSignIn.sharedInstance;
  262. XCTAssertTrue(signIn1 == signIn2, @"shared instance must be singleton");
  263. }
  264. - (void)testInitPrivate {
  265. GIDSignIn *signIn = [[GIDSignIn alloc] initPrivate];
  266. XCTAssertNotNil(signIn.configuration);
  267. XCTAssertEqual(signIn.configuration.clientID, kClientId);
  268. XCTAssertNil(signIn.configuration.serverClientID);
  269. XCTAssertNil(signIn.configuration.hostedDomain);
  270. XCTAssertNil(signIn.configuration.openIDRealm);
  271. }
  272. - (void)testInitPrivate_noConfig {
  273. [_fakeMainBundle fakeWithClientID:nil
  274. serverClientID:nil
  275. hostedDomain:nil
  276. openIDRealm:nil];
  277. GIDSignIn *signIn = [[GIDSignIn alloc] initPrivate];
  278. XCTAssertNil(signIn.configuration);
  279. }
  280. - (void)testInitPrivate_fullConfig {
  281. [_fakeMainBundle fakeWithClientID:kClientId
  282. serverClientID:kServerClientId
  283. hostedDomain:kFakeHostedDomain
  284. openIDRealm:kOpenIDRealm];
  285. GIDSignIn *signIn = [[GIDSignIn alloc] initPrivate];
  286. XCTAssertNotNil(signIn.configuration);
  287. XCTAssertEqual(signIn.configuration.clientID, kClientId);
  288. XCTAssertEqual(signIn.configuration.serverClientID, kServerClientId);
  289. XCTAssertEqual(signIn.configuration.hostedDomain, kFakeHostedDomain);
  290. XCTAssertEqual(signIn.configuration.openIDRealm, kOpenIDRealm);
  291. }
  292. - (void)testInitPrivate_invalidConfig {
  293. [_fakeMainBundle fakeWithClientID:@[ @"bad", @"config", @"values" ]
  294. serverClientID:nil
  295. hostedDomain:nil
  296. openIDRealm:nil];
  297. GIDSignIn *signIn = [[GIDSignIn alloc] initPrivate];
  298. XCTAssertNil(signIn.configuration);
  299. }
  300. - (void)testRestorePreviousSignInNoRefresh_hasPreviousUser {
  301. [[[_authorization stub] andReturn:_authState] authState];
  302. [[_authorization expect] setTokenRefreshDelegate:OCMOCK_ANY];
  303. OCMStub([_authState lastTokenResponse]).andReturn(_tokenResponse);
  304. OCMStub([_authState refreshToken]).andReturn(kRefreshToken);
  305. [[_authState expect] setStateChangeDelegate:OCMOCK_ANY];
  306. [_keychainHandler saveAuthState:_authState];
  307. id idTokenDecoded = OCMClassMock([OIDIDToken class]);
  308. OCMStub([idTokenDecoded alloc]).andReturn(idTokenDecoded);
  309. OCMStub([idTokenDecoded initWithIDTokenString:OCMOCK_ANY]).andReturn(idTokenDecoded);
  310. OCMStub([idTokenDecoded subject]).andReturn(kFakeGaiaID);
  311. // Mock generating a GIDConfiguration when initializing GIDGoogleUser.
  312. OIDAuthorizationResponse *authResponse =
  313. [OIDAuthorizationResponse testInstanceWithAdditionalParameters:nil
  314. errorString:nil];
  315. OCMStub([_authState lastAuthorizationResponse]).andReturn(authResponse);
  316. OCMStub([_tokenResponse idToken]).andReturn(kFakeIDToken);
  317. OCMStub([_tokenResponse accessToken]).andReturn(kAccessToken);
  318. OCMStub([_tokenResponse accessTokenExpirationDate]).andReturn(nil);
  319. GIDProfileData *fakeProfileData = [GIDProfileData testInstance];
  320. GIDProfileDataFetcherTestBlock testBlock = ^(GIDProfileDataFetcherFakeResponseProvider
  321. responseProvider) {
  322. responseProvider(fakeProfileData, nil);
  323. };
  324. _profileDataFetcher.testBlock = testBlock;
  325. [_signIn restorePreviousSignInNoRefresh];
  326. XCTAssertEqual(_signIn.currentUser.userID, kFakeGaiaID);
  327. XCTAssertEqualObjects(_signIn.currentUser.profile, fakeProfileData);
  328. [idTokenDecoded stopMocking];
  329. }
  330. - (void)testRestoredPreviousSignInNoRefresh_hasNoPreviousUser {
  331. XCTAssertNil([_keychainHandler loadAuthState]);
  332. [_signIn restorePreviousSignInNoRefresh];
  333. XCTAssertNil(_signIn.currentUser);
  334. }
  335. - (void)testHasPreviousSignIn_HasBeenAuthenticated {
  336. [_keychainHandler saveAuthState:_authState];
  337. [[[_authState expect] andReturnValue:[NSNumber numberWithBool:YES]] isAuthorized];
  338. XCTAssertTrue([_signIn hasPreviousSignIn], @"should return |YES|");
  339. [_authState verify];
  340. XCTAssertFalse(_completionCalled, @"should not call delegate");
  341. XCTAssertNil(_authError, @"should have no error");
  342. }
  343. - (void)testHasPreviousSignIn_HasNotBeenAuthenticated {
  344. [_keychainHandler saveAuthState:_authState];
  345. [[[_authState expect] andReturnValue:[NSNumber numberWithBool:NO]] isAuthorized];
  346. XCTAssertFalse([_signIn hasPreviousSignIn], @"should return |NO|");
  347. [_authState verify];
  348. XCTAssertFalse(_completionCalled, @"should not call delegate");
  349. }
  350. - (void)testRestorePreviousSignInWhenSignedOut {
  351. [_keychainHandler saveAuthState:_authState];
  352. [[[_authState expect] andReturnValue:[NSNumber numberWithBool:NO]] isAuthorized];
  353. _completionCalled = NO;
  354. _authError = nil;
  355. XCTestExpectation *expectation = [self expectationWithDescription:@"Callback should be called."];
  356. [_signIn restorePreviousSignInWithCompletion:^(GIDGoogleUser *_Nullable user,
  357. NSError * _Nullable error) {
  358. [expectation fulfill];
  359. XCTAssertNotNil(error, @"error should not have been nil");
  360. XCTAssertEqual(error.domain,
  361. kGIDSignInErrorDomain,
  362. @"error domain should have been the sign-in error domain.");
  363. XCTAssertEqual(error.code,
  364. kGIDSignInErrorCodeHasNoAuthInKeychain,
  365. @"error code should have been the 'NoAuthInKeychain' error code.");
  366. }];
  367. [self waitForExpectationsWithTimeout:1 handler:nil];
  368. [_authState verify];
  369. }
  370. - (void)testOAuthLogin {
  371. [self OAuthLoginWithAddScopesFlow:NO
  372. authError:nil
  373. tokenError:nil
  374. emmPasscodeInfoRequired:NO
  375. keychainError:NO
  376. restoredSignIn:NO
  377. oldAccessToken:NO
  378. modalCancel:NO];
  379. }
  380. - (void)testOAuthLogin_RestoredSignIn {
  381. [self OAuthLoginWithAddScopesFlow:NO
  382. authError:nil
  383. tokenError:nil
  384. emmPasscodeInfoRequired:NO
  385. keychainError:NO
  386. restoredSignIn:YES
  387. oldAccessToken:NO
  388. modalCancel:NO];
  389. }
  390. - (void)testOAuthLogin_RestoredSignInOldAccessToken {
  391. [self OAuthLoginWithAddScopesFlow:NO
  392. authError:nil
  393. tokenError:nil
  394. emmPasscodeInfoRequired:NO
  395. keychainError:NO
  396. restoredSignIn:YES
  397. oldAccessToken:YES
  398. modalCancel:NO];
  399. }
  400. - (void)testOpenIDRealm {
  401. _signIn.configuration = [[GIDConfiguration alloc] initWithClientID:kClientId
  402. serverClientID:nil
  403. hostedDomain:nil
  404. openIDRealm:kOpenIDRealm];
  405. [self OAuthLoginWithAddScopesFlow:NO
  406. authError:nil
  407. tokenError:nil
  408. emmPasscodeInfoRequired:NO
  409. keychainError:NO
  410. restoredSignIn:NO
  411. oldAccessToken:NO
  412. modalCancel:NO];
  413. NSDictionary<NSString *, NSString *> *params = _savedTokenRequest.additionalParameters;
  414. XCTAssertEqual(params[kOpenIDRealmKey], kOpenIDRealm, @"OpenID Realm should match.");
  415. }
  416. - (void)testOAuthLogin_ConsentCanceled {
  417. [self OAuthLoginWithAddScopesFlow:NO
  418. authError:@"access_denied"
  419. tokenError:nil
  420. emmPasscodeInfoRequired:NO
  421. keychainError:NO
  422. restoredSignIn:NO
  423. oldAccessToken:NO
  424. modalCancel:NO];
  425. [self waitForExpectationsWithTimeout:1 handler:nil];
  426. XCTAssertTrue(_completionCalled, @"should call delegate");
  427. XCTAssertEqual(_authError.code, kGIDSignInErrorCodeCanceled);
  428. }
  429. - (void)testOAuthLogin_ModalCanceled {
  430. [self OAuthLoginWithAddScopesFlow:NO
  431. authError:nil
  432. tokenError:nil
  433. emmPasscodeInfoRequired:NO
  434. keychainError:NO
  435. restoredSignIn:NO
  436. oldAccessToken:NO
  437. modalCancel:YES];
  438. [self waitForExpectationsWithTimeout:1 handler:nil];
  439. XCTAssertTrue(_completionCalled, @"should call delegate");
  440. XCTAssertEqual(_authError.code, kGIDSignInErrorCodeCanceled);
  441. }
  442. - (void)testOAuthLogin_KeychainError {
  443. [self OAuthLoginWithAddScopesFlow:NO
  444. authError:nil
  445. tokenError:nil
  446. emmPasscodeInfoRequired:NO
  447. keychainError:YES
  448. restoredSignIn:NO
  449. oldAccessToken:NO
  450. modalCancel:NO];
  451. [self waitForExpectationsWithTimeout:1 handler:nil];
  452. XCTAssertTrue(_completionCalled, @"should call delegate");
  453. XCTAssertEqualObjects(_authError.domain, kGIDSignInErrorDomain);
  454. XCTAssertEqual(_authError.code, kGIDSignInErrorCodeKeychain);
  455. }
  456. - (void)testSignOut {
  457. XCTAssert([_keychainHandler saveAuthState:_authState]);
  458. // Sign in a user so that we can then sign them out.
  459. [self OAuthLoginWithAddScopesFlow:NO
  460. authError:nil
  461. tokenError:nil
  462. emmPasscodeInfoRequired:NO
  463. keychainError:NO
  464. restoredSignIn:YES
  465. oldAccessToken:NO
  466. modalCancel:NO];
  467. XCTAssertNotNil(_signIn.currentUser);
  468. XCTAssertNotNil([_keychainHandler loadAuthState]);
  469. [_signIn signOut];
  470. XCTAssertNil(_signIn.currentUser, @"should not have a current user");
  471. XCTAssertNil([_keychainHandler loadAuthState]);
  472. }
  473. - (void)testHandleRightPath {
  474. XCTAssertTrue([_signIn handleURL:[NSURL URLWithString:kRightPathURL]]);
  475. XCTAssertFalse(_completionCalled, @"should not call delegate");
  476. }
  477. - (void)testNotHandleWrongPath {
  478. XCTAssertFalse([_signIn handleURL:[NSURL URLWithString:kWrongPathURL]], @"should not handle URL");
  479. XCTAssertFalse(_completionCalled, @"should not call delegate");
  480. }
  481. #pragma mark - Tests - disconnectWithCallback:
  482. // Verifies disconnect calls callback with no errors if access token is present.
  483. - (void)testDisconnect_accessTokenIsPresent {
  484. [_keychainHandler saveAuthState:_authState];
  485. OCMStub([_authState lastTokenResponse]).andReturn(_tokenResponse);
  486. OCMStub([_tokenResponse accessToken]).andReturn(kAccessToken);
  487. XCTestExpectation *fetcherExpectation =
  488. [self expectationWithDescription:@"testBlock is invoked."];
  489. GIDHTTPFetcherTestBlock testBlock =
  490. ^(NSURLRequest *request, GIDHTTPFetcherFakeResponseProviderBlock responseProvider) {
  491. [self verifyRevokeRequest:request withToken:kAccessToken];
  492. NSData *data = [[NSData alloc] init];
  493. responseProvider(data, nil);
  494. [fetcherExpectation fulfill];
  495. };
  496. [_httpFetcher setTestBlock:testBlock];
  497. XCTestExpectation *completionExpectation =
  498. [self expectationWithDescription:@"Callback called with nil error"];
  499. [_signIn disconnectWithCompletion:^(NSError * _Nullable error) {
  500. XCTAssertNil(error);
  501. [completionExpectation fulfill];
  502. }];
  503. [self waitForExpectationsWithTimeout:1 handler:nil];
  504. XCTAssertNil([_keychainHandler loadAuthState]);
  505. }
  506. // Verifies disconnect if access token is present.
  507. - (void)testDisconnectNoCallback_accessTokenIsPresent {
  508. [_keychainHandler saveAuthState:_authState];
  509. OCMStub([_authState lastTokenResponse]).andReturn(_tokenResponse);
  510. OCMStub([_tokenResponse accessToken]).andReturn(kAccessToken);
  511. XCTestExpectation *fetcherExpectation =
  512. [self expectationWithDescription:@"testBlock is invoked."];
  513. GIDHTTPFetcherTestBlock testBlock =
  514. ^(NSURLRequest *request, GIDHTTPFetcherFakeResponseProviderBlock responseProvider) {
  515. [self verifyRevokeRequest:request withToken:kAccessToken];
  516. NSData *data = [[NSData alloc] init];
  517. responseProvider(data, nil);
  518. [fetcherExpectation fulfill];
  519. };
  520. [_httpFetcher setTestBlock:testBlock];
  521. [_signIn disconnectWithCompletion:nil];
  522. [self waitForExpectationsWithTimeout:1 handler:nil];
  523. XCTAssertNil([_keychainHandler loadAuthState]);
  524. }
  525. // Verifies disconnect calls callback with no errors if refresh token is present.
  526. - (void)testDisconnect_refreshTokenIsPresent {
  527. [_keychainHandler saveAuthState:_authState];
  528. OCMStub([_authState lastTokenResponse]).andReturn(_tokenResponse);
  529. OCMStub([_tokenResponse accessToken]).andReturn(nil);
  530. OCMStub([_tokenResponse refreshToken]).andReturn(kRefreshToken);
  531. XCTestExpectation *fetcherExpectation =
  532. [self expectationWithDescription:@"testBlock is invoked."];
  533. GIDHTTPFetcherTestBlock testBlock =
  534. ^(NSURLRequest *request, GIDHTTPFetcherFakeResponseProviderBlock responseProvider) {
  535. [self verifyRevokeRequest:request withToken:kRefreshToken];
  536. NSData *data = [[NSData alloc] init];
  537. responseProvider(data, nil);
  538. [fetcherExpectation fulfill];
  539. };
  540. [_httpFetcher setTestBlock:testBlock];
  541. XCTestExpectation *completionExpectation =
  542. [self expectationWithDescription:@"Callback called with nil error"];
  543. [_signIn disconnectWithCompletion:^(NSError * _Nullable error) {
  544. XCTAssertNil(error);
  545. [completionExpectation fulfill];
  546. }];
  547. [self waitForExpectationsWithTimeout:1 handler:nil];
  548. XCTAssertNil([_keychainHandler loadAuthState]);
  549. }
  550. // Verifies disconnect errors are passed along to the callback.
  551. - (void)testDisconnect_errors {
  552. [_keychainHandler saveAuthState:_authState];
  553. OCMStub([_authState lastTokenResponse]).andReturn(_tokenResponse);
  554. OCMStub([_tokenResponse accessToken]).andReturn(kAccessToken);
  555. XCTestExpectation *fetcherExpectation =
  556. [self expectationWithDescription:@"testBlock is invoked."];
  557. GIDHTTPFetcherTestBlock testBlock =
  558. ^(NSURLRequest *request, GIDHTTPFetcherFakeResponseProviderBlock responseProvider) {
  559. [self verifyRevokeRequest:request withToken:kAccessToken];
  560. NSError *error = [self error];
  561. responseProvider(nil, error);
  562. [fetcherExpectation fulfill];
  563. };
  564. [_httpFetcher setTestBlock:testBlock];
  565. XCTestExpectation *completionExpectation =
  566. [self expectationWithDescription:@"Callback called with an error"];
  567. [_signIn disconnectWithCompletion:^(NSError * _Nullable error) {
  568. XCTAssertNotNil(error);
  569. [completionExpectation fulfill];
  570. }];
  571. [self waitForExpectationsWithTimeout:1 handler:nil];
  572. XCTAssertNotNil([_keychainHandler loadAuthState]);
  573. }
  574. // Verifies disconnect with errors
  575. - (void)testDisconnectNoCallback_errors {
  576. [_keychainHandler saveAuthState:_authState];
  577. OCMStub([_authState lastTokenResponse]).andReturn(_tokenResponse);
  578. OCMStub([_tokenResponse accessToken]).andReturn(kAccessToken);
  579. XCTestExpectation *fetcherExpectation =
  580. [self expectationWithDescription:@"testBlock is invoked."];
  581. GIDHTTPFetcherTestBlock testBlock =
  582. ^(NSURLRequest *request, GIDHTTPFetcherFakeResponseProviderBlock responseProvider) {
  583. [self verifyRevokeRequest:request withToken:kAccessToken];
  584. NSError *error = [self error];
  585. responseProvider(nil, error);
  586. [fetcherExpectation fulfill];
  587. };
  588. [_httpFetcher setTestBlock:testBlock];
  589. [_signIn disconnectWithCompletion:nil];
  590. [self waitForExpectationsWithTimeout:1 handler:nil];
  591. XCTAssertNotNil([_keychainHandler loadAuthState]);
  592. }
  593. // Verifies disconnect calls callback with no errors and clears keychain if no tokens are present.
  594. - (void)testDisconnect_noTokens {
  595. [_keychainHandler saveAuthState:_authState];
  596. OCMStub([_authState lastTokenResponse]).andReturn(_tokenResponse);
  597. OCMStub([_tokenResponse accessToken]).andReturn(nil);
  598. OCMStub([_tokenResponse refreshToken]).andReturn(nil);
  599. GIDHTTPFetcherTestBlock testBlock =
  600. ^(NSURLRequest *request, GIDHTTPFetcherFakeResponseProviderBlock responseProvider) {
  601. XCTFail(@"_httpFetcher should not be invoked.");
  602. };
  603. [_httpFetcher setTestBlock:testBlock];
  604. XCTestExpectation *expectation =
  605. [self expectationWithDescription:@"Callback called with nil error"];
  606. [_signIn disconnectWithCompletion:^(NSError * _Nullable error) {
  607. XCTAssertNil(error);
  608. [expectation fulfill];
  609. }];
  610. [self waitForExpectationsWithTimeout:1 handler:nil];
  611. XCTAssertNil([_keychainHandler loadAuthState]);
  612. }
  613. // Verifies disconnect clears keychain if no tokens are present.
  614. - (void)testDisconnectNoCallback_noTokens {
  615. [_keychainHandler saveAuthState:_authState];
  616. OCMStub([_authState lastTokenResponse]).andReturn(_tokenResponse);
  617. OCMStub([_tokenResponse accessToken]).andReturn(nil);
  618. OCMStub([_tokenResponse refreshToken]).andReturn(nil);
  619. GIDHTTPFetcherTestBlock testBlock =
  620. ^(NSURLRequest *request, GIDHTTPFetcherFakeResponseProviderBlock responseProvider) {
  621. XCTFail(@"_httpFetcher should not be invoked.");
  622. };
  623. [_httpFetcher setTestBlock:testBlock];
  624. [_signIn disconnectWithCompletion:nil];
  625. XCTAssertNil([_keychainHandler loadAuthState]);
  626. }
  627. - (void)testPresentingViewControllerException {
  628. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  629. _presentingViewController = nil;
  630. #elif TARGET_OS_OSX
  631. _presentingWindow = nil;
  632. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  633. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  634. XCTAssertThrows([_signIn signInWithPresentingViewController:_presentingViewController
  635. #elif TARGET_OS_OSX
  636. XCTAssertThrows([_signIn signInWithPresentingWindow:_presentingWindow
  637. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  638. hint:nil
  639. completion:_completion]);
  640. }
  641. - (void)testClientIDMissingException {
  642. #pragma GCC diagnostic push
  643. #pragma GCC diagnostic ignored "-Wnonnull"
  644. _signIn.configuration = [[GIDConfiguration alloc] initWithClientID:nil];
  645. #pragma GCC diagnostic pop
  646. BOOL threw = NO;
  647. @try {
  648. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  649. [_signIn signInWithPresentingViewController:_presentingViewController
  650. #elif TARGET_OS_OSX
  651. [_signIn signInWithPresentingWindow:_presentingWindow
  652. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  653. completion:nil];
  654. } @catch (NSException *exception) {
  655. threw = YES;
  656. XCTAssertEqualObjects(exception.description,
  657. @"You must specify |clientID| in |GIDConfiguration|");
  658. } @finally {
  659. }
  660. XCTAssert(threw);
  661. }
  662. - (void)testSchemesNotSupportedException {
  663. [_fakeMainBundle fakeMissingAllSchemes];
  664. BOOL threw = NO;
  665. @try {
  666. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  667. [_signIn signInWithPresentingViewController:_presentingViewController
  668. #elif TARGET_OS_OSX
  669. [_signIn signInWithPresentingWindow:_presentingWindow
  670. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  671. hint:nil
  672. completion:_completion];
  673. } @catch (NSException *exception) {
  674. threw = YES;
  675. XCTAssertEqualObjects(exception.description,
  676. @"Your app is missing support for the following URL schemes: "
  677. "fakeclientid");
  678. } @finally {
  679. }
  680. XCTAssert(threw);
  681. }
  682. #pragma mark - Restarting Authentication Tests
  683. // Verifies that URL is not handled if there is no pending sign-in
  684. - (void)testRequiringPendingSignIn {
  685. BOOL result = [_signIn handleURL:[NSURL URLWithString:kEMMRestartAuthURL]];
  686. XCTAssertFalse(result);
  687. }
  688. #pragma mark - EMM tests
  689. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  690. - (void)testEmmSupportRequestParameters {
  691. [self OAuthLoginWithAddScopesFlow:NO
  692. authError:nil
  693. tokenError:nil
  694. emmPasscodeInfoRequired:NO
  695. keychainError:NO
  696. restoredSignIn:NO
  697. oldAccessToken:NO
  698. modalCancel:NO];
  699. NSString *systemName = [UIDevice currentDevice].systemName;
  700. if ([systemName isEqualToString:@"iPhone OS"]) {
  701. systemName = @"iOS";
  702. }
  703. NSString *expectedOSVersion = [NSString stringWithFormat:@"%@ %@",
  704. systemName, [UIDevice currentDevice].systemVersion];
  705. NSDictionary<NSString *, NSString *> *tokenParams = _savedTokenRequest.additionalParameters;
  706. if (_isEligibleForEMM) {
  707. XCTAssertEqualObjects(tokenParams[@"emm_support"], kEMMVersion,
  708. @"EMM support should match in token request");
  709. XCTAssertEqualObjects(tokenParams[@"device_os"],
  710. expectedOSVersion,
  711. @"OS version should match in token request");
  712. XCTAssertNil(tokenParams[@"emm_passcode_info"],
  713. @"no passcode info should be in token request");
  714. } else {
  715. XCTAssertNil(tokenParams[@"emm_support"],
  716. @"EMM support should not be in token request for unsupported OS");
  717. XCTAssertNil(tokenParams[@"device_os"],
  718. @"OS version should not be in token request for unsupported OS");
  719. XCTAssertNil(tokenParams[@"emm_passcode_info"],
  720. @"passcode info should not be in token request for unsupported OS");
  721. }
  722. }
  723. - (void)testEmmPasscodeInfo {
  724. [self OAuthLoginWithAddScopesFlow:NO
  725. authError:nil
  726. tokenError:nil
  727. emmPasscodeInfoRequired:YES
  728. keychainError:NO
  729. restoredSignIn:NO
  730. oldAccessToken:NO
  731. modalCancel:NO];
  732. NSDictionary<NSString *, NSString *> *tokenParams = _savedTokenRequest.additionalParameters;
  733. if (_isEligibleForEMM) {
  734. XCTAssertNotNil(tokenParams[@"emm_passcode_info"],
  735. @"passcode info should be in token request");
  736. } else {
  737. XCTAssertNil(tokenParams[@"emm_passcode_info"],
  738. @"passcode info should not be in token request for unsupported OS");
  739. }
  740. }
  741. - (void)testAuthEndpointEMMError {
  742. if (!_isEligibleForEMM) {
  743. return;
  744. }
  745. id mockEMMErrorHandler = OCMStrictClassMock([GIDEMMErrorHandler class]);
  746. [[[mockEMMErrorHandler stub] andReturn:mockEMMErrorHandler] sharedInstance];
  747. __block void (^completion)(void);
  748. NSDictionary<NSString *, NSString *> *callbackParams = @{ @"error" : @"EMM Specific Error" };
  749. [[[mockEMMErrorHandler expect] andReturnValue:@YES]
  750. handleErrorFromResponse:callbackParams completion:SAVE_TO_ARG_BLOCK(completion)];
  751. [self OAuthLoginWithAddScopesFlow:NO
  752. authError:callbackParams[@"error"]
  753. tokenError:nil
  754. emmPasscodeInfoRequired:NO
  755. keychainError:NO
  756. restoredSignIn:NO
  757. oldAccessToken:NO
  758. modalCancel:NO];
  759. [mockEMMErrorHandler verify];
  760. [mockEMMErrorHandler stopMocking];
  761. completion();
  762. [self waitForExpectationsWithTimeout:1 handler:nil];
  763. XCTAssertTrue(_completionCalled, @"should call delegate");
  764. XCTAssertNotNil(_authError, @"should have error");
  765. XCTAssertEqualObjects(_authError.domain, kGIDSignInErrorDomain);
  766. XCTAssertEqual(_authError.code, kGIDSignInErrorCodeEMM);
  767. XCTAssertNil(_signIn.currentUser, @"should not have current user");
  768. }
  769. - (void)testTokenEndpointEMMError {
  770. if (!_isEligibleForEMM) {
  771. return;
  772. }
  773. __block void (^completion)(NSError *);
  774. NSDictionary *errorJSON = @{ @"error" : @"EMM Specific Error" };
  775. NSError *emmError = [NSError errorWithDomain:@"anydomain"
  776. code:12345
  777. userInfo:@{ OIDOAuthErrorFieldError : errorJSON }];
  778. id emmSupport = OCMStrictClassMock([GIDEMMSupport class]);
  779. [[emmSupport expect] handleTokenFetchEMMError:emmError
  780. completion:SAVE_TO_ARG_BLOCK(completion)];
  781. [self OAuthLoginWithAddScopesFlow:NO
  782. authError:nil
  783. tokenError:emmError
  784. emmPasscodeInfoRequired:NO
  785. keychainError:NO
  786. restoredSignIn:NO
  787. oldAccessToken:NO
  788. modalCancel:NO];
  789. NSError *handledError = [NSError errorWithDomain:kGIDSignInErrorDomain
  790. code:kGIDSignInErrorCodeEMM
  791. userInfo:emmError.userInfo];
  792. completion(handledError);
  793. [self waitForExpectationsWithTimeout:1 handler:nil];
  794. [emmSupport verify];
  795. XCTAssertTrue(_completionCalled, @"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. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  802. #pragma mark - Helpers
  803. - (NSError *)error {
  804. return [NSError errorWithDomain:kErrorDomain code:kErrorCode userInfo:nil];
  805. }
  806. - (void)verifyRevokeRequest:(NSURLRequest *)request withToken:(NSString *)token {
  807. NSURL *url = request.URL;
  808. XCTAssertEqualObjects([url scheme], @"https", @"scheme must match");
  809. XCTAssertEqualObjects([url host], @"accounts.google.com", @"host must match");
  810. XCTAssertEqualObjects([url path], @"/o/oauth2/revoke", @"path must match");
  811. OIDURLQueryComponent *queryComponent = [[OIDURLQueryComponent alloc] initWithURL:url];
  812. NSDictionary<NSString *, NSObject<NSCopying> *> *params = queryComponent.dictionaryValue;
  813. XCTAssertEqualObjects([params valueForKey:kSDKVersionLoggingParameter], GIDVersion(),
  814. @"SDK version logging parameter should match");
  815. XCTAssertEqualObjects([params valueForKey:kEnvironmentLoggingParameter], GIDEnvironment(),
  816. @"Environment logging parameter should match");
  817. NSData *body = request.HTTPBody;
  818. NSString* bodyString = [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding];
  819. NSArray<NSString *> *strings = [bodyString componentsSeparatedByString:@"="];
  820. XCTAssertEqualObjects(strings[1], token);
  821. }
  822. // The authorization flow with parameters to control which branches to take.
  823. - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow
  824. authError:(NSString *)authError
  825. tokenError:(NSError *)tokenError
  826. emmPasscodeInfoRequired:(BOOL)emmPasscodeInfoRequired
  827. keychainError:(BOOL)keychainError
  828. restoredSignIn:(BOOL)restoredSignIn
  829. oldAccessToken:(BOOL)oldAccessToken
  830. modalCancel:(BOOL)modalCancel {
  831. if (restoredSignIn) {
  832. // clearAndAuthenticateWithOptions
  833. [_keychainHandler saveAuthState:_authState];
  834. BOOL isAuthorized = restoredSignIn ? YES : NO;
  835. [[[_authState expect] andReturnValue:[NSNumber numberWithBool:isAuthorized]] isAuthorized];
  836. }
  837. NSDictionary<NSString *, NSString *> *additionalParameters = emmPasscodeInfoRequired ?
  838. @{ @"emm_passcode_info_required" : @"1" } : nil;
  839. OIDAuthorizationResponse *authResponse =
  840. [OIDAuthorizationResponse testInstanceWithAdditionalParameters:additionalParameters
  841. errorString:authError];
  842. OIDTokenResponse *tokenResponse =
  843. [OIDTokenResponse testInstanceWithIDToken:[OIDTokenResponse fatIDToken]
  844. accessToken:restoredSignIn ? kAccessToken : nil
  845. expiresIn:oldAccessToken ? @(300) : nil
  846. refreshToken:kRefreshToken
  847. tokenRequest:nil];
  848. OIDTokenRequest *tokenRequest = [[OIDTokenRequest alloc]
  849. initWithConfiguration:authResponse.request.configuration
  850. grantType:OIDGrantTypeRefreshToken
  851. authorizationCode:nil
  852. redirectURL:nil
  853. clientID:authResponse.request.clientID
  854. clientSecret:authResponse.request.clientSecret
  855. scope:nil
  856. refreshToken:kRefreshToken
  857. codeVerifier:nil
  858. additionalParameters:tokenResponse.request.additionalParameters];
  859. // Set the response for the auth endpoint.
  860. GIDAuthorizationFlowProcessorTestBlock authorizationFlowTestBlock;
  861. if (modalCancel) {
  862. NSError *error = [NSError errorWithDomain:OIDGeneralErrorDomain
  863. code:OIDErrorCodeUserCanceledAuthorizationFlow
  864. userInfo:nil];
  865. authorizationFlowTestBlock =
  866. ^(GIDAuthorizationFlowProcessorFakeResponseProviderBlock responseProvider) {
  867. responseProvider(nil, error);
  868. };
  869. } else {
  870. authorizationFlowTestBlock =
  871. ^(GIDAuthorizationFlowProcessorFakeResponseProviderBlock responseProvider) {
  872. responseProvider(authResponse, nil);
  873. };
  874. }
  875. _authorizationFlowProcessor.testBlock = authorizationFlowTestBlock;
  876. // Set the response for `GIDProfileDataFetcher`.
  877. GIDProfileDataFetcherTestBlock profileDataFetcherTestBlock =
  878. ^(GIDProfileDataFetcherFakeResponseProvider responseProvider) {
  879. GIDProfileData *profileData = [GIDProfileData testInstance];
  880. responseProvider(profileData, nil);
  881. };
  882. _profileDataFetcher.testBlock = profileDataFetcherTestBlock;
  883. if (restoredSignIn) {
  884. // Mock `maybeFetchToken:` method in `restorePreviousSignIn:` flow.
  885. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  886. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  887. if (oldAccessToken) {
  888. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  889. // Corresponds to EMM support
  890. [[[_authState expect] andReturn:authResponse] lastAuthorizationResponse];
  891. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  892. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  893. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  894. [[[_authState expect] andReturn:tokenRequest]
  895. tokenRefreshRequestWithAdditionalParameters:[OCMArg any]];
  896. }
  897. } else {
  898. XCTestExpectation *expectation = [self expectationWithDescription:@"Callback called"];
  899. GIDSignInCompletion completion = ^(GIDSignInResult *_Nullable signInResult,
  900. NSError * _Nullable error) {
  901. [expectation fulfill];
  902. if (signInResult) {
  903. XCTAssertEqualObjects(signInResult.serverAuthCode, kServerAuthCode);
  904. } else {
  905. XCTAssertNotNil(error, @"Should have an error if the signInResult is nil");
  906. }
  907. XCTAssertFalse(self->_completionCalled, @"callback already called");
  908. self->_completionCalled = YES;
  909. self->_authError = error;
  910. };
  911. if (addScopesFlow) {
  912. [_signIn addScopes:@[kNewScope]
  913. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  914. presentingViewController:_presentingViewController
  915. #elif TARGET_OS_OSX
  916. presentingWindow:_presentingWindow
  917. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  918. completion:completion];
  919. } else {
  920. // Mock `maybeFetchToken:` method in Sign in flow.
  921. if (!(authError || modalCancel)) {
  922. [[[_authState expect] andReturn:nil] lastTokenResponse];
  923. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  924. // Corresponds to EMM support
  925. [[[_authState expect] andReturn:authResponse] lastAuthorizationResponse];
  926. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  927. [[[_authState expect] andReturn:nil] lastTokenResponse];
  928. [[[_authState expect] andReturn:authResponse] lastAuthorizationResponse];
  929. [[[_authState expect] andReturn:authResponse] lastAuthorizationResponse];
  930. }
  931. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  932. [_signIn signInWithPresentingViewController:_presentingViewController
  933. #elif TARGET_OS_OSX
  934. [_signIn signInWithPresentingWindow:_presentingWindow
  935. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  936. hint:nil
  937. completion:completion];
  938. }
  939. if (authError || modalCancel) {
  940. return;
  941. }
  942. [_authState verify];
  943. }
  944. if (restoredSignIn && oldAccessToken) {
  945. XCTestExpectation *expectation = [self expectationWithDescription:@"Callback should be called"];
  946. [_signIn restorePreviousSignInWithCompletion:^(GIDGoogleUser * _Nullable user,
  947. NSError * _Nullable error) {
  948. [expectation fulfill];
  949. XCTAssertNil(error, @"should have no error");
  950. }];
  951. }
  952. if (!restoredSignIn || (restoredSignIn && oldAccessToken)) {
  953. XCTAssertNotNil(_savedTokenRequest);
  954. XCTAssertNotNil(_savedTokenCallback);
  955. // OIDTokenCallback
  956. if (tokenError) {
  957. [[_authState expect] updateWithTokenResponse:nil error:tokenError];
  958. } else {
  959. [[_authState expect] updateWithTokenResponse:[OCMArg any] error:nil];
  960. }
  961. }
  962. if (tokenError) {
  963. _savedTokenCallback(nil, tokenError);
  964. return;
  965. }
  966. // SaveAuthCallback
  967. __block OIDAuthState *authState;
  968. __block OIDTokenResponse *updatedTokenResponse;
  969. __block OIDAuthorizationResponse *updatedAuthorizationResponse;
  970. __block GIDProfileData *profileData;
  971. if (keychainError) {
  972. _keychainHandler.failToSave = YES;
  973. } else {
  974. if (addScopesFlow) {
  975. [[[_authState expect] andReturn:authResponse] lastAuthorizationResponse];
  976. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  977. [[_user expect] updateWithTokenResponse:SAVE_TO_ARG_BLOCK(updatedTokenResponse)
  978. authorizationResponse:SAVE_TO_ARG_BLOCK(updatedAuthorizationResponse)
  979. profileData:SAVE_TO_ARG_BLOCK(profileData)];
  980. } else {
  981. [[[_user stub] andReturn:_user] alloc];
  982. (void)[[[_user expect] andReturn:_user] initWithAuthState:SAVE_TO_ARG_BLOCK(authState)
  983. profileData:SAVE_TO_ARG_BLOCK(profileData)];
  984. }
  985. }
  986. // CompletionCallback - mock server auth code parsing
  987. if (!keychainError) {
  988. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  989. }
  990. if (restoredSignIn && !oldAccessToken) {
  991. XCTestExpectation *expectation = [self expectationWithDescription:@"Callback should be called"];
  992. [_signIn restorePreviousSignInWithCompletion:^(GIDGoogleUser * _Nullable user,
  993. NSError * _Nullable error) {
  994. [expectation fulfill];
  995. XCTAssertNil(error, @"should have no error");
  996. }];
  997. } else {
  998. // Simulate token endpoint response.
  999. _savedTokenCallback(tokenResponse, nil);
  1000. }
  1001. if (keychainError) {
  1002. return;
  1003. }
  1004. [self waitForExpectationsWithTimeout:1 handler:nil];
  1005. [_authState verify];
  1006. if (addScopesFlow) {
  1007. XCTAssertNotNil(updatedTokenResponse);
  1008. XCTAssertNotNil(updatedAuthorizationResponse);
  1009. } else {
  1010. XCTAssertNotNil(authState);
  1011. }
  1012. // Check fat ID token decoding
  1013. XCTAssertEqualObjects(profileData, [GIDProfileData testInstance]);
  1014. // If attempt to authenticate again, will reuse existing auth object.
  1015. _completionCalled = NO;
  1016. _authError = nil;
  1017. __block GIDGoogleUserCompletion completion;
  1018. [[_user expect] refreshTokensIfNeededWithCompletion:SAVE_TO_ARG_BLOCK(completion)];
  1019. XCTestExpectation *expectation = [self expectationWithDescription:@"Callback should be called"];
  1020. [_signIn restorePreviousSignInWithCompletion:^(GIDGoogleUser * _Nullable user,
  1021. NSError * _Nullable error) {
  1022. [expectation fulfill];
  1023. XCTAssertNil(error, @"should have no error");
  1024. }];
  1025. completion(_user, nil);
  1026. [self waitForExpectationsWithTimeout:1 handler:nil];
  1027. }
  1028. @end