GIDSignInTest.m 65 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683
  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 GTMAppAuth;
  25. #import "GoogleSignIn/Sources/GIDEMMSupport.h"
  26. #import "GoogleSignIn/Sources/GIDGoogleUser_Private.h"
  27. #import "GoogleSignIn/Sources/GIDSignIn_Private.h"
  28. #import "GoogleSignIn/Sources/GIDSignInPreferences.h"
  29. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  30. #import <AppCheckCore/GACAppCheckToken.h>
  31. #import "GoogleSignIn/Sources/GIDAppCheck/Implementations/GIDAppCheck.h"
  32. #import "GoogleSignIn/Sources/GIDAppCheck/Implementations/Fake/GIDAppCheckProviderFake.h"
  33. #import "GoogleSignIn/Sources/GIDEMMErrorHandler.h"
  34. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  35. #import "GoogleSignIn/Tests/Unit/GIDFakeFetcher.h"
  36. #import "GoogleSignIn/Tests/Unit/GIDFakeFetcherService.h"
  37. #import "GoogleSignIn/Tests/Unit/GIDFakeMainBundle.h"
  38. #import "GoogleSignIn/Tests/Unit/OIDAuthorizationResponse+Testing.h"
  39. #import "GoogleSignIn/Tests/Unit/OIDTokenResponse+Testing.h"
  40. #ifdef SWIFT_PACKAGE
  41. @import AppAuth;
  42. @import GTMSessionFetcherCore;
  43. @import OCMock;
  44. #else
  45. #import <AppAuth/OIDAuthState.h>
  46. #import <AppAuth/OIDAuthorizationRequest.h>
  47. #import <AppAuth/OIDAuthorizationResponse.h>
  48. #import <AppAuth/OIDAuthorizationService.h>
  49. #import <AppAuth/OIDError.h>
  50. #import <AppAuth/OIDGrantTypes.h>
  51. #import <AppAuth/OIDTokenRequest.h>
  52. #import <AppAuth/OIDTokenResponse.h>
  53. #import <AppAuth/OIDURLQueryComponent.h>
  54. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  55. #import <AppAuth/OIDAuthorizationService+IOS.h>
  56. #elif TARGET_OS_OSX
  57. #import <AppAuth/OIDAuthorizationService+Mac.h>
  58. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  59. #import <GTMSessionFetcher/GTMSessionFetcher.h>
  60. #import <OCMock/OCMock.h>
  61. #endif
  62. // Create a BLOCK to store the actual address for arg in param.
  63. #define SAVE_TO_ARG_BLOCK(param) [OCMArg checkWithBlock:^(id arg) {\
  64. param = arg;\
  65. return YES;\
  66. }]
  67. #define COPY_TO_ARG_BLOCK(param) [OCMArg checkWithBlock:^(id arg) {\
  68. param = [arg copy];\
  69. return YES;\
  70. }]
  71. /// `NSUserDefaults` suite name for testing with `GIDAppCheck`.
  72. static NSString *const kUserDefaultsSuiteName = @"GIDAppCheckKeySuiteName";
  73. static NSString * const kFakeGaiaID = @"123456789";
  74. static NSString * const kFakeIDToken = @"FakeIDToken";
  75. static NSString * const kClientId = @"FakeClientID";
  76. static NSString * const kDotReversedClientId = @"FakeClientID";
  77. static NSString * const kClientId2 = @"FakeClientID2";
  78. static NSString * const kServerClientId = @"FakeServerClientID";
  79. static NSString * const kLanguage = @"FakeLanguage";
  80. static NSString * const kScope = @"FakeScope";
  81. static NSString * const kScope2 = @"FakeScope2";
  82. static NSString * const kAuthCode = @"FakeAuthCode";
  83. static NSString * const kKeychainName = @"auth";
  84. static NSString * const kUserEmail = @"FakeUserEmail";
  85. static NSString * const kVerifier = @"FakeVerifier";
  86. static NSString * const kOpenIDRealm = @"FakeRealm";
  87. static NSString * const kFakeHostedDomain = @"fakehosteddomain.com";
  88. static NSString * const kFakeUserName = @"fake username";
  89. static NSString * const kFakeUserGivenName = @"fake";
  90. static NSString * const kFakeUserFamilyName = @"username";
  91. static NSString * const kFakeUserPictureURL = @"fake_user_picture_url";
  92. static NSString * const kContinueURL = @"com.google.UnitTests:/oauth2callback";
  93. static NSString * const kContinueURLWithClientID = @"FakeClientID:/oauth2callback";
  94. static NSString * const kWrongSchemeURL = @"wrong.app:/oauth2callback";
  95. static NSString * const kWrongPathURL = @"com.google.UnitTests:/wrong_path";
  96. static NSString * const kEMMRestartAuthURL =
  97. @"com.google.UnitTests:///emmcallback?action=restart_auth";
  98. static NSString * const kEMMWrongPathURL =
  99. @"com.google.UnitTests:///unknowcallback?action=restart_auth";
  100. static NSString * const kEMMWrongActionURL =
  101. @"com.google.UnitTests:///emmcallback?action=unrecognized";
  102. static NSString * const kDevicePolicyAppBundleID = @"com.google.DevicePolicy";
  103. static NSString * const kAppHasRunBeforeKey = @"GPP_AppHasRunBefore";
  104. static NSString * const kFingerprintKeychainName = @"fingerprint";
  105. static NSString * const kVerifierKeychainName = @"verifier";
  106. static NSString * const kVerifierKey = @"verifier";
  107. static NSString * const kOpenIDRealmKey = @"openid.realm";
  108. static NSString * const kSavedKeychainServiceName = @"saved-keychain";
  109. static NSString * const kKeychainAccountName = @"GooglePlus";
  110. static NSString * const kUserNameKey = @"name";
  111. static NSString * const kUserGivenNameKey = @"givenName";
  112. static NSString * const kUserFamilyNameKey = @"familyName";
  113. static NSString * const kUserImageKey = @"picture";
  114. static NSString * const kAppName = @"UnitTests";
  115. static NSString * const kUserIDKey = @"userID";
  116. static NSString * const kHostedDomainKey = @"hostedDomain";
  117. static NSString * const kIDTokenExpirationKey = @"idTokenExp";
  118. static NSString * const kScopeKey = @"scope";
  119. // Basic profile (Fat ID Token / userinfo endpoint) keys
  120. static NSString *const kBasicProfilePictureKey = @"picture";
  121. static NSString *const kBasicProfileNameKey = @"name";
  122. static NSString *const kBasicProfileGivenNameKey = @"given_name";
  123. static NSString *const kBasicProfileFamilyNameKey = @"family_name";
  124. static NSString * const kCustomKeychainName = @"CUSTOM_KEYCHAIN_NAME";
  125. static NSString * const kAddActivity = @"http://schemas.google.com/AddActivity";
  126. static NSString * const kErrorDomain = @"ERROR_DOMAIN";
  127. static NSInteger const kErrorCode = 212;
  128. static NSString *const kDriveScope = @"https://www.googleapis.com/auth/drive";
  129. static NSString *const kTokenURL = @"https://oauth2.googleapis.com/token";
  130. static NSString *const kFakeURL = @"http://foo.com";
  131. static NSString *const kEMMSupport = @"1";
  132. static NSString *const kGrantedScope = @"grantedScope";
  133. static NSString *const kNewScope = @"newScope";
  134. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  135. // This category is used to allow the test to swizzle a private method.
  136. @interface UIViewController (Testing)
  137. // This private method provides access to the window. It's declared here to avoid a warning about
  138. // an unrecognized selector in the test.
  139. - (UIWindow *)_window;
  140. @end
  141. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  142. // This class extension exposes GIDSignIn methods to our tests.
  143. @interface GIDSignIn ()
  144. // Exposing private method so we can call it to disambiguate between interactive and non-interactive
  145. // sign-in attempts for the purposes of testing the GIDSignInUIDelegate (which should not be
  146. // called in the case of a non-interactive sign in).
  147. - (void)authenticateMaybeInteractively:(BOOL)interactive withParams:(NSDictionary *)params;
  148. - (BOOL)assertValidPresentingViewContoller;
  149. @end
  150. @interface GIDSignInTest : XCTestCase {
  151. @private
  152. // Whether or not the OS version is eligible for EMM.
  153. BOOL _isEligibleForEMM;
  154. // Mock |OIDAuthState|.
  155. id _authState;
  156. // Mock |OIDTokenResponse|.
  157. id _tokenResponse;
  158. // Mock |OIDTokenRequest|.
  159. id _tokenRequest;
  160. // Mock |GTMAuthSession|.
  161. id _authorization;
  162. // Mock |GTMKeychainStore|.
  163. id _keychainStore;
  164. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  165. // Mock |UIViewController|.
  166. id _presentingViewController;
  167. #elif TARGET_OS_OSX
  168. // Mock |NSWindow|.
  169. id _presentingWindow;
  170. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  171. // Mock for |GIDGoogleUser|.
  172. id _user;
  173. // Mock for |OIDAuthorizationService|
  174. id _oidAuthorizationService;
  175. // Parameter saved from delegate call.
  176. NSError *_authError;
  177. // Whether callback block has been called.
  178. BOOL _completionCalled;
  179. // Fake fetcher service to emulate network requests.
  180. GIDFakeFetcherService *_fetcherService;
  181. // Fake [NSBundle mainBundle];
  182. GIDFakeMainBundle *_fakeMainBundle;
  183. // Whether |saveParamsToKeychainForName:authentication:| has been called.
  184. BOOL _keychainSaved;
  185. // Whether |removeAuthFromKeychainForName:| has been called.
  186. BOOL _keychainRemoved;
  187. // The |GIDSignIn| object being tested.
  188. GIDSignIn *_signIn;
  189. // The configuration to be used when testing |GIDSignIn|.
  190. GIDConfiguration *_configuration;
  191. // The login hint to be used when testing |GIDSignIn|.
  192. NSString *_hint;
  193. // The completion to be used when testing |GIDSignIn|.
  194. GIDSignInCompletion _completion;
  195. // The saved authorization request.
  196. OIDAuthorizationRequest *_savedAuthorizationRequest;
  197. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  198. // The saved presentingViewController from the authorization request.
  199. UIViewController *_savedPresentingViewController;
  200. #elif TARGET_OS_OSX
  201. // The saved presentingWindow from the authorization request.
  202. NSWindow *_savedPresentingWindow;
  203. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  204. // The saved authorization callback.
  205. OIDAuthorizationCallback _savedAuthorizationCallback;
  206. // The saved token request.
  207. OIDTokenRequest *_savedTokenRequest;
  208. // The saved token request callback.
  209. OIDTokenCallback _savedTokenCallback;
  210. // Status returned by saveAuthorization:toKeychainForName:
  211. BOOL _saveAuthorizationReturnValue;
  212. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  213. // Test userDefaults for use with `GIDAppCheck`
  214. NSUserDefaults *_testUserDefaults;
  215. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  216. }
  217. @end
  218. @implementation GIDSignInTest
  219. #pragma mark - Lifecycle
  220. - (void)setUp {
  221. [super setUp];
  222. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  223. _isEligibleForEMM = [UIDevice currentDevice].systemVersion.integerValue >= 9;
  224. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  225. _saveAuthorizationReturnValue = YES;
  226. // States
  227. _completionCalled = NO;
  228. _keychainSaved = NO;
  229. _keychainRemoved = NO;
  230. // Mocks
  231. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  232. _presentingViewController = OCMStrictClassMock([UIViewController class]);
  233. #elif TARGET_OS_OSX
  234. _presentingWindow = OCMStrictClassMock([NSWindow class]);
  235. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  236. _authState = OCMStrictClassMock([OIDAuthState class]);
  237. OCMStub([_authState alloc]).andReturn(_authState);
  238. OCMStub([_authState initWithAuthorizationResponse:OCMOCK_ANY]).andReturn(_authState);
  239. _tokenResponse = OCMStrictClassMock([OIDTokenResponse class]);
  240. _tokenRequest = OCMStrictClassMock([OIDTokenRequest class]);
  241. _authorization = OCMStrictClassMock([GTMAuthSession class]);
  242. _keychainStore = OCMStrictClassMock([GTMKeychainStore class]);
  243. OCMStub(
  244. [_keychainStore retrieveAuthSessionWithItemName:OCMOCK_ANY error:OCMArg.anyObjectRef]
  245. ).andReturn(_authorization);
  246. OCMStub([_keychainStore retrieveAuthSessionWithError:nil]).andReturn(_authorization);
  247. OCMStub([_authorization alloc]).andReturn(_authorization);
  248. OCMStub([_authorization initWithAuthState:OCMOCK_ANY]).andReturn(_authorization);
  249. OCMStub(
  250. [_keychainStore removeAuthSessionWithError:OCMArg.anyObjectRef]
  251. ).andDo(^(NSInvocation *invocation) {
  252. self->_keychainRemoved = YES;
  253. });
  254. _user = OCMStrictClassMock([GIDGoogleUser class]);
  255. _oidAuthorizationService = OCMStrictClassMock([OIDAuthorizationService class]);
  256. OCMStub([_oidAuthorizationService
  257. presentAuthorizationRequest:SAVE_TO_ARG_BLOCK(self->_savedAuthorizationRequest)
  258. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  259. presentingViewController:SAVE_TO_ARG_BLOCK(self->_savedPresentingViewController)
  260. #elif TARGET_OS_OSX
  261. presentingWindow:SAVE_TO_ARG_BLOCK(self->_savedPresentingWindow)
  262. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  263. callback:COPY_TO_ARG_BLOCK(self->_savedAuthorizationCallback)]);
  264. OCMStub([self->_oidAuthorizationService
  265. performTokenRequest:SAVE_TO_ARG_BLOCK(self->_savedTokenRequest)
  266. callback:COPY_TO_ARG_BLOCK(self->_savedTokenCallback)]);
  267. // Fakes
  268. _fetcherService = [[GIDFakeFetcherService alloc] init];
  269. _fakeMainBundle = [[GIDFakeMainBundle alloc] init];
  270. [_fakeMainBundle startFakingWithClientID:kClientId];
  271. [_fakeMainBundle fakeAllSchemesSupported];
  272. // Object under test
  273. [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kAppHasRunBeforeKey];
  274. _signIn = [[GIDSignIn alloc] initWithKeychainStore:_keychainStore];
  275. _hint = nil;
  276. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  277. _testUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:kUserDefaultsSuiteName];
  278. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  279. __weak GIDSignInTest *weakSelf = self;
  280. _completion = ^(GIDSignInResult *_Nullable signInResult, NSError * _Nullable error) {
  281. GIDSignInTest *strongSelf = weakSelf;
  282. if (!signInResult) {
  283. XCTAssertNotNil(error, @"should have an error if the signInResult is nil");
  284. }
  285. XCTAssertFalse(strongSelf->_completionCalled, @"callback already called");
  286. strongSelf->_completionCalled = YES;
  287. strongSelf->_authError = error;
  288. };
  289. }
  290. - (void)tearDown {
  291. OCMVerifyAll(_authState);
  292. OCMVerifyAll(_tokenResponse);
  293. OCMVerifyAll(_tokenRequest);
  294. OCMVerifyAll(_authorization);
  295. OCMVerifyAll(_user);
  296. OCMVerifyAll(_oidAuthorizationService);
  297. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  298. OCMVerifyAll(_presentingViewController);
  299. #elif TARGET_OS_OSX
  300. OCMVerifyAll(_presentingWindow);
  301. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  302. [[NSUserDefaults standardUserDefaults] removeObjectForKey:kAppHasRunBeforeKey];
  303. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  304. [_testUserDefaults removeObjectForKey:kGIDAppCheckPreparedKey];
  305. [_testUserDefaults removeSuiteNamed:kUserDefaultsSuiteName];
  306. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  307. [_fakeMainBundle stopFaking];
  308. [super tearDown];
  309. }
  310. #pragma mark - Tests
  311. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  312. - (void)testConfigureSucceeds {
  313. if (@available(iOS 14, *)) {
  314. XCTestExpectation *configureSucceedsExpecation =
  315. [self expectationWithDescription:@"Configure succeeds expectation"];
  316. GACAppCheckToken *token = [[GACAppCheckToken alloc] initWithToken:@"foo"
  317. expirationDate:[NSDate distantFuture]];
  318. GIDAppCheckProviderFake *fakeProvider =
  319. [[GIDAppCheckProviderFake alloc] initWithAppCheckToken:token error:nil];
  320. GIDAppCheck *appCheck = [[GIDAppCheck alloc] initWithAppCheckProvider:fakeProvider
  321. userDefaults:_testUserDefaults];
  322. GIDSignIn *signIn = [[GIDSignIn alloc] initWithKeychainStore:_keychainStore
  323. appCheck:appCheck];
  324. [signIn configureWithCompletion:^(NSError * _Nullable error) {
  325. XCTAssertNil(error);
  326. [configureSucceedsExpecation fulfill];
  327. }];
  328. [self waitForExpectations:@[configureSucceedsExpecation] timeout:1];
  329. XCTAssertTrue(appCheck.isPrepared);
  330. }
  331. }
  332. - (void)testConfigureFailsNoTokenOrError {
  333. if (@available(iOS 14, *)) {
  334. XCTestExpectation *configureFailsExpecation =
  335. [self expectationWithDescription:@"Configure fails expectation"];
  336. GIDAppCheckProviderFake *fakeProvider =
  337. [[GIDAppCheckProviderFake alloc] initWithAppCheckToken:nil error:nil];
  338. GIDAppCheck *appCheck =
  339. [[GIDAppCheck alloc] initWithAppCheckProvider:fakeProvider
  340. userDefaults:_testUserDefaults];
  341. GIDSignIn *signIn = [[GIDSignIn alloc] initWithKeychainStore:_keychainStore
  342. appCheck:appCheck];
  343. // Should fail if missing both token and error
  344. [signIn configureWithCompletion:^(NSError * _Nullable error) {
  345. XCTAssertNotNil(error);
  346. XCTAssertEqual(error.code, kGIDAppCheckUnexpectedError);
  347. [configureFailsExpecation fulfill];
  348. }];
  349. [self waitForExpectations:@[configureFailsExpecation] timeout:1];
  350. XCTAssertFalse(appCheck.isPrepared);
  351. }
  352. }
  353. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  354. - (void)testInitWithKeychainStore {
  355. GTMKeychainStore *store = [[GTMKeychainStore alloc] initWithItemName:@"foo"];
  356. GIDSignIn *signIn;
  357. signIn = [[GIDSignIn alloc] initWithKeychainStore:store];
  358. XCTAssertNotNil(signIn.configuration);
  359. XCTAssertEqual(signIn.configuration.clientID, kClientId);
  360. XCTAssertNil(signIn.configuration.serverClientID);
  361. XCTAssertNil(signIn.configuration.hostedDomain);
  362. XCTAssertNil(signIn.configuration.openIDRealm);
  363. }
  364. - (void)testInitWithKeychainStore_noConfig {
  365. [_fakeMainBundle fakeWithClientID:nil
  366. serverClientID:nil
  367. hostedDomain:nil
  368. openIDRealm:nil];
  369. GTMKeychainStore *store = [[GTMKeychainStore alloc] initWithItemName:@"foo"];
  370. GIDSignIn *signIn;
  371. signIn = [[GIDSignIn alloc] initWithKeychainStore:store];
  372. XCTAssertNil(signIn.configuration);
  373. }
  374. - (void)testInitWithKeychainStore_fullConfig {
  375. [_fakeMainBundle fakeWithClientID:kClientId
  376. serverClientID:kServerClientId
  377. hostedDomain:kFakeHostedDomain
  378. openIDRealm:kOpenIDRealm];
  379. GTMKeychainStore *store = [[GTMKeychainStore alloc] initWithItemName:@"foo"];
  380. GIDSignIn *signIn;
  381. signIn = [[GIDSignIn alloc] initWithKeychainStore:store];
  382. XCTAssertNotNil(signIn.configuration);
  383. XCTAssertEqual(signIn.configuration.clientID, kClientId);
  384. XCTAssertEqual(signIn.configuration.serverClientID, kServerClientId);
  385. XCTAssertEqual(signIn.configuration.hostedDomain, kFakeHostedDomain);
  386. XCTAssertEqual(signIn.configuration.openIDRealm, kOpenIDRealm);
  387. }
  388. - (void)testInitWithKeychainStore_invalidConfig {
  389. [_fakeMainBundle fakeWithClientID:@[ @"bad", @"config", @"values" ]
  390. serverClientID:nil
  391. hostedDomain:nil
  392. openIDRealm:nil];
  393. GTMKeychainStore *store = [[GTMKeychainStore alloc] initWithItemName:@"foo"];
  394. GIDSignIn *signIn;
  395. signIn = [[GIDSignIn alloc] initWithKeychainStore:store];
  396. XCTAssertNil(signIn.configuration);
  397. }
  398. - (void)testRestorePreviousSignInNoRefresh_hasPreviousUser {
  399. [[[_authorization stub] andReturn:_authState] authState];
  400. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  401. [[_authorization expect] setDelegate:OCMOCK_ANY];
  402. #endif // TARGET_OS_IOS || !TARGET_OS_MACCATALYST
  403. OCMStub([_authState lastTokenResponse]).andReturn(_tokenResponse);
  404. OCMStub([_authState refreshToken]).andReturn(kRefreshToken);
  405. [[_authState expect] setStateChangeDelegate:OCMOCK_ANY];
  406. id idTokenDecoded = OCMClassMock([OIDIDToken class]);
  407. OCMStub([idTokenDecoded alloc]).andReturn(idTokenDecoded);
  408. OCMStub([idTokenDecoded initWithIDTokenString:OCMOCK_ANY]).andReturn(idTokenDecoded);
  409. OCMStub([idTokenDecoded subject]).andReturn(kFakeGaiaID);
  410. // Mock generating a GIDConfiguration when initializing GIDGoogleUser.
  411. OIDAuthorizationResponse *authResponse =
  412. [OIDAuthorizationResponse testInstance];
  413. OCMStub([_authState lastAuthorizationResponse]).andReturn(authResponse);
  414. OCMStub([_tokenResponse idToken]).andReturn(kFakeIDToken);
  415. OCMStub([_tokenResponse request]).andReturn(_tokenRequest);
  416. OCMStub([_tokenRequest additionalParameters]).andReturn(nil);
  417. OCMStub([_tokenResponse accessToken]).andReturn(kAccessToken);
  418. OCMStub([_tokenResponse accessTokenExpirationDate]).andReturn(nil);
  419. [_signIn restorePreviousSignInNoRefresh];
  420. [idTokenDecoded verify];
  421. XCTAssertEqual(_signIn.currentUser.userID, kFakeGaiaID);
  422. [idTokenDecoded stopMocking];
  423. }
  424. - (void)testRestoredPreviousSignInNoRefresh_hasNoPreviousUser {
  425. [[[_authorization expect] andReturn:nil] authState];
  426. [_signIn restorePreviousSignInNoRefresh];
  427. [_authorization verify];
  428. XCTAssertNil(_signIn.currentUser);
  429. }
  430. - (void)testHasPreviousSignIn_HasBeenAuthenticated {
  431. [[[_authorization expect] andReturn:_authState] authState];
  432. [[[_authState expect] andReturnValue:[NSNumber numberWithBool:YES]] isAuthorized];
  433. XCTAssertTrue([_signIn hasPreviousSignIn], @"should return |YES|");
  434. [_authorization verify];
  435. [_authState verify];
  436. XCTAssertFalse(_keychainRemoved, @"should not remove keychain");
  437. XCTAssertFalse(_completionCalled, @"should not call delegate");
  438. XCTAssertNil(_authError, @"should have no error");
  439. }
  440. - (void)testHasPreviousSignIn_HasNotBeenAuthenticated {
  441. [[[_authorization expect] andReturn:_authState] authState];
  442. [[[_authState expect] andReturnValue:[NSNumber numberWithBool:NO]] isAuthorized];
  443. XCTAssertFalse([_signIn hasPreviousSignIn], @"should return |NO|");
  444. [_authorization verify];
  445. [_authState verify];
  446. XCTAssertFalse(_keychainRemoved, @"should not remove keychain");
  447. XCTAssertFalse(_completionCalled, @"should not call delegate");
  448. }
  449. - (void)testRestorePreviousSignInWhenSignedOut {
  450. [[[_authorization expect] andReturn:_authState] authState];
  451. [[[_authState expect] andReturnValue:[NSNumber numberWithBool:NO]] isAuthorized];
  452. _completionCalled = NO;
  453. _authError = nil;
  454. XCTestExpectation *expectation = [self expectationWithDescription:@"Callback should be called."];
  455. [_signIn restorePreviousSignInWithCompletion:^(GIDGoogleUser *_Nullable user,
  456. NSError * _Nullable error) {
  457. [expectation fulfill];
  458. XCTAssertNotNil(error, @"error should not have been nil");
  459. XCTAssertEqual(error.domain,
  460. kGIDSignInErrorDomain,
  461. @"error domain should have been the sign-in error domain.");
  462. XCTAssertEqual(error.code,
  463. kGIDSignInErrorCodeHasNoAuthInKeychain,
  464. @"error code should have been the 'NoAuthInKeychain' error code.");
  465. }];
  466. [self waitForExpectationsWithTimeout:1 handler:nil];
  467. [_authorization verify];
  468. [_authState verify];
  469. }
  470. - (void)testNotRestorePreviousSignInWhenSignedOutAndCompletionIsNil {
  471. [[[_authorization expect] andReturn:_authState] authState];
  472. [[[_authState expect] andReturnValue:[NSNumber numberWithBool:NO]] isAuthorized];
  473. [_signIn restorePreviousSignInWithCompletion:nil];
  474. XCTAssertNil(_signIn.currentUser);
  475. }
  476. - (void)testRestorePreviousSignInWhenCompletionIsNil {
  477. [[[_authorization expect] andReturn:_authState] authState];
  478. [[_keychainStore expect] saveAuthSession:OCMOCK_ANY error:[OCMArg anyObjectRef]];
  479. [[[_authState expect] andReturnValue:[NSNumber numberWithBool:YES]] isAuthorized];
  480. OIDTokenResponse *tokenResponse =
  481. [OIDTokenResponse testInstanceWithIDToken:[OIDTokenResponse fatIDToken]
  482. accessToken:kAccessToken
  483. expiresIn:nil
  484. refreshToken:kRefreshToken
  485. tokenRequest:nil];
  486. [[[_authState stub] andReturn:tokenResponse] lastTokenResponse];
  487. // TODO: Create a real GIDGoogleUser to verify the signed in user value(#306).
  488. [[[_user stub] andReturn:_user] alloc];
  489. (void)[[[_user expect] andReturn:_user] initWithAuthState:OCMOCK_ANY
  490. profileData:OCMOCK_ANY];
  491. XCTAssertNil(_signIn.currentUser);
  492. [_signIn restorePreviousSignInWithCompletion:nil];
  493. XCTAssertNotNil(_signIn.currentUser);
  494. }
  495. - (void)testOAuthLogin {
  496. OCMStub(
  497. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  498. ).andDo(^(NSInvocation *invocation) {
  499. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  500. });
  501. [self OAuthLoginWithAddScopesFlow:NO
  502. authError:nil
  503. tokenError:nil
  504. emmPasscodeInfoRequired:NO
  505. keychainError:NO
  506. restoredSignIn:NO
  507. oldAccessToken:NO
  508. modalCancel:NO];
  509. }
  510. - (void)testOAuthLogin_RestoredSignIn {
  511. OCMStub(
  512. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  513. ).andDo(^(NSInvocation *invocation) {
  514. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  515. });
  516. [self OAuthLoginWithAddScopesFlow:NO
  517. authError:nil
  518. tokenError:nil
  519. emmPasscodeInfoRequired:NO
  520. keychainError:NO
  521. restoredSignIn:YES
  522. oldAccessToken:NO
  523. modalCancel:NO];
  524. }
  525. - (void)testOAuthLogin_RestoredSignInOldAccessToken {
  526. OCMStub(
  527. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  528. ).andDo(^(NSInvocation *invocation) {
  529. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  530. });
  531. [self OAuthLoginWithAddScopesFlow:NO
  532. authError:nil
  533. tokenError:nil
  534. emmPasscodeInfoRequired:NO
  535. keychainError:NO
  536. restoredSignIn:YES
  537. oldAccessToken:YES
  538. modalCancel:NO];
  539. }
  540. - (void)testOAuthLogin_AdditionalScopes {
  541. NSString *expectedScopeString;
  542. OCMStub(
  543. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  544. ).andDo(^(NSInvocation *invocation) {
  545. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  546. });
  547. [self OAuthLoginWithAddScopesFlow:NO
  548. authError:nil
  549. tokenError:nil
  550. emmPasscodeInfoRequired:NO
  551. keychainError:NO
  552. restoredSignIn:NO
  553. oldAccessToken:NO
  554. modalCancel:NO
  555. useAdditionalScopes:YES
  556. additionalScopes:nil
  557. manualNonce:nil];
  558. expectedScopeString = [@[ @"email", @"profile" ] componentsJoinedByString:@" "];
  559. XCTAssertEqualObjects(_savedAuthorizationRequest.scope, expectedScopeString);
  560. [self OAuthLoginWithAddScopesFlow:NO
  561. authError:nil
  562. tokenError:nil
  563. emmPasscodeInfoRequired:NO
  564. keychainError:NO
  565. restoredSignIn:NO
  566. oldAccessToken:NO
  567. modalCancel:NO
  568. useAdditionalScopes:YES
  569. additionalScopes:@[ kScope ]
  570. manualNonce:nil];
  571. expectedScopeString = [@[ kScope, @"email", @"profile" ] componentsJoinedByString:@" "];
  572. XCTAssertEqualObjects(_savedAuthorizationRequest.scope, expectedScopeString);
  573. [self OAuthLoginWithAddScopesFlow:NO
  574. authError:nil
  575. tokenError:nil
  576. emmPasscodeInfoRequired:NO
  577. keychainError:NO
  578. restoredSignIn:NO
  579. oldAccessToken:NO
  580. modalCancel:NO
  581. useAdditionalScopes:YES
  582. additionalScopes:@[ kScope, kScope2 ]
  583. manualNonce:nil];
  584. expectedScopeString = [@[ kScope, kScope2, @"email", @"profile" ] componentsJoinedByString:@" "];
  585. XCTAssertEqualObjects(_savedAuthorizationRequest.scope, expectedScopeString);
  586. }
  587. - (void)testAddScopes {
  588. // Restore the previous sign-in account. This is the preparation for adding scopes.
  589. OCMStub(
  590. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  591. ).andDo(^(NSInvocation *invocation) {
  592. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  593. });
  594. [self OAuthLoginWithAddScopesFlow:NO
  595. authError:nil
  596. tokenError:nil
  597. emmPasscodeInfoRequired:NO
  598. keychainError:NO
  599. restoredSignIn:YES
  600. oldAccessToken:NO
  601. modalCancel:NO];
  602. XCTAssertNotNil(_signIn.currentUser);
  603. id profile = OCMStrictClassMock([GIDProfileData class]);
  604. OCMStub([profile email]).andReturn(kUserEmail);
  605. // Mock for the method `addScopes`.
  606. GIDConfiguration *configuration = [[GIDConfiguration alloc] initWithClientID:kClientId
  607. serverClientID:nil
  608. hostedDomain:nil
  609. openIDRealm:kOpenIDRealm];
  610. OCMStub([_user configuration]).andReturn(configuration);
  611. OCMStub([_user profile]).andReturn(profile);
  612. OCMStub([_user grantedScopes]).andReturn(@[kGrantedScope]);
  613. [self OAuthLoginWithAddScopesFlow:YES
  614. authError:nil
  615. tokenError:nil
  616. emmPasscodeInfoRequired:NO
  617. keychainError:NO
  618. restoredSignIn:NO
  619. oldAccessToken:NO
  620. modalCancel:NO];
  621. NSArray<NSString *> *grantedScopes;
  622. NSString *grantedScopeString = _savedAuthorizationRequest.scope;
  623. if (grantedScopeString) {
  624. grantedScopeString = [grantedScopeString stringByTrimmingCharactersInSet:
  625. [NSCharacterSet whitespaceCharacterSet]];
  626. // Tokenize with space as a delimiter.
  627. NSMutableArray<NSString *> *parsedScopes =
  628. [[grantedScopeString componentsSeparatedByString:@" "] mutableCopy];
  629. // Remove empty strings.
  630. [parsedScopes removeObject:@""];
  631. grantedScopes = [parsedScopes copy];
  632. }
  633. NSArray<NSString *> *expectedScopes = @[kNewScope, kGrantedScope];
  634. XCTAssertEqualObjects(grantedScopes, expectedScopes);
  635. [_user verify];
  636. [profile verify];
  637. [profile stopMocking];
  638. }
  639. - (void)testOpenIDRealm {
  640. _signIn.configuration = [[GIDConfiguration alloc] initWithClientID:kClientId
  641. serverClientID:nil
  642. hostedDomain:nil
  643. openIDRealm:kOpenIDRealm];
  644. OCMStub(
  645. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  646. ).andDo(^(NSInvocation *invocation) {
  647. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  648. });
  649. [self OAuthLoginWithAddScopesFlow:NO
  650. authError:nil
  651. tokenError:nil
  652. emmPasscodeInfoRequired:NO
  653. keychainError:NO
  654. restoredSignIn:NO
  655. oldAccessToken:NO
  656. modalCancel:NO];
  657. NSDictionary<NSString *, NSString *> *params = _savedTokenRequest.additionalParameters;
  658. XCTAssertEqual(params[kOpenIDRealmKey], kOpenIDRealm, @"OpenID Realm should match.");
  659. }
  660. - (void)testManualNonce {
  661. _signIn.configuration = [[GIDConfiguration alloc] initWithClientID:kClientId
  662. serverClientID:nil
  663. hostedDomain:nil
  664. openIDRealm:kOpenIDRealm];
  665. OCMStub(
  666. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  667. ).andDo(^(NSInvocation *invocation) {
  668. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  669. });
  670. NSString* manualNonce = @"manual_nonce";
  671. [self OAuthLoginWithAddScopesFlow:NO
  672. authError:nil
  673. tokenError:nil
  674. emmPasscodeInfoRequired:NO
  675. keychainError:NO
  676. restoredSignIn:NO
  677. oldAccessToken:NO
  678. modalCancel:NO
  679. useAdditionalScopes:NO
  680. additionalScopes:@[]
  681. manualNonce:manualNonce];
  682. XCTAssertEqualObjects(_savedAuthorizationRequest.nonce,
  683. manualNonce,
  684. @"Provided nonce should match nonce in authorization request.");
  685. }
  686. - (void)testOAuthLogin_LoginHint {
  687. _hint = kUserEmail;
  688. OCMStub(
  689. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  690. ).andDo(^(NSInvocation *invocation) {
  691. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  692. });
  693. [self OAuthLoginWithAddScopesFlow:NO
  694. authError:nil
  695. tokenError:nil
  696. emmPasscodeInfoRequired:NO
  697. keychainError:NO
  698. restoredSignIn:NO
  699. oldAccessToken:NO
  700. modalCancel:NO];
  701. NSDictionary<NSString *, NSObject *> *params = _savedAuthorizationRequest.additionalParameters;
  702. XCTAssertEqualObjects(params[@"login_hint"], kUserEmail, @"login hint should match");
  703. }
  704. - (void)testOAuthLogin_HostedDomain {
  705. _signIn.configuration = [[GIDConfiguration alloc] initWithClientID:kClientId
  706. serverClientID:nil
  707. hostedDomain:kHostedDomain
  708. openIDRealm:nil];
  709. OCMStub(
  710. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  711. ).andDo(^(NSInvocation *invocation) {
  712. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  713. });
  714. [self OAuthLoginWithAddScopesFlow:NO
  715. authError:nil
  716. tokenError:nil
  717. emmPasscodeInfoRequired:NO
  718. keychainError:NO
  719. restoredSignIn:NO
  720. oldAccessToken:NO
  721. modalCancel:NO];
  722. NSDictionary<NSString *, NSObject *> *params = _savedAuthorizationRequest.additionalParameters;
  723. XCTAssertEqualObjects(params[@"hd"], kHostedDomain, @"hosted domain should match");
  724. }
  725. - (void)testOAuthLogin_ConsentCanceled {
  726. [self OAuthLoginWithAddScopesFlow:NO
  727. authError:@"access_denied"
  728. tokenError:nil
  729. emmPasscodeInfoRequired:NO
  730. keychainError:NO
  731. restoredSignIn:NO
  732. oldAccessToken:NO
  733. modalCancel:NO];
  734. [self waitForExpectationsWithTimeout:1 handler:nil];
  735. XCTAssertTrue(_completionCalled, @"should call delegate");
  736. XCTAssertEqual(_authError.code, kGIDSignInErrorCodeCanceled);
  737. }
  738. - (void)testOAuthLogin_ModalCanceled {
  739. [self OAuthLoginWithAddScopesFlow:NO
  740. authError:nil
  741. tokenError:nil
  742. emmPasscodeInfoRequired:NO
  743. keychainError:NO
  744. restoredSignIn:NO
  745. oldAccessToken:NO
  746. modalCancel:YES];
  747. [self waitForExpectationsWithTimeout:1 handler:nil];
  748. XCTAssertTrue(_completionCalled, @"should call delegate");
  749. XCTAssertEqual(_authError.code, kGIDSignInErrorCodeCanceled);
  750. }
  751. - (void)testOAuthLogin_KeychainError {
  752. // This error is going be overidden by `-[GIDSignIn errorWithString:code:]`
  753. // We just need to fill in the error so that happens.
  754. NSError *keychainError = [NSError errorWithDomain:@"com.googleSignIn.throwAway"
  755. code:1
  756. userInfo:nil];
  757. OCMStub(
  758. [_keychainStore saveAuthSession:OCMOCK_ANY error:[OCMArg setTo:keychainError]]
  759. ).andDo(^(NSInvocation *invocation) {
  760. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  761. });
  762. [self OAuthLoginWithAddScopesFlow:NO
  763. authError:nil
  764. tokenError:nil
  765. emmPasscodeInfoRequired:NO
  766. keychainError:YES
  767. restoredSignIn:NO
  768. oldAccessToken:NO
  769. modalCancel:NO];
  770. [self waitForExpectationsWithTimeout:1 handler:nil];
  771. XCTAssertFalse(_keychainSaved, @"should save to keychain");
  772. XCTAssertTrue(_completionCalled, @"should call delegate");
  773. XCTAssertEqualObjects(_authError.domain, kGIDSignInErrorDomain);
  774. XCTAssertEqual(_authError.code, kGIDSignInErrorCodeKeychain);
  775. }
  776. - (void)testSignOut {
  777. #if TARGET_OS_IOS || !TARGET_OS_MACCATALYST
  778. // OCMStub([_authorization authState]).andReturn(_authState);
  779. #endif // TARGET_OS_IOS || !TARGET_OS_MACCATALYST
  780. OCMStub([_authorization fetcherService]).andReturn(_fetcherService);
  781. OCMStub(
  782. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  783. ).andDo(^(NSInvocation *invocation) {
  784. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  785. });
  786. // Sign in a user so that we can then sign them out.
  787. [self OAuthLoginWithAddScopesFlow:NO
  788. authError:nil
  789. tokenError:nil
  790. emmPasscodeInfoRequired:NO
  791. keychainError:NO
  792. restoredSignIn:YES
  793. oldAccessToken:NO
  794. modalCancel:NO];
  795. XCTAssertNotNil(_signIn.currentUser);
  796. [_signIn signOut];
  797. XCTAssertNil(_signIn.currentUser, @"should not have a current user");
  798. XCTAssertTrue(_keychainRemoved, @"should remove keychain");
  799. OCMVerify([_keychainStore removeAuthSessionWithError:OCMArg.anyObjectRef]);
  800. }
  801. - (void)testNotHandleWrongScheme {
  802. XCTAssertFalse([_signIn handleURL:[NSURL URLWithString:kWrongSchemeURL]],
  803. @"should not handle URL");
  804. XCTAssertFalse(_keychainSaved, @"should not save to keychain");
  805. XCTAssertFalse(_completionCalled, @"should not call delegate");
  806. }
  807. - (void)testNotHandleWrongPath {
  808. XCTAssertFalse([_signIn handleURL:[NSURL URLWithString:kWrongPathURL]], @"should not handle URL");
  809. XCTAssertFalse(_keychainSaved, @"should not save to keychain");
  810. XCTAssertFalse(_completionCalled, @"should not call delegate");
  811. }
  812. #pragma mark - Tests - disconnectWithCallback:
  813. // Verifies disconnect calls callback with no errors if access token is present.
  814. - (void)testDisconnect_accessToken {
  815. [[[_authorization expect] andReturn:_authState] authState];
  816. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  817. [[[_tokenResponse expect] andReturn:kAccessToken] accessToken];
  818. [[[_authorization expect] andReturn:_fetcherService] fetcherService];
  819. XCTestExpectation *accessTokenExpectation =
  820. [self expectationWithDescription:@"Callback called with nil error"];
  821. [_signIn disconnectWithCompletion:^(NSError * _Nullable error) {
  822. if (error == nil) {
  823. [accessTokenExpectation fulfill];
  824. }
  825. }];
  826. [self verifyAndRevokeToken:kAccessToken
  827. hasCallback:YES
  828. waitingForExpectations:@[accessTokenExpectation]];
  829. [_authorization verify];
  830. [_authState verify];
  831. [_tokenResponse verify];
  832. }
  833. // Verifies disconnect if access token is present.
  834. - (void)testDisconnectNoCallback_accessToken {
  835. [[[_authorization expect] andReturn:_authState] authState];
  836. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  837. [[[_tokenResponse expect] andReturn:kAccessToken] accessToken];
  838. [[[_authorization expect] andReturn:_fetcherService] fetcherService];
  839. [_signIn disconnectWithCompletion:nil];
  840. [self verifyAndRevokeToken:kAccessToken hasCallback:NO waitingForExpectations:@[]];
  841. [_authorization verify];
  842. [_authState verify];
  843. [_tokenResponse verify];
  844. }
  845. // Verifies disconnect calls callback with no errors if refresh token is present.
  846. - (void)testDisconnect_refreshToken {
  847. [[[_authorization expect] andReturn:_authState] authState];
  848. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  849. [[[_tokenResponse expect] andReturn:nil] accessToken];
  850. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  851. [[[_tokenResponse expect] andReturn:kRefreshToken] refreshToken];
  852. [[[_authorization expect] andReturn:_fetcherService] fetcherService];
  853. XCTestExpectation *refreshTokenExpectation =
  854. [self expectationWithDescription:@"Callback called with nil error"];
  855. [_signIn disconnectWithCompletion:^(NSError * _Nullable error) {
  856. if (error == nil) {
  857. [refreshTokenExpectation fulfill];
  858. }
  859. }];
  860. [self verifyAndRevokeToken:kRefreshToken
  861. hasCallback:YES
  862. waitingForExpectations:@[refreshTokenExpectation]];
  863. [_authorization verify];
  864. [_authState verify];
  865. [_tokenResponse verify];
  866. }
  867. // Verifies disconnect errors are passed along to the callback.
  868. - (void)testDisconnect_errors {
  869. [[[_authorization expect] andReturn:_authState] authState];
  870. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  871. [[[_tokenResponse expect] andReturn:kAccessToken] accessToken];
  872. [[[_authorization expect] andReturn:_fetcherService] fetcherService];
  873. XCTestExpectation *errorExpectation =
  874. [self expectationWithDescription:@"Callback called with an error"];
  875. [_signIn disconnectWithCompletion:^(NSError * _Nullable error) {
  876. if (error != nil) {
  877. [errorExpectation fulfill];
  878. }
  879. }];
  880. XCTAssertTrue([self isFetcherStarted], @"should start fetching");
  881. // Emulate result back from server.
  882. NSError *error = [self error];
  883. [self didFetch:nil error:error];
  884. [self waitForExpectations:@[errorExpectation] timeout:1];
  885. [_authorization verify];
  886. [_authState verify];
  887. [_tokenResponse verify];
  888. }
  889. // Verifies disconnect with errors
  890. - (void)testDisconnectNoCallback_errors {
  891. [[[_authorization expect] andReturn:_authState] authState];
  892. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  893. [[[_tokenResponse expect] andReturn:kAccessToken] accessToken];
  894. [[[_authorization expect] andReturn:_fetcherService] fetcherService];
  895. [_signIn disconnectWithCompletion:nil];
  896. XCTAssertTrue([self isFetcherStarted], @"should start fetching");
  897. // Emulate result back from server.
  898. NSError *error = [self error];
  899. [self didFetch:nil error:error];
  900. [_authorization verify];
  901. [_authState verify];
  902. [_tokenResponse verify];
  903. }
  904. // Verifies disconnect calls callback with no errors and clears keychain if no tokens are present.
  905. - (void)testDisconnect_noTokens {
  906. [[[_authorization expect] andReturn:_authState] authState];
  907. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  908. [[[_tokenResponse expect] andReturn:nil] accessToken];
  909. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  910. [[[_tokenResponse expect] andReturn:nil] refreshToken];
  911. XCTestExpectation *noTokensExpectation =
  912. [self expectationWithDescription:@"Callback called with nil error"];
  913. [_signIn disconnectWithCompletion:^(NSError * _Nullable error) {
  914. if (error == nil) {
  915. [noTokensExpectation fulfill];
  916. }
  917. }];
  918. [self waitForExpectations:@[noTokensExpectation] timeout:1];
  919. XCTAssertFalse([self isFetcherStarted], @"should not fetch");
  920. XCTAssertTrue(_keychainRemoved, @"keychain should be removed");
  921. [_authorization verify];
  922. [_authState verify];
  923. [_tokenResponse verify];
  924. }
  925. // Verifies disconnect clears keychain if no tokens are present.
  926. - (void)testDisconnectNoCallback_noTokens {
  927. [[[_authorization expect] andReturn:_authState] authState];
  928. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  929. [[[_tokenResponse expect] andReturn:nil] accessToken];
  930. [[[_authState expect] andReturn:_tokenResponse] lastTokenResponse];
  931. [[[_tokenResponse expect] andReturn:nil] refreshToken];
  932. [_signIn disconnectWithCompletion:nil];
  933. XCTAssertFalse([self isFetcherStarted], @"should not fetch");
  934. XCTAssertTrue(_keychainRemoved, @"keychain should be removed");
  935. [_authorization verify];
  936. [_authState verify];
  937. [_tokenResponse verify];
  938. }
  939. - (void)testPresentingViewControllerException {
  940. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  941. _presentingViewController = nil;
  942. #elif TARGET_OS_OSX
  943. _presentingWindow = nil;
  944. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  945. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  946. XCTAssertThrows([_signIn signInWithPresentingViewController:_presentingViewController
  947. #elif TARGET_OS_OSX
  948. XCTAssertThrows([_signIn signInWithPresentingWindow:_presentingWindow
  949. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  950. hint:_hint
  951. completion:_completion]);
  952. }
  953. - (void)testClientIDMissingException {
  954. #pragma GCC diagnostic push
  955. #pragma GCC diagnostic ignored "-Wnonnull"
  956. _signIn.configuration = [[GIDConfiguration alloc] initWithClientID:nil];
  957. #pragma GCC diagnostic pop
  958. BOOL threw = NO;
  959. @try {
  960. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  961. [_signIn signInWithPresentingViewController:_presentingViewController
  962. #elif TARGET_OS_OSX
  963. [_signIn signInWithPresentingWindow:_presentingWindow
  964. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  965. completion:nil];
  966. } @catch (NSException *exception) {
  967. threw = YES;
  968. XCTAssertEqualObjects(exception.description,
  969. @"You must specify |clientID| in |GIDConfiguration|");
  970. } @finally {
  971. }
  972. XCTAssert(threw);
  973. }
  974. - (void)testSchemesNotSupportedException {
  975. [_fakeMainBundle fakeMissingAllSchemes];
  976. BOOL threw = NO;
  977. @try {
  978. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  979. [_signIn signInWithPresentingViewController:_presentingViewController
  980. #elif TARGET_OS_OSX
  981. [_signIn signInWithPresentingWindow:_presentingWindow
  982. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  983. hint:_hint
  984. completion:_completion];
  985. } @catch (NSException *exception) {
  986. threw = YES;
  987. XCTAssertEqualObjects(exception.description,
  988. @"Your app is missing support for the following URL schemes: "
  989. "fakeclientid");
  990. } @finally {
  991. }
  992. XCTAssert(threw);
  993. }
  994. #pragma mark - Restarting Authentication Tests
  995. // Verifies that URL is not handled if there is no pending sign-in
  996. - (void)testRequiringPendingSignIn {
  997. BOOL result = [_signIn handleURL:[NSURL URLWithString:kEMMRestartAuthURL]];
  998. XCTAssertFalse(result);
  999. }
  1000. #pragma mark - EMM tests
  1001. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  1002. - (void)testEmmSupportRequestParameters {
  1003. OCMStub(
  1004. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  1005. ).andDo(^(NSInvocation *invocation) {
  1006. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  1007. });
  1008. [self OAuthLoginWithAddScopesFlow:NO
  1009. authError:nil
  1010. tokenError:nil
  1011. emmPasscodeInfoRequired:NO
  1012. keychainError:NO
  1013. restoredSignIn:NO
  1014. oldAccessToken:NO
  1015. modalCancel:NO];
  1016. NSString *systemName = [UIDevice currentDevice].systemName;
  1017. if ([systemName isEqualToString:@"iPhone OS"]) {
  1018. systemName = @"iOS";
  1019. }
  1020. NSString *expectedOSVersion = [NSString stringWithFormat:@"%@ %@",
  1021. systemName, [UIDevice currentDevice].systemVersion];
  1022. NSDictionary<NSString *, NSObject *> *authParams =
  1023. _savedAuthorizationRequest.additionalParameters;
  1024. NSDictionary<NSString *, NSString *> *tokenParams = _savedTokenRequest.additionalParameters;
  1025. if (_isEligibleForEMM) {
  1026. XCTAssertEqualObjects(authParams[@"emm_support"], kEMMSupport,
  1027. @"EMM support should match in auth request");
  1028. XCTAssertEqualObjects(authParams[@"device_os"], expectedOSVersion,
  1029. @"OS version should match in auth request");
  1030. XCTAssertEqualObjects(tokenParams[@"emm_support"], kEMMSupport,
  1031. @"EMM support should match in token request");
  1032. XCTAssertEqualObjects(tokenParams[@"device_os"],
  1033. expectedOSVersion,
  1034. @"OS version should match in token request");
  1035. XCTAssertNil(tokenParams[@"emm_passcode_info"],
  1036. @"no passcode info should be in token request");
  1037. } else {
  1038. XCTAssertNil(authParams[@"emm_support"],
  1039. @"EMM support should not be in auth request for unsupported OS");
  1040. XCTAssertNil(authParams[@"device_os"],
  1041. @"OS version should not be in auth request for unsupported OS");
  1042. XCTAssertNil(tokenParams[@"emm_support"],
  1043. @"EMM support should not be in token request for unsupported OS");
  1044. XCTAssertNil(tokenParams[@"device_os"],
  1045. @"OS version should not be in token request for unsupported OS");
  1046. XCTAssertNil(tokenParams[@"emm_passcode_info"],
  1047. @"passcode info should not be in token request for unsupported OS");
  1048. }
  1049. }
  1050. - (void)testEmmPasscodeInfo {
  1051. OCMStub(
  1052. [_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
  1053. ).andDo(^(NSInvocation *invocation) {
  1054. self->_keychainSaved = self->_saveAuthorizationReturnValue;
  1055. });
  1056. [self OAuthLoginWithAddScopesFlow:NO
  1057. authError:nil
  1058. tokenError:nil
  1059. emmPasscodeInfoRequired:YES
  1060. keychainError:NO
  1061. restoredSignIn:NO
  1062. oldAccessToken:NO
  1063. modalCancel:NO];
  1064. NSDictionary<NSString *, NSString *> *tokenParams = _savedTokenRequest.additionalParameters;
  1065. if (_isEligibleForEMM) {
  1066. XCTAssertNotNil(tokenParams[@"emm_passcode_info"],
  1067. @"passcode info should be in token request");
  1068. } else {
  1069. XCTAssertNil(tokenParams[@"emm_passcode_info"],
  1070. @"passcode info should not be in token request for unsupported OS");
  1071. }
  1072. }
  1073. - (void)testAuthEndpointEMMError {
  1074. if (!_isEligibleForEMM) {
  1075. return;
  1076. }
  1077. id mockEMMErrorHandler = OCMStrictClassMock([GIDEMMErrorHandler class]);
  1078. [[[mockEMMErrorHandler stub] andReturn:mockEMMErrorHandler] sharedInstance];
  1079. __block void (^completion)(void);
  1080. NSDictionary<NSString *, NSString *> *callbackParams = @{ @"error" : @"EMM Specific Error" };
  1081. [[[mockEMMErrorHandler expect] andReturnValue:@YES]
  1082. handleErrorFromResponse:callbackParams completion:SAVE_TO_ARG_BLOCK(completion)];
  1083. [self OAuthLoginWithAddScopesFlow:NO
  1084. authError:callbackParams[@"error"]
  1085. tokenError:nil
  1086. emmPasscodeInfoRequired:NO
  1087. keychainError:NO
  1088. restoredSignIn:NO
  1089. oldAccessToken:NO
  1090. modalCancel:NO];
  1091. [mockEMMErrorHandler verify];
  1092. [mockEMMErrorHandler stopMocking];
  1093. completion();
  1094. [self waitForExpectationsWithTimeout:1 handler:nil];
  1095. XCTAssertFalse(_keychainSaved, @"should not save to keychain");
  1096. XCTAssertTrue(_completionCalled, @"should call delegate");
  1097. XCTAssertNotNil(_authError, @"should have error");
  1098. XCTAssertEqualObjects(_authError.domain, kGIDSignInErrorDomain);
  1099. XCTAssertEqual(_authError.code, kGIDSignInErrorCodeEMM);
  1100. XCTAssertNil(_signIn.currentUser, @"should not have current user");
  1101. }
  1102. - (void)testTokenEndpointEMMError {
  1103. if (!_isEligibleForEMM) {
  1104. return;
  1105. }
  1106. __block void (^completion)(NSError *);
  1107. NSDictionary *errorJSON = @{ @"error" : @"EMM Specific Error" };
  1108. NSError *emmError = [NSError errorWithDomain:@"anydomain"
  1109. code:12345
  1110. userInfo:@{ OIDOAuthErrorFieldError : errorJSON }];
  1111. id emmSupport = OCMStrictClassMock([GIDEMMSupport class]);
  1112. [[emmSupport expect] handleTokenFetchEMMError:emmError
  1113. completion:SAVE_TO_ARG_BLOCK(completion)];
  1114. [self OAuthLoginWithAddScopesFlow:NO
  1115. authError:nil
  1116. tokenError:emmError
  1117. emmPasscodeInfoRequired:NO
  1118. keychainError:NO
  1119. restoredSignIn:NO
  1120. oldAccessToken:NO
  1121. modalCancel:NO];
  1122. NSError *handledError = [NSError errorWithDomain:kGIDSignInErrorDomain
  1123. code:kGIDSignInErrorCodeEMM
  1124. userInfo:emmError.userInfo];
  1125. completion(handledError);
  1126. [self waitForExpectationsWithTimeout:1 handler:nil];
  1127. [emmSupport verify];
  1128. XCTAssertFalse(_keychainSaved, @"should not save to keychain");
  1129. XCTAssertTrue(_completionCalled, @"should call delegate");
  1130. XCTAssertNotNil(_authError, @"should have error");
  1131. XCTAssertEqualObjects(_authError.domain, kGIDSignInErrorDomain);
  1132. XCTAssertEqual(_authError.code, kGIDSignInErrorCodeEMM);
  1133. XCTAssertNil(_signIn.currentUser, @"should not have current user");
  1134. }
  1135. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  1136. #pragma mark - Helpers
  1137. // Whether or not a fetcher has been started.
  1138. - (BOOL)isFetcherStarted {
  1139. NSUInteger count = _fetcherService.fetchers.count;
  1140. XCTAssertTrue(count <= 1, @"Only one fetcher is supported");
  1141. return !!count;
  1142. }
  1143. // Gets the URL being fetched.
  1144. - (NSURL *)fetchedURL {
  1145. return [_fetcherService.fetchers[0] requestURL];
  1146. }
  1147. // Emulates server returning the data as in JSON.
  1148. - (void)didFetch:(id)dataObject error:(NSError *)error {
  1149. NSData *data = nil;
  1150. if (dataObject) {
  1151. NSError *jsonError = nil;
  1152. data = [NSJSONSerialization dataWithJSONObject:dataObject
  1153. options:0
  1154. error:&jsonError];
  1155. XCTAssertNil(jsonError, @"must provide valid data");
  1156. }
  1157. [_fetcherService.fetchers[0] didFinishWithData:data error:error];
  1158. }
  1159. - (NSError *)error {
  1160. return [NSError errorWithDomain:kErrorDomain code:kErrorCode userInfo:nil];
  1161. }
  1162. // Verifies a fetcher has started for revoking token and emulates a server response.
  1163. - (void)verifyAndRevokeToken:(NSString *)token
  1164. hasCallback:(BOOL)hasCallback
  1165. waitingForExpectations:(NSArray<XCTestExpectation *> *)expectations {
  1166. XCTAssertTrue([self isFetcherStarted], @"should start fetching");
  1167. NSURL *url = [self fetchedURL];
  1168. XCTAssertEqualObjects([url scheme], @"https", @"scheme must match");
  1169. XCTAssertEqualObjects([url host], @"accounts.google.com", @"host must match");
  1170. XCTAssertEqualObjects([url path], @"/o/oauth2/revoke", @"path must match");
  1171. OIDURLQueryComponent *queryComponent = [[OIDURLQueryComponent alloc] initWithURL:url];
  1172. NSDictionary<NSString *, NSObject<NSCopying> *> *params = queryComponent.dictionaryValue;
  1173. XCTAssertEqualObjects([params valueForKey:@"token"], token,
  1174. @"token parameter should match");
  1175. XCTAssertEqualObjects([params valueForKey:kSDKVersionLoggingParameter], GIDVersion(),
  1176. @"SDK version logging parameter should match");
  1177. XCTAssertEqualObjects([params valueForKey:kEnvironmentLoggingParameter], GIDEnvironment(),
  1178. @"Environment logging parameter should match");
  1179. // Emulate result back from server.
  1180. [self didFetch:nil error:nil];
  1181. XCTAssertTrue(_keychainRemoved, @"should clear saved keychain name");
  1182. if (hasCallback) {
  1183. [self waitForExpectations:expectations timeout:1];
  1184. }
  1185. }
  1186. - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow
  1187. authError:(NSString *)authError
  1188. tokenError:(NSError *)tokenError
  1189. emmPasscodeInfoRequired:(BOOL)emmPasscodeInfoRequired
  1190. keychainError:(BOOL)keychainError
  1191. restoredSignIn:(BOOL)restoredSignIn
  1192. oldAccessToken:(BOOL)oldAccessToken
  1193. modalCancel:(BOOL)modalCancel {
  1194. [self OAuthLoginWithAddScopesFlow:addScopesFlow
  1195. authError:authError
  1196. tokenError:tokenError
  1197. emmPasscodeInfoRequired:emmPasscodeInfoRequired
  1198. keychainError:keychainError
  1199. restoredSignIn:restoredSignIn
  1200. oldAccessToken:oldAccessToken
  1201. modalCancel:modalCancel
  1202. useAdditionalScopes:NO
  1203. additionalScopes:nil
  1204. manualNonce:nil];
  1205. }
  1206. // The authorization flow with parameters to control which branches to take.
  1207. - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow
  1208. authError:(NSString *)authError
  1209. tokenError:(NSError *)tokenError
  1210. emmPasscodeInfoRequired:(BOOL)emmPasscodeInfoRequired
  1211. keychainError:(BOOL)keychainError
  1212. restoredSignIn:(BOOL)restoredSignIn
  1213. oldAccessToken:(BOOL)oldAccessToken
  1214. modalCancel:(BOOL)modalCancel
  1215. useAdditionalScopes:(BOOL)useAdditionalScopes
  1216. additionalScopes:(NSArray *)additionalScopes
  1217. manualNonce:(NSString *)nonce {
  1218. if (restoredSignIn) {
  1219. // clearAndAuthenticateWithOptions
  1220. [[[_authorization expect] andReturn:_authState] authState];
  1221. BOOL isAuthorized = restoredSignIn;
  1222. [[[_authState expect] andReturnValue:[NSNumber numberWithBool:isAuthorized]] isAuthorized];
  1223. }
  1224. NSDictionary<NSString *, NSString *> *additionalParameters = emmPasscodeInfoRequired ?
  1225. @{ @"emm_passcode_info_required" : @"1" } : nil;
  1226. OIDAuthorizationResponse *authResponse =
  1227. [OIDAuthorizationResponse testInstanceWithAdditionalParameters:additionalParameters
  1228. nonce:nonce
  1229. errorString:authError];
  1230. OIDTokenResponse *tokenResponse =
  1231. [OIDTokenResponse testInstanceWithIDToken:[OIDTokenResponse fatIDToken]
  1232. accessToken:restoredSignIn ? kAccessToken : nil
  1233. expiresIn:oldAccessToken ? @(300) : nil
  1234. refreshToken:kRefreshToken
  1235. tokenRequest:nil];
  1236. OIDTokenRequest *tokenRequest = [[OIDTokenRequest alloc]
  1237. initWithConfiguration:authResponse.request.configuration
  1238. grantType:OIDGrantTypeRefreshToken
  1239. authorizationCode:nil
  1240. redirectURL:nil
  1241. clientID:authResponse.request.clientID
  1242. clientSecret:authResponse.request.clientSecret
  1243. scope:nil
  1244. refreshToken:kRefreshToken
  1245. codeVerifier:nil
  1246. additionalParameters:tokenResponse.request.additionalParameters];
  1247. if (restoredSignIn) {
  1248. // maybeFetchToken
  1249. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  1250. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  1251. if (oldAccessToken) {
  1252. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  1253. // Corresponds to EMM support
  1254. [[[_authState expect] andReturn:authResponse] lastAuthorizationResponse];
  1255. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  1256. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  1257. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  1258. [[[_authState expect] andReturn:tokenRequest]
  1259. tokenRefreshRequestWithAdditionalParameters:[OCMArg any]];
  1260. }
  1261. } else {
  1262. XCTestExpectation *newAccessTokenExpectation =
  1263. [self expectationWithDescription:@"Callback called"];
  1264. GIDSignInCompletion completion = ^(GIDSignInResult *_Nullable signInResult,
  1265. NSError * _Nullable error) {
  1266. [newAccessTokenExpectation fulfill];
  1267. if (signInResult) {
  1268. XCTAssertEqualObjects(signInResult.serverAuthCode, kServerAuthCode);
  1269. } else {
  1270. XCTAssertNotNil(error, @"Should have an error if the signInResult is nil");
  1271. }
  1272. XCTAssertFalse(self->_completionCalled, @"callback already called");
  1273. self->_completionCalled = YES;
  1274. self->_authError = error;
  1275. };
  1276. if (addScopesFlow) {
  1277. [_signIn addScopes:@[kNewScope]
  1278. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  1279. presentingViewController:_presentingViewController
  1280. #elif TARGET_OS_OSX
  1281. presentingWindow:_presentingWindow
  1282. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  1283. completion:completion];
  1284. } else {
  1285. if (useAdditionalScopes) {
  1286. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  1287. [_signIn signInWithPresentingViewController:_presentingViewController
  1288. #elif TARGET_OS_OSX
  1289. [_signIn signInWithPresentingWindow:_presentingWindow
  1290. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  1291. hint:_hint
  1292. additionalScopes:additionalScopes
  1293. completion:completion];
  1294. } else {
  1295. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  1296. [_signIn signInWithPresentingViewController:_presentingViewController
  1297. #elif TARGET_OS_OSX
  1298. [_signIn signInWithPresentingWindow:_presentingWindow
  1299. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  1300. hint:_hint
  1301. additionalScopes:nil
  1302. nonce:nonce
  1303. completion:completion];
  1304. }
  1305. }
  1306. [_authorization verify];
  1307. [_authState verify];
  1308. XCTAssertNotNil(_savedAuthorizationRequest);
  1309. NSDictionary<NSString *, NSObject *> *params = _savedAuthorizationRequest.additionalParameters;
  1310. XCTAssertEqualObjects(params[@"include_granted_scopes"], @"true");
  1311. XCTAssertEqualObjects(params[kSDKVersionLoggingParameter], GIDVersion());
  1312. XCTAssertEqualObjects(params[kEnvironmentLoggingParameter], GIDEnvironment());
  1313. XCTAssertNotNil(_savedAuthorizationCallback);
  1314. #if TARGET_OS_IOS || TARGET_OS_MACCATALYST
  1315. XCTAssertEqual(_savedPresentingViewController, _presentingViewController);
  1316. #elif TARGET_OS_OSX
  1317. XCTAssertEqual(_savedPresentingWindow, _presentingWindow);
  1318. #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
  1319. // maybeFetchToken
  1320. if (!(authError || modalCancel)) {
  1321. [[[_authState expect] andReturn:nil] lastTokenResponse];
  1322. #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  1323. // Corresponds to EMM support
  1324. [[[_authState expect] andReturn:authResponse] lastAuthorizationResponse];
  1325. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST
  1326. [[[_authState expect] andReturn:nil] lastTokenResponse];
  1327. [[[_authState expect] andReturn:authResponse] lastAuthorizationResponse];
  1328. [[[_authState expect] andReturn:authResponse] lastAuthorizationResponse];
  1329. }
  1330. // Simulate auth endpoint response
  1331. if (modalCancel) {
  1332. NSError *error = [NSError errorWithDomain:OIDGeneralErrorDomain
  1333. code:OIDErrorCodeUserCanceledAuthorizationFlow
  1334. userInfo:nil];
  1335. _savedAuthorizationCallback(nil, error);
  1336. } else {
  1337. _savedAuthorizationCallback(authResponse, nil);
  1338. }
  1339. if (authError || modalCancel) {
  1340. return;
  1341. }
  1342. [_authState verify];
  1343. }
  1344. if (restoredSignIn && oldAccessToken) {
  1345. XCTestExpectation *callbackShouldBeCalledExpectation =
  1346. [self expectationWithDescription:@"Callback should be called"];
  1347. [_signIn restorePreviousSignInWithCompletion:^(GIDGoogleUser * _Nullable user,
  1348. NSError * _Nullable error) {
  1349. [callbackShouldBeCalledExpectation fulfill];
  1350. XCTAssertNil(error, @"should have no error");
  1351. }];
  1352. }
  1353. if (!restoredSignIn || (restoredSignIn && oldAccessToken)) {
  1354. XCTAssertNotNil(_savedTokenRequest);
  1355. XCTAssertNotNil(_savedTokenCallback);
  1356. // OIDTokenCallback
  1357. if (tokenError) {
  1358. [[_authState expect] updateWithTokenResponse:nil error:tokenError];
  1359. } else {
  1360. [[_authState expect] updateWithTokenResponse:[OCMArg any] error:nil];
  1361. }
  1362. }
  1363. if (tokenError) {
  1364. _savedTokenCallback(nil, tokenError);
  1365. return;
  1366. }
  1367. // DecodeIdTokenCallback
  1368. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  1369. // SaveAuthCallback
  1370. __block OIDAuthState *authState;
  1371. __block OIDTokenResponse *updatedTokenResponse;
  1372. __block OIDAuthorizationResponse *updatedAuthorizationResponse;
  1373. __block GIDProfileData *profileData;
  1374. if (keychainError) {
  1375. _saveAuthorizationReturnValue = NO;
  1376. } else {
  1377. if (addScopesFlow) {
  1378. [[[_authState expect] andReturn:authResponse] lastAuthorizationResponse];
  1379. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  1380. [[_user expect] updateWithTokenResponse:SAVE_TO_ARG_BLOCK(updatedTokenResponse)
  1381. authorizationResponse:SAVE_TO_ARG_BLOCK(updatedAuthorizationResponse)
  1382. profileData:SAVE_TO_ARG_BLOCK(profileData)];
  1383. } else {
  1384. [[[_user expect] andReturn:_user] alloc];
  1385. (void)[[[_user expect] andReturn:_user] initWithAuthState:SAVE_TO_ARG_BLOCK(authState)
  1386. profileData:SAVE_TO_ARG_BLOCK(profileData)];
  1387. }
  1388. }
  1389. // CompletionCallback - mock server auth code parsing
  1390. if (!keychainError) {
  1391. [[[_authState expect] andReturn:tokenResponse] lastTokenResponse];
  1392. }
  1393. if (restoredSignIn && !oldAccessToken) {
  1394. XCTestExpectation *restoredSignInExpectation = [self expectationWithDescription:@"Callback should be called"];
  1395. [_signIn restorePreviousSignInWithCompletion:^(GIDGoogleUser * _Nullable user,
  1396. NSError * _Nullable error) {
  1397. [restoredSignInExpectation fulfill];
  1398. XCTAssertNil(error, @"should have no error");
  1399. }];
  1400. } else {
  1401. // Simulate token endpoint response.
  1402. _savedTokenCallback(tokenResponse, nil);
  1403. }
  1404. if (keychainError) {
  1405. return;
  1406. }
  1407. [self waitForExpectationsWithTimeout:1 handler:nil];
  1408. [_authState verify];
  1409. XCTAssertTrue(_keychainSaved, @"should save to keychain");
  1410. if (addScopesFlow) {
  1411. XCTAssertNotNil(updatedTokenResponse);
  1412. XCTAssertNotNil(updatedAuthorizationResponse);
  1413. } else {
  1414. XCTAssertNotNil(authState);
  1415. }
  1416. // Check fat ID token decoding
  1417. XCTAssertEqualObjects(profileData.name, kFatName);
  1418. XCTAssertEqualObjects(profileData.givenName, kFatGivenName);
  1419. XCTAssertEqualObjects(profileData.familyName, kFatFamilyName);
  1420. XCTAssertTrue(profileData.hasImage);
  1421. // If attempt to authenticate again, will reuse existing auth object.
  1422. _completionCalled = NO;
  1423. _keychainRemoved = NO;
  1424. _keychainSaved = NO;
  1425. _authError = nil;
  1426. __block GIDGoogleUserCompletion completion;
  1427. [[_user expect] refreshTokensIfNeededWithCompletion:SAVE_TO_ARG_BLOCK(completion)];
  1428. XCTestExpectation *restorePreviousSignInExpectation =
  1429. [self expectationWithDescription:@"Callback should be called"];
  1430. [_signIn restorePreviousSignInWithCompletion:^(GIDGoogleUser * _Nullable user,
  1431. NSError * _Nullable error) {
  1432. [restorePreviousSignInExpectation fulfill];
  1433. XCTAssertNil(error, @"should have no error");
  1434. }];
  1435. completion(_user, nil);
  1436. [self waitForExpectationsWithTimeout:1 handler:nil];
  1437. XCTAssertFalse(_keychainRemoved, @"should not remove keychain");
  1438. XCTAssertFalse(_keychainSaved, @"should not save to keychain again");
  1439. if (restoredSignIn) {
  1440. // Ignore the return value
  1441. OCMVerify((void)[_keychainStore retrieveAuthSessionWithError:OCMArg.anyObjectRef]);
  1442. OCMVerify([_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]);
  1443. }
  1444. }
  1445. @end