GIDSignInTest.m 55 KB

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