GIDSignInTest.m 55 KB

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