GIDSignInTest.m 55 KB

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