GIDSignInTest.m 56 KB

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