GIDSignInTest.m 53 KB

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