GIDSignInTest.m 61 KB

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