GIDSignInTest.m 55 KB

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