GIDSignInTest.m 53 KB

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