FIROAuthProviderTests.m 46 KB

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