GIDSignInTest.m 55 KB

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