FIROAuthProviderTests.m 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <OCMock/OCMock.h>
  17. #import <XCTest/XCTest.h>
  18. #import "FIRApp.h"
  19. #import "FIRAuthBackend.h"
  20. #import "FIRAuthErrorUtils.h"
  21. #import "FIRAuthErrors.h"
  22. #import "FIRAuthGlobalWorkQueue.h"
  23. #import "FIRAuthRequestConfiguration.h"
  24. #import "FIRAuthUIDelegate.h"
  25. #import "FIRAuthURLPresenter.h"
  26. #import "FIRAuthWebUtils.h"
  27. #import "FIRAuth_Internal.h"
  28. #import "FIRGetProjectConfigRequest.h"
  29. #import "FIRGetProjectConfigResponse.h"
  30. #import "FIROAuthProvider.h"
  31. #import "FIROptions.h"
  32. #import "OAuth/FIROAuthCredential_Internal.h"
  33. #import "OCMStubRecorder+FIRAuthUnitTests.h"
  34. /** @var kExpectationTimeout
  35. @brief The maximum time waiting for expectations to fulfill.
  36. */
  37. static const NSTimeInterval kExpectationTimeout = 1;
  38. /** @var kFakeAuthorizedDomain
  39. @brief A fake authorized domain for the app.
  40. */
  41. static NSString *const kFakeAuthorizedDomain = @"test.firebaseapp.com";
  42. /** @var kFakeBundleID
  43. @brief A fake bundle ID.
  44. */
  45. static NSString *const kFakeBundleID = @"com.firebaseapp.example";
  46. /** @var kFakeAccessToken
  47. @brief A fake access token for testing.
  48. */
  49. static NSString *const kFakeAccessToken = @"fakeAccessToken";
  50. /** @var kFakeIDToken
  51. @brief A fake ID token for testing.
  52. */
  53. static NSString *const kFakeIDToken = @"fakeIDToken";
  54. /** @var kFakeProviderID
  55. @brief A fake provider ID for testing.
  56. */
  57. static NSString *const kFakeProviderID = @"fakeProviderID";
  58. /** @var kFakeAPIKey
  59. @brief A fake API key.
  60. */
  61. static NSString *const kFakeAPIKey = @"asdfghjkl";
  62. /** @var kFakeClientID
  63. @brief A fake client ID.
  64. */
  65. static NSString *const kFakeClientID = @"123456.apps.googleusercontent.com";
  66. /** @var kFakeReverseClientID
  67. @brief The dot-reversed version of the fake client ID.
  68. */
  69. static NSString *const kFakeReverseClientID = @"com.googleusercontent.apps.123456";
  70. /** @var kFakeOAuthResponseURL
  71. @brief A fake OAuth response URL used in test.
  72. */
  73. static NSString *const kFakeOAuthResponseURL = @"fakeOAuthResponseURL";
  74. /** @var kFakeRedirectURLResponseURL
  75. @brief A fake callback URL containing a fake response URL.
  76. */
  77. static NSString *const kFakeRedirectURLResponseURL =
  78. @"com.googleusercontent.apps.123456://firebase"
  79. "auth/"
  80. "link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcallback%3FauthType"
  81. "%3DsignInWithRedirect%26link%3D";
  82. /** @var kFakeRedirectURLBaseErrorString
  83. @brief The base for a fake redirect URL string that contains an error.
  84. */
  85. static NSString *const kFakeRedirectURLBaseErrorString =
  86. @"com.googleusercontent.apps.123456://fire"
  87. "baseauth/link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcallback%3f";
  88. /** @var kNetworkRequestFailedErrorString
  89. @brief The error message returned if a network request failure occurs within the web context.
  90. */
  91. static NSString *const kNetworkRequestFailedErrorString =
  92. @"firebaseError%3D%257B%2522code%2"
  93. "522%253A%2522auth%252Fnetwork-request-failed%2522%252C%2522message%2522%253A%2522The%"
  94. "2520netwo"
  95. "rk%2520request%2520failed%2520.%2522%257D%26authType%3DsignInWithRedirect";
  96. /** @var kInvalidClientIDString
  97. @brief The error message returned if the client ID used is invalid.
  98. */
  99. static NSString *const kInvalidClientIDString =
  100. @"firebaseError%3D%257B%2522code%2522%253A%2522auth"
  101. "%252Finvalid-oauth-client-id%2522%252C%2522message%2522%253A%2522The%2520OAuth%2520client%"
  102. "2520"
  103. "ID%2520provided%2520is%2520either%2520invalid%2520or%2520does%2520not%2520match%2520the%"
  104. "2520sp"
  105. "ecified%2520API%2520key.%2522%257D%26authType%3DsignInWithRedirect";
  106. /** @var kInternalErrorString
  107. @brief The error message returned if there is an internal error within the web context.
  108. */
  109. static NSString *const kInternalErrorString =
  110. @"firebaseError%3D%257B%2522code%2522%253"
  111. "A%2522auth%252Finternal-error%2522%252C%2522message%2522%253A%2522Internal%2520error%2520.%"
  112. "252"
  113. "2%257D%26authType%3DsignInWithRedirect";
  114. /** @var kUnknownErrorString
  115. @brief The error message returned if an unknown error is returned from the web context.
  116. */
  117. static NSString *const kUnknownErrorString =
  118. @"firebaseError%3D%257B%2522code%2522%253A%2522auth%2"
  119. "52Funknown-error-id%2522%252C%2522message%2522%253A%2522The%2520OAuth%2520client%2520ID%"
  120. "2520pr"
  121. "ovided%2520is%2520either%2520invalid%2520or%2520does%2520not%2520match%2520the%2520specified%"
  122. "2"
  123. "520API%2520key.%2522%257D%26authType%3DsignInWithRedirect";
  124. @interface FIROAuthProviderTests : XCTestCase
  125. @end
  126. @implementation FIROAuthProviderTests {
  127. /** @var _mockBackend
  128. @brief The mock @c FIRAuthBackendImplementation.
  129. */
  130. id _mockBackend;
  131. /** @var _provider
  132. @brief The @c FIROAuthProvider instance under test.
  133. */
  134. FIROAuthProvider *_provider;
  135. /** @var _mockAuth
  136. @brief The mock @c FIRAuth instance associated with @c _provider.
  137. */
  138. id _mockAuth;
  139. /** @var _mockURLPresenter
  140. @brief The mock @c FIRAuthURLPresenter instance associated with @c _mockAuth.
  141. */
  142. id _mockURLPresenter;
  143. /** @var _mockApp
  144. @brief The mock @c FIRApp instance associated with @c _mockAuth.
  145. */
  146. id _mockApp;
  147. }
  148. - (void)setUp {
  149. [super setUp];
  150. _mockBackend = OCMProtocolMock(@protocol(FIRAuthBackendImplementation));
  151. [FIRAuthBackend setBackendImplementation:_mockBackend];
  152. _mockAuth = OCMClassMock([FIRAuth class]);
  153. _mockApp = OCMClassMock([FIRApp class]);
  154. OCMStub([_mockAuth app]).andReturn(_mockApp);
  155. id mockOptions = OCMClassMock([FIROptions class]);
  156. OCMStub([(FIRApp *)_mockApp options]).andReturn(mockOptions);
  157. OCMStub([mockOptions clientID]).andReturn(kFakeClientID);
  158. _mockURLPresenter = OCMClassMock([FIRAuthURLPresenter class]);
  159. OCMStub([_mockAuth authURLPresenter]).andReturn(_mockURLPresenter);
  160. id mockRequestConfiguration = OCMClassMock([FIRAuthRequestConfiguration class]);
  161. OCMStub([_mockAuth requestConfiguration]).andReturn(mockRequestConfiguration);
  162. OCMStub([mockRequestConfiguration APIKey]).andReturn(kFakeAPIKey);
  163. _provider = [FIROAuthProvider providerWithProviderID:@"fake id" auth:_mockAuth];
  164. }
  165. /** @fn testObtainingOAuthCredentialNoIDToken
  166. @brief Tests the correct creation of an OAuthCredential without an IDToken.
  167. */
  168. - (void)testObtainingOAuthCredentialNoIDToken {
  169. FIRAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:kFakeProviderID
  170. accessToken:kFakeAccessToken];
  171. XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
  172. FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
  173. XCTAssertEqualObjects(OAuthCredential.accessToken, kFakeAccessToken);
  174. XCTAssertEqualObjects(OAuthCredential.provider, kFakeProviderID);
  175. XCTAssertNil(OAuthCredential.IDToken);
  176. }
  177. /** @fn testObtainingOAuthCredentialWithIDToken
  178. @brief Tests the correct creation of an OAuthCredential with an IDToken
  179. */
  180. - (void)testObtainingOAuthCredentialWithIDToken {
  181. FIRAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:kFakeProviderID
  182. IDToken:kFakeIDToken
  183. accessToken:kFakeAccessToken];
  184. XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
  185. FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
  186. XCTAssertEqualObjects(OAuthCredential.accessToken, kFakeAccessToken);
  187. XCTAssertEqualObjects(OAuthCredential.provider, kFakeProviderID);
  188. XCTAssertEqualObjects(OAuthCredential.IDToken, kFakeIDToken);
  189. }
  190. /** @fn testObtainingOAuthProvider
  191. @brief Tests the correct creation of an FIROAuthProvider instance.
  192. */
  193. - (void)testObtainingOAuthProvider {
  194. id mockAuth = OCMClassMock([FIRAuth class]);
  195. id mockApp = OCMClassMock([FIRApp class]);
  196. OCMStub([mockAuth app]).andReturn(mockApp);
  197. id mockOptions = OCMClassMock([FIROptions class]);
  198. OCMStub([(FIRApp *)mockApp options]).andReturn(mockOptions);
  199. FIROAuthProvider *OAuthProvider = [FIROAuthProvider providerWithProviderID:kFakeProviderID
  200. auth:mockAuth];
  201. XCTAssertTrue([OAuthProvider isKindOfClass:[FIROAuthProvider class]]);
  202. XCTAssertEqualObjects(OAuthProvider.providerID, kFakeProviderID);
  203. }
  204. /** @fn testGetCredentialWithUIDelegate
  205. @brief Tests a successful invocation of @c getCredentialWithUIDelegte:completion:
  206. */
  207. - (void)testGetCredentialWithUIDelegate {
  208. id mockBundle = OCMClassMock([NSBundle class]);
  209. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  210. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  211. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  212. ]);
  213. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  214. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  215. .andCallBlock2(
  216. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  217. XCTAssertNotNil(request);
  218. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  219. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  220. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  221. kFakeAuthorizedDomain
  222. ]);
  223. callback(mockGetProjectConfigResponse, nil);
  224. });
  225. });
  226. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  227. // Expect view controller presentation by UIDelegate.
  228. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  229. UIDelegate:mockUIDelegate
  230. callbackMatcher:OCMOCK_ANY
  231. completion:OCMOCK_ANY])
  232. .andDo(^(NSInvocation *invocation) {
  233. __unsafe_unretained id unretainedArgument;
  234. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  235. // `presentURL` is at index 2.
  236. [invocation getArgument:&unretainedArgument atIndex:2];
  237. NSURL *presentURL = unretainedArgument;
  238. XCTAssertEqualObjects(presentURL.scheme, @"https");
  239. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  240. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  241. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  242. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  243. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  244. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  245. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  246. XCTAssertNotNil(params[@"v"]);
  247. // `callbackMatcher` is at index 4
  248. [invocation getArgument:&unretainedArgument atIndex:4];
  249. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  250. NSMutableString *redirectURL =
  251. [NSMutableString stringWithString:kFakeRedirectURLResponseURL];
  252. // Add fake OAuthResponse to callback.
  253. [redirectURL appendString:kFakeOAuthResponseURL];
  254. // Verify that the URL is rejected by the callback matcher without the event ID.
  255. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  256. [redirectURL appendString:@"%26eventId%3D"];
  257. [redirectURL appendString:params[@"eventId"]];
  258. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  259. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  260. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  261. NSURLComponents *components = [originalComponents copy];
  262. components.query = @"https";
  263. XCTAssertFalse(callbackMatcher([components URL]));
  264. components = [originalComponents copy];
  265. components.host = @"badhost";
  266. XCTAssertFalse(callbackMatcher([components URL]));
  267. components = [originalComponents copy];
  268. components.path = @"badpath";
  269. XCTAssertFalse(callbackMatcher([components URL]));
  270. components = [originalComponents copy];
  271. components.query = @"badquery";
  272. XCTAssertFalse(callbackMatcher([components URL]));
  273. // `completion` is at index 5
  274. [invocation getArgument:&unretainedArgument atIndex:5];
  275. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  276. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  277. completion(originalComponents.URL, nil);
  278. });
  279. });
  280. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  281. [_provider
  282. getCredentialWithUIDelegate:mockUIDelegate
  283. completion:^(FIRAuthCredential *_Nullable credential,
  284. NSError *_Nullable error) {
  285. XCTAssertTrue([NSThread isMainThread]);
  286. XCTAssertNil(error);
  287. XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
  288. FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
  289. XCTAssertEqualObjects(kFakeOAuthResponseURL,
  290. OAuthCredential.OAuthResponseURLString);
  291. [expectation fulfill];
  292. }];
  293. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  294. OCMVerifyAll(_mockBackend);
  295. }
  296. /** @fn testGetCredentialWithUIDelegateUserCancellation
  297. @brief Tests an unsuccessful invocation of @c getCredentialWithUIDelegte:completion: due to user
  298. cancelation.
  299. */
  300. - (void)testGetCredentialWithUIDelegateUserCancellation {
  301. id mockBundle = OCMClassMock([NSBundle class]);
  302. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  303. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  304. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  305. ]);
  306. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  307. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  308. .andCallBlock2(
  309. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  310. XCTAssertNotNil(request);
  311. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  312. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  313. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  314. kFakeAuthorizedDomain
  315. ]);
  316. callback(mockGetProjectConfigResponse, nil);
  317. });
  318. });
  319. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  320. // Expect view controller presentation by UIDelegate.
  321. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  322. UIDelegate:mockUIDelegate
  323. callbackMatcher:OCMOCK_ANY
  324. completion:OCMOCK_ANY])
  325. .andDo(^(NSInvocation *invocation) {
  326. __unsafe_unretained id unretainedArgument;
  327. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  328. // `presentURL` is at index 2.
  329. [invocation getArgument:&unretainedArgument atIndex:2];
  330. NSURL *presentURL = unretainedArgument;
  331. XCTAssertEqualObjects(presentURL.scheme, @"https");
  332. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  333. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  334. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  335. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  336. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  337. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  338. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  339. XCTAssertNotNil(params[@"v"]);
  340. // `callbackMatcher` is at index 4
  341. [invocation getArgument:&unretainedArgument atIndex:4];
  342. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  343. NSMutableString *redirectURL =
  344. [NSMutableString stringWithString:kFakeRedirectURLResponseURL];
  345. // Add fake OAuthResponse to callback.
  346. [redirectURL appendString:kFakeOAuthResponseURL];
  347. // Verify that the URL is rejected by the callback matcher without the event ID.
  348. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  349. [redirectURL appendString:@"%26eventId%3D"];
  350. [redirectURL appendString:params[@"eventId"]];
  351. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  352. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  353. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  354. NSURLComponents *components = [originalComponents copy];
  355. components.query = @"https";
  356. XCTAssertFalse(callbackMatcher([components URL]));
  357. components = [originalComponents copy];
  358. components.host = @"badhost";
  359. XCTAssertFalse(callbackMatcher([components URL]));
  360. components = [originalComponents copy];
  361. components.path = @"badpath";
  362. XCTAssertFalse(callbackMatcher([components URL]));
  363. components = [originalComponents copy];
  364. components.query = @"badquery";
  365. XCTAssertFalse(callbackMatcher([components URL]));
  366. // `completion` is at index 5
  367. [invocation getArgument:&unretainedArgument atIndex:5];
  368. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  369. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  370. completion(nil, [FIRAuthErrorUtils webContextCancelledErrorWithMessage:nil]);
  371. });
  372. });
  373. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  374. [_provider getCredentialWithUIDelegate:mockUIDelegate
  375. completion:^(FIRAuthCredential *_Nullable credential,
  376. NSError *_Nullable error) {
  377. XCTAssertTrue([NSThread isMainThread]);
  378. XCTAssertNil(credential);
  379. XCTAssertEqual(FIRAuthErrorCodeWebContextCancelled, error.code);
  380. [expectation fulfill];
  381. }];
  382. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  383. OCMVerifyAll(_mockBackend);
  384. }
  385. /** @fn testGetCredentialWithUIDelegateNetworkRequestFailed
  386. @brief Tests an unsuccessful invocation of @c getCredentialWithUIDelegte:completion: due to a
  387. failed network request within the web context.
  388. */
  389. - (void)testGetCredentialWithUIDelegateNetworkRequestFailed {
  390. id mockBundle = OCMClassMock([NSBundle class]);
  391. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  392. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  393. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  394. ]);
  395. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  396. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  397. .andCallBlock2(
  398. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  399. XCTAssertNotNil(request);
  400. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  401. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  402. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  403. kFakeAuthorizedDomain
  404. ]);
  405. callback(mockGetProjectConfigResponse, nil);
  406. });
  407. });
  408. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  409. // Expect view controller presentation by UIDelegate.
  410. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  411. UIDelegate:mockUIDelegate
  412. callbackMatcher:OCMOCK_ANY
  413. completion:OCMOCK_ANY])
  414. .andDo(^(NSInvocation *invocation) {
  415. __unsafe_unretained id unretainedArgument;
  416. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  417. // `presentURL` is at index 2.
  418. [invocation getArgument:&unretainedArgument atIndex:2];
  419. NSURL *presentURL = unretainedArgument;
  420. XCTAssertEqualObjects(presentURL.scheme, @"https");
  421. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  422. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  423. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  424. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  425. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  426. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  427. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  428. XCTAssertNotNil(params[@"v"]);
  429. // `callbackMatcher` is at index 4
  430. [invocation getArgument:&unretainedArgument atIndex:4];
  431. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  432. NSMutableString *redirectURL =
  433. [NSMutableString stringWithString:kFakeRedirectURLBaseErrorString];
  434. [redirectURL appendString:kNetworkRequestFailedErrorString];
  435. // Verify that the URL is rejected by the callback matcher without the event ID.
  436. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  437. [redirectURL appendString:@"%26eventId%3D"];
  438. [redirectURL appendString:params[@"eventId"]];
  439. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  440. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  441. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  442. NSURLComponents *components = [originalComponents copy];
  443. components.query = @"https";
  444. XCTAssertFalse(callbackMatcher([components URL]));
  445. components = [originalComponents copy];
  446. components.host = @"badhost";
  447. XCTAssertFalse(callbackMatcher([components URL]));
  448. components = [originalComponents copy];
  449. components.path = @"badpath";
  450. XCTAssertFalse(callbackMatcher([components URL]));
  451. components = [originalComponents copy];
  452. components.query = @"badquery";
  453. XCTAssertFalse(callbackMatcher([components URL]));
  454. // `completion` is at index 5
  455. [invocation getArgument:&unretainedArgument atIndex:5];
  456. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  457. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  458. completion(originalComponents.URL, nil);
  459. });
  460. });
  461. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  462. [_provider getCredentialWithUIDelegate:mockUIDelegate
  463. completion:^(FIRAuthCredential *_Nullable credential,
  464. NSError *_Nullable error) {
  465. XCTAssertTrue([NSThread isMainThread]);
  466. XCTAssertNil(credential);
  467. XCTAssertEqual(FIRAuthErrorCodeWebNetworkRequestFailed, error.code);
  468. [expectation fulfill];
  469. }];
  470. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  471. OCMVerifyAll(_mockBackend);
  472. }
  473. /** @fn testGetCredentialWithUIDelegateInternalError
  474. @brief Tests an unsuccessful invocation of @c getCredentialWithUIDelegte:completion: due to an
  475. internal error within the web context.
  476. */
  477. - (void)testGetCredentialWithUIDelegateInternalError {
  478. id mockBundle = OCMClassMock([NSBundle class]);
  479. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  480. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  481. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  482. ]);
  483. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  484. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  485. .andCallBlock2(
  486. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  487. XCTAssertNotNil(request);
  488. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  489. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  490. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  491. kFakeAuthorizedDomain
  492. ]);
  493. callback(mockGetProjectConfigResponse, nil);
  494. });
  495. });
  496. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  497. // Expect view controller presentation by UIDelegate.
  498. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  499. UIDelegate:mockUIDelegate
  500. callbackMatcher:OCMOCK_ANY
  501. completion:OCMOCK_ANY])
  502. .andDo(^(NSInvocation *invocation) {
  503. __unsafe_unretained id unretainedArgument;
  504. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  505. // `presentURL` is at index 2.
  506. [invocation getArgument:&unretainedArgument atIndex:2];
  507. NSURL *presentURL = unretainedArgument;
  508. XCTAssertEqualObjects(presentURL.scheme, @"https");
  509. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  510. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  511. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  512. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  513. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  514. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  515. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  516. XCTAssertNotNil(params[@"v"]);
  517. // `callbackMatcher` is at index 4
  518. [invocation getArgument:&unretainedArgument atIndex:4];
  519. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  520. NSMutableString *redirectURL =
  521. [NSMutableString stringWithString:kFakeRedirectURLBaseErrorString];
  522. // Add internal error string to redirect URL.
  523. [redirectURL appendString:kInternalErrorString];
  524. // Verify that the URL is rejected by the callback matcher without the event ID.
  525. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  526. [redirectURL appendString:@"%26eventId%3D"];
  527. [redirectURL appendString:params[@"eventId"]];
  528. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  529. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  530. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  531. NSURLComponents *components = [originalComponents copy];
  532. components.query = @"https";
  533. XCTAssertFalse(callbackMatcher([components URL]));
  534. components = [originalComponents copy];
  535. components.host = @"badhost";
  536. XCTAssertFalse(callbackMatcher([components URL]));
  537. components = [originalComponents copy];
  538. components.path = @"badpath";
  539. XCTAssertFalse(callbackMatcher([components URL]));
  540. components = [originalComponents copy];
  541. components.query = @"badquery";
  542. XCTAssertFalse(callbackMatcher([components URL]));
  543. // `completion` is at index 5
  544. [invocation getArgument:&unretainedArgument atIndex:5];
  545. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  546. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  547. completion(originalComponents.URL, nil);
  548. });
  549. });
  550. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  551. [_provider getCredentialWithUIDelegate:mockUIDelegate
  552. completion:^(FIRAuthCredential *_Nullable credential,
  553. NSError *_Nullable error) {
  554. XCTAssertTrue([NSThread isMainThread]);
  555. XCTAssertNil(credential);
  556. XCTAssertEqual(FIRAuthErrorCodeWebInternalError, error.code);
  557. [expectation fulfill];
  558. }];
  559. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  560. OCMVerifyAll(_mockBackend);
  561. }
  562. /** @fn testGetCredentialWithUIDelegateInvalidClientID
  563. @brief Tests an unsuccessful invocation of @c getCredentialWithUIDelegte:completion: due to an
  564. use of an invalid client ID.
  565. */
  566. - (void)testGetCredentialWithUIDelegateInvalidClientID {
  567. id mockBundle = OCMClassMock([NSBundle class]);
  568. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  569. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  570. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  571. ]);
  572. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  573. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  574. .andCallBlock2(
  575. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  576. XCTAssertNotNil(request);
  577. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  578. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  579. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  580. kFakeAuthorizedDomain
  581. ]);
  582. callback(mockGetProjectConfigResponse, nil);
  583. });
  584. });
  585. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  586. // Expect view controller presentation by UIDelegate.
  587. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  588. UIDelegate:mockUIDelegate
  589. callbackMatcher:OCMOCK_ANY
  590. completion:OCMOCK_ANY])
  591. .andDo(^(NSInvocation *invocation) {
  592. __unsafe_unretained id unretainedArgument;
  593. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  594. // `presentURL` is at index 2.
  595. [invocation getArgument:&unretainedArgument atIndex:2];
  596. NSURL *presentURL = unretainedArgument;
  597. XCTAssertEqualObjects(presentURL.scheme, @"https");
  598. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  599. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  600. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  601. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  602. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  603. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  604. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  605. XCTAssertNotNil(params[@"v"]);
  606. // `callbackMatcher` is at index 4
  607. [invocation getArgument:&unretainedArgument atIndex:4];
  608. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  609. NSMutableString *redirectURL =
  610. [NSMutableString stringWithString:kFakeRedirectURLBaseErrorString];
  611. // Add invalid client ID error to redirect URL.
  612. [redirectURL appendString:kInvalidClientIDString];
  613. // Verify that the URL is rejected by the callback matcher without the event ID.
  614. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  615. [redirectURL appendString:@"%26eventId%3D"];
  616. [redirectURL appendString:params[@"eventId"]];
  617. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  618. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  619. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  620. NSURLComponents *components = [originalComponents copy];
  621. components.query = @"https";
  622. XCTAssertFalse(callbackMatcher([components URL]));
  623. components = [originalComponents copy];
  624. components.host = @"badhost";
  625. XCTAssertFalse(callbackMatcher([components URL]));
  626. components = [originalComponents copy];
  627. components.path = @"badpath";
  628. XCTAssertFalse(callbackMatcher([components URL]));
  629. components = [originalComponents copy];
  630. components.query = @"badquery";
  631. XCTAssertFalse(callbackMatcher([components URL]));
  632. // `completion` is at index 5
  633. [invocation getArgument:&unretainedArgument atIndex:5];
  634. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  635. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  636. completion(originalComponents.URL, nil);
  637. });
  638. });
  639. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  640. [_provider getCredentialWithUIDelegate:mockUIDelegate
  641. completion:^(FIRAuthCredential *_Nullable credential,
  642. NSError *_Nullable error) {
  643. XCTAssertTrue([NSThread isMainThread]);
  644. XCTAssertNil(credential);
  645. XCTAssertEqual(FIRAuthErrorCodeInvalidClientID, error.code);
  646. [expectation fulfill];
  647. }];
  648. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  649. OCMVerifyAll(_mockBackend);
  650. }
  651. /** @fn testGetCredentialWithUIDelegateUknownError
  652. @brief Tests an unsuccessful invocation of @c getCredentialWithUIDelegte:completion: due to an
  653. unknown error.
  654. */
  655. - (void)testGetCredentialWithUIDelegateUknownError {
  656. id mockBundle = OCMClassMock([NSBundle class]);
  657. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  658. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  659. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  660. ]);
  661. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  662. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  663. .andCallBlock2(
  664. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  665. XCTAssertNotNil(request);
  666. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  667. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  668. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  669. kFakeAuthorizedDomain
  670. ]);
  671. callback(mockGetProjectConfigResponse, nil);
  672. });
  673. });
  674. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  675. // Expect view controller presentation by UIDelegate.
  676. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  677. UIDelegate:mockUIDelegate
  678. callbackMatcher:OCMOCK_ANY
  679. completion:OCMOCK_ANY])
  680. .andDo(^(NSInvocation *invocation) {
  681. __unsafe_unretained id unretainedArgument;
  682. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  683. // `presentURL` is at index 2.
  684. [invocation getArgument:&unretainedArgument atIndex:2];
  685. NSURL *presentURL = unretainedArgument;
  686. XCTAssertEqualObjects(presentURL.scheme, @"https");
  687. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  688. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  689. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  690. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  691. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  692. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  693. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  694. XCTAssertNotNil(params[@"v"]);
  695. // `callbackMatcher` is at index 4
  696. [invocation getArgument:&unretainedArgument atIndex:4];
  697. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  698. NSMutableString *redirectURL =
  699. [NSMutableString stringWithString:kFakeRedirectURLBaseErrorString];
  700. // Add unknown error to redirect URL.
  701. [redirectURL appendString:kUnknownErrorString];
  702. // Verify that the URL is rejected by the callback matcher without the event ID.
  703. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  704. [redirectURL appendString:@"%26eventId%3D"];
  705. [redirectURL appendString:params[@"eventId"]];
  706. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  707. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  708. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  709. NSURLComponents *components = [originalComponents copy];
  710. components.query = @"https";
  711. XCTAssertFalse(callbackMatcher([components URL]));
  712. components = [originalComponents copy];
  713. components.host = @"badhost";
  714. XCTAssertFalse(callbackMatcher([components URL]));
  715. components = [originalComponents copy];
  716. components.path = @"badpath";
  717. XCTAssertFalse(callbackMatcher([components URL]));
  718. components = [originalComponents copy];
  719. components.query = @"badquery";
  720. XCTAssertFalse(callbackMatcher([components URL]));
  721. // `completion` is at index 5
  722. [invocation getArgument:&unretainedArgument atIndex:5];
  723. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  724. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  725. completion(originalComponents.URL, nil);
  726. });
  727. });
  728. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  729. [_provider getCredentialWithUIDelegate:mockUIDelegate
  730. completion:^(FIRAuthCredential *_Nullable credential,
  731. NSError *_Nullable error) {
  732. XCTAssertTrue([NSThread isMainThread]);
  733. XCTAssertNil(credential);
  734. XCTAssertEqual(FIRAuthErrorCodeWebSignInUserInteractionFailure,
  735. error.code);
  736. [expectation fulfill];
  737. }];
  738. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  739. OCMVerifyAll(_mockBackend);
  740. }
  741. @end