GIDSignInTest.m 46 KB

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