FIROAuthProviderTests.m 71 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452
  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 <OCMock/OCMock.h>
  19. #import <XCTest/XCTest.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/Extension/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/FIRFakeAppCheck.h"
  35. #import "FirebaseAuth/Tests/Unit/OCMStubRecorder+FIRAuthUnitTests.h"
  36. /** @var kExpectationTimeout
  37. @brief The maximum time waiting for expectations to fulfill.
  38. */
  39. static const NSTimeInterval kExpectationTimeout = 1;
  40. /** @var kFakeAuthorizedDomain
  41. @brief A fake authorized domain for the app.
  42. */
  43. static NSString *const kFakeAuthorizedDomain = @"test.firebaseapp.com";
  44. /** @var kFakeBundleID
  45. @brief A fake bundle ID.
  46. */
  47. static NSString *const kFakeBundleID = @"com.firebaseapp.example";
  48. /** @var kFakeAccessToken
  49. @brief A fake access token for testing.
  50. */
  51. static NSString *const kFakeAccessToken = @"fakeAccessToken";
  52. /** @var kFakeIDToken
  53. @brief A fake ID token for testing.
  54. */
  55. static NSString *const kFakeIDToken = @"fakeIDToken";
  56. /** @var kFakeProviderID
  57. @brief A fake provider ID for testing.
  58. */
  59. static NSString *const kFakeProviderID = @"fakeProviderID";
  60. /** @var kFakeGivenName
  61. @brief A fake given name for testing.
  62. */
  63. static NSString *const kFakeGivenName = @"fakeGivenName";
  64. /** @var kFakeFamilyName
  65. @brief A fake family name for testing.
  66. */
  67. static NSString *const kFakeFamilyName = @"fakeFamilyName";
  68. /** @var kFakeAPIKey
  69. @brief A fake API key.
  70. */
  71. static NSString *const kFakeAPIKey = @"asdfghjkl";
  72. /** @var kFakeEmulatorHost
  73. @brief A fake emulator host.
  74. */
  75. static NSString *const kFakeEmulatorHost = @"emulatorhost";
  76. /** @var kFakeEmulatorPort
  77. @brief A fake emulator port.
  78. */
  79. static NSString *const kFakeEmulatorPort = @"12345";
  80. /** @var kFakeClientID
  81. @brief A fake client ID.
  82. */
  83. static NSString *const kFakeClientID = @"123456.apps.googleusercontent.com";
  84. /** @var kFakeReverseClientID
  85. @brief The dot-reversed version of the fake client ID.
  86. */
  87. static NSString *const kFakeReverseClientID = @"com.googleusercontent.apps.123456";
  88. /** @var kFakeFirebaseAppID
  89. @brief A fake Firebase app ID.
  90. */
  91. static NSString *const kFakeFirebaseAppID = @"1:123456789:ios:123abc456def";
  92. /** @var kFakeEncodedFirebaseAppID
  93. @brief A fake encoded Firebase app ID to be used as a custom URL scheme.
  94. */
  95. static NSString *const kFakeEncodedFirebaseAppID = @"app-1-123456789-ios-123abc456def";
  96. /** @var kFakeTenantID
  97. @brief A fake tenant ID.
  98. */
  99. static NSString *const kFakeTenantID = @"tenantID";
  100. /** @var kFakeOAuthResponseURL
  101. @brief A fake OAuth response URL used in test.
  102. */
  103. static NSString *const kFakeOAuthResponseURL = @"fakeOAuthResponseURL";
  104. /** @var kFakeRedirectURLResponseURL
  105. @brief A fake callback URL (minus the scheme) containing a fake response URL.
  106. */
  107. static NSString *const kFakeRedirectURLResponseURL =
  108. @"://firebaseauth/"
  109. @"link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcallback%3FauthType%"
  110. @"3DsignInWithRedirect%26link%3D";
  111. /** @var kFakeRedirectURLBaseErrorString
  112. @brief The base for a fake redirect URL string that contains an error.
  113. */
  114. static NSString *const kFakeRedirectURLBaseErrorString =
  115. @"com.googleusercontent.apps.123456://fire"
  116. "baseauth/link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcallback%3f";
  117. /** @var kNetworkRequestFailedErrorString
  118. @brief The error message returned if a network request failure occurs within the web context.
  119. */
  120. static NSString *const kNetworkRequestFailedErrorString =
  121. @"firebaseError%3D%257B%2522code%2"
  122. "522%253A%2522auth%252Fnetwork-request-failed%2522%252C%2522message%2522%253A%2522The%"
  123. "2520netwo"
  124. "rk%2520request%2520failed%2520.%2522%257D%26authType%3DsignInWithRedirect";
  125. /** @var kInvalidClientIDString
  126. @brief The error message returned if the client ID used is invalid.
  127. */
  128. static NSString *const kInvalidClientIDString =
  129. @"firebaseError%3D%257B%2522code%2522%253A%2522auth"
  130. "%252Finvalid-oauth-client-id%2522%252C%2522message%2522%253A%2522The%2520OAuth%2520client%"
  131. "2520"
  132. "ID%2520provided%2520is%2520either%2520invalid%2520or%2520does%2520not%2520match%2520the%"
  133. "2520sp"
  134. "ecified%2520API%2520key.%2522%257D%26authType%3DsignInWithRedirect";
  135. /** @var kInternalErrorString
  136. @brief The error message returned if there is an internal error within the web context.
  137. */
  138. static NSString *const kInternalErrorString =
  139. @"firebaseError%3D%257B%2522code%2522%253"
  140. "A%2522auth%252Finternal-error%2522%252C%2522message%2522%253A%2522Internal%2520error%2520.%"
  141. "252"
  142. "2%257D%26authType%3DsignInWithRedirect";
  143. /** @var kUnknownErrorString
  144. @brief The error message returned if an unknown error is returned from the web context.
  145. */
  146. static NSString *const kUnknownErrorString =
  147. @"firebaseError%3D%257B%2522code%2522%253A%2522auth%2"
  148. "52Funknown-error-id%2522%252C%2522message%2522%253A%2522The%2520OAuth%2520client%2520ID%"
  149. "2520pr"
  150. "ovided%2520is%2520either%2520invalid%2520or%2520does%2520not%2520match%2520the%2520specified%"
  151. "2"
  152. "520API%2520key.%2522%257D%26authType%3DsignInWithRedirect";
  153. @interface FIROAuthProviderTests : XCTestCase
  154. @end
  155. @implementation FIROAuthProviderTests {
  156. /** @var _mockBackend
  157. @brief The mock @c FIRAuthBackendImplementation.
  158. */
  159. id _mockBackend;
  160. /** @var _provider
  161. @brief The @c FIROAuthProvider instance under test.
  162. */
  163. FIROAuthProvider *_provider;
  164. /** @var _mockAuth
  165. @brief The mock @c FIRAuth instance associated with @c _provider.
  166. */
  167. id _mockAuth;
  168. #if !defined(TARGET_OS_VISION) || !TARGET_OS_VISION
  169. /** @var _mockURLPresenter
  170. @brief The mock @c FIRAuthURLPresenter instance associated with @c _mockAuth.
  171. */
  172. id _mockURLPresenter;
  173. #endif // !defined(TARGET_OS_VISION) || !TARGET_OS_VISION
  174. /** @var _mockApp
  175. @brief The mock @c FIRApp instance associated with @c _mockAuth.
  176. */
  177. id _mockApp;
  178. /** @var _mockOptions
  179. @brief The mock @c FIROptions instance associated with @c _mockApp.
  180. */
  181. id _mockOptions;
  182. /** @var _mockRequestConfiguration
  183. @brief The mock @c FIRAuthRequestConfiguration instance associated with @c _mockAuth.
  184. */
  185. id _mockRequestConfiguration;
  186. }
  187. - (void)setUp {
  188. [super setUp];
  189. _mockBackend = OCMProtocolMock(@protocol(FIRAuthBackendImplementation));
  190. [FIRAuthBackend setBackendImplementation:_mockBackend];
  191. _mockAuth = OCMClassMock([FIRAuth class]);
  192. _mockApp = OCMClassMock([FIRApp class]);
  193. OCMStub([_mockAuth app]).andReturn(_mockApp);
  194. _mockOptions = OCMClassMock([FIROptions class]);
  195. OCMStub([(FIRApp *)_mockApp options]).andReturn(_mockOptions);
  196. OCMStub([_mockOptions googleAppID]).andReturn(kFakeFirebaseAppID);
  197. #if !defined(TARGET_OS_VISION) || !TARGET_OS_VISION
  198. _mockURLPresenter = OCMClassMock([FIRAuthURLPresenter class]);
  199. OCMStub([_mockAuth authURLPresenter]).andReturn(_mockURLPresenter);
  200. #endif // !defined(TARGET_OS_VISION) || !TARGET_OS_VISION
  201. _mockRequestConfiguration = OCMClassMock([FIRAuthRequestConfiguration class]);
  202. OCMStub([_mockAuth requestConfiguration]).andReturn(_mockRequestConfiguration);
  203. OCMStub([_mockRequestConfiguration APIKey]).andReturn(kFakeAPIKey);
  204. }
  205. /** @fn testObtainingOAuthCredentialNoIDToken
  206. @brief Tests the correct creation of an OAuthCredential without an IDToken.
  207. */
  208. - (void)testObtainingOAuthCredentialNoIDToken {
  209. FIRAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:kFakeProviderID
  210. accessToken:kFakeAccessToken];
  211. XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
  212. FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
  213. XCTAssertEqualObjects(OAuthCredential.accessToken, kFakeAccessToken);
  214. XCTAssertEqualObjects(OAuthCredential.provider, kFakeProviderID);
  215. XCTAssertNil(OAuthCredential.IDToken);
  216. }
  217. /** @fn testObtainingOAuthCredentialWithFullName
  218. @brief Tests the correct creation of an OAuthCredential with a fullName.
  219. */
  220. - (void)testObtainingOAuthCredentialWithFullName {
  221. NSPersonNameComponents *fullName = [[NSPersonNameComponents alloc] init];
  222. fullName.givenName = kFakeGivenName;
  223. fullName.familyName = kFakeFamilyName;
  224. FIRAuthCredential *credential = [FIROAuthProvider appleCredentialWithIDToken:kFakeIDToken
  225. rawNonce:nil
  226. fullName:fullName];
  227. XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
  228. FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
  229. XCTAssertEqualObjects(OAuthCredential.provider, @"apple.com");
  230. XCTAssertEqualObjects(OAuthCredential.IDToken, kFakeIDToken);
  231. XCTAssertEqualObjects(OAuthCredential.fullName, fullName);
  232. XCTAssertNil(OAuthCredential.accessToken);
  233. }
  234. /** @fn testObtainingOAuthCredentialWithIDToken
  235. @brief Tests the correct creation of an OAuthCredential with an IDToken
  236. */
  237. - (void)testObtainingOAuthCredentialWithIDToken {
  238. FIRAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:kFakeProviderID
  239. IDToken:kFakeIDToken
  240. accessToken:kFakeAccessToken];
  241. XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
  242. FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
  243. XCTAssertEqualObjects(OAuthCredential.accessToken, kFakeAccessToken);
  244. XCTAssertEqualObjects(OAuthCredential.provider, kFakeProviderID);
  245. XCTAssertEqualObjects(OAuthCredential.IDToken, kFakeIDToken);
  246. }
  247. /** @fn testObtainingOAuthProvider
  248. @brief Tests the correct creation of an FIROAuthProvider instance.
  249. */
  250. - (void)testObtainingOAuthProvider {
  251. id mockAuth = OCMClassMock([FIRAuth class]);
  252. id mockApp = OCMClassMock([FIRApp class]);
  253. OCMStub([mockAuth app]).andReturn(mockApp);
  254. id mockOptions = OCMClassMock([FIROptions class]);
  255. OCMStub([mockOptions clientID]).andReturn(kFakeClientID);
  256. OCMStub([mockOptions googleAppID]).andReturn(kFakeFirebaseAppID);
  257. OCMStub([(FIRApp *)mockApp options]).andReturn(mockOptions);
  258. FIROAuthProvider *OAuthProvider = [FIROAuthProvider providerWithProviderID:kFakeProviderID
  259. auth:mockAuth];
  260. XCTAssertTrue([OAuthProvider isKindOfClass:[FIROAuthProvider class]]);
  261. XCTAssertEqualObjects(OAuthProvider.providerID, kFakeProviderID);
  262. }
  263. #if !defined(TARGET_OS_VISION) || !TARGET_OS_VISION
  264. /** @fn testGetCredentialWithUIDelegateWithClientID
  265. @brief Tests a successful invocation of @c getCredentialWithUIDelegte:completion:
  266. */
  267. - (void)testGetCredentialWithUIDelegateWithClientID {
  268. id mockBundle = OCMClassMock([NSBundle class]);
  269. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  270. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  271. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  272. ]);
  273. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  274. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  275. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  276. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  277. .andCallBlock2(
  278. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  279. XCTAssertNotNil(request);
  280. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  281. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  282. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  283. kFakeAuthorizedDomain
  284. ]);
  285. callback(mockGetProjectConfigResponse, nil);
  286. });
  287. });
  288. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  289. // Expect view controller presentation by UIDelegate.
  290. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  291. UIDelegate:mockUIDelegate
  292. callbackMatcher:OCMOCK_ANY
  293. completion:OCMOCK_ANY])
  294. .andDo(^(NSInvocation *invocation) {
  295. __unsafe_unretained id unretainedArgument;
  296. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  297. // `presentURL` is at index 2.
  298. [invocation getArgument:&unretainedArgument atIndex:2];
  299. NSURL *presentURL = unretainedArgument;
  300. XCTAssertEqualObjects(presentURL.scheme, @"https");
  301. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  302. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  303. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  304. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  305. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  306. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  307. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  308. XCTAssertNotNil(params[@"v"]);
  309. XCTAssertNil(params[@"tid"]);
  310. NSString *appCheckToken = presentURL.fragment;
  311. XCTAssertNil(appCheckToken);
  312. // `callbackMatcher` is at index 4
  313. [invocation getArgument:&unretainedArgument atIndex:4];
  314. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  315. NSMutableString *redirectURL = [NSMutableString
  316. stringWithString:[kFakeReverseClientID
  317. stringByAppendingString:kFakeRedirectURLResponseURL]];
  318. // Add fake OAuthResponse to callback.
  319. [redirectURL appendString:kFakeOAuthResponseURL];
  320. // Verify that the URL is rejected by the callback matcher without the event ID.
  321. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  322. [redirectURL appendString:@"%26eventId%3D"];
  323. [redirectURL appendString:params[@"eventId"]];
  324. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  325. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  326. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  327. NSURLComponents *components = [originalComponents copy];
  328. components.query = @"https";
  329. XCTAssertFalse(callbackMatcher([components URL]));
  330. components = [originalComponents copy];
  331. components.host = @"badhost";
  332. XCTAssertFalse(callbackMatcher([components URL]));
  333. components = [originalComponents copy];
  334. components.path = @"badpath";
  335. XCTAssertFalse(callbackMatcher([components URL]));
  336. components = [originalComponents copy];
  337. components.query = @"badquery";
  338. XCTAssertFalse(callbackMatcher([components URL]));
  339. // `completion` is at index 5
  340. [invocation getArgument:&unretainedArgument atIndex:5];
  341. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  342. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  343. completion(originalComponents.URL, nil);
  344. });
  345. });
  346. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  347. [_provider
  348. getCredentialWithUIDelegate:mockUIDelegate
  349. completion:^(FIRAuthCredential *_Nullable credential,
  350. NSError *_Nullable error) {
  351. XCTAssertTrue([NSThread isMainThread]);
  352. XCTAssertNil(error);
  353. XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
  354. FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
  355. XCTAssertEqualObjects(kFakeOAuthResponseURL,
  356. OAuthCredential.OAuthResponseURLString);
  357. [expectation fulfill];
  358. }];
  359. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  360. OCMVerifyAll(_mockBackend);
  361. }
  362. /** @fn testGetCredentialWithUIDelegateWithTenantID
  363. @brief Tests a successful invocation of @c getCredentialWithUIDelegte:completion:
  364. */
  365. - (void)testGetCredentialWithUIDelegateWithTenantID {
  366. id mockBundle = OCMClassMock([NSBundle class]);
  367. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  368. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  369. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  370. ]);
  371. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  372. OCMStub([_mockAuth tenantID]).andReturn(kFakeTenantID);
  373. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  374. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  375. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  376. .andCallBlock2(
  377. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  378. XCTAssertNotNil(request);
  379. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  380. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  381. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  382. kFakeAuthorizedDomain
  383. ]);
  384. callback(mockGetProjectConfigResponse, nil);
  385. });
  386. });
  387. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  388. // Expect view controller presentation by UIDelegate.
  389. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  390. UIDelegate:mockUIDelegate
  391. callbackMatcher:OCMOCK_ANY
  392. completion:OCMOCK_ANY])
  393. .andDo(^(NSInvocation *invocation) {
  394. __unsafe_unretained id unretainedArgument;
  395. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  396. // `presentURL` is at index 2.
  397. [invocation getArgument:&unretainedArgument atIndex:2];
  398. NSURL *presentURL = unretainedArgument;
  399. XCTAssertEqualObjects(presentURL.scheme, @"https");
  400. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  401. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  402. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  403. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  404. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  405. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  406. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  407. XCTAssertEqualObjects(params[@"tid"], kFakeTenantID);
  408. XCTAssertNotNil(params[@"v"]);
  409. NSString *appCheckToken = presentURL.fragment;
  410. XCTAssertNil(appCheckToken);
  411. // `callbackMatcher` is at index 4
  412. [invocation getArgument:&unretainedArgument atIndex:4];
  413. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  414. NSMutableString *redirectURL = [NSMutableString
  415. stringWithString:[kFakeReverseClientID
  416. stringByAppendingString:kFakeRedirectURLResponseURL]];
  417. // Add fake OAuthResponse to callback.
  418. [redirectURL appendString:kFakeOAuthResponseURL];
  419. // Verify that the URL is rejected by the callback matcher without the event ID.
  420. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  421. [redirectURL appendString:@"%26eventId%3D"];
  422. [redirectURL appendString:params[@"eventId"]];
  423. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  424. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  425. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  426. NSURLComponents *components = [originalComponents copy];
  427. components.query = @"https";
  428. XCTAssertFalse(callbackMatcher([components URL]));
  429. components = [originalComponents copy];
  430. components.host = @"badhost";
  431. XCTAssertFalse(callbackMatcher([components URL]));
  432. components = [originalComponents copy];
  433. components.path = @"badpath";
  434. XCTAssertFalse(callbackMatcher([components URL]));
  435. components = [originalComponents copy];
  436. components.query = @"badquery";
  437. XCTAssertFalse(callbackMatcher([components URL]));
  438. // `completion` is at index 5
  439. [invocation getArgument:&unretainedArgument atIndex:5];
  440. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  441. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  442. completion(originalComponents.URL, nil);
  443. });
  444. });
  445. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  446. [_provider
  447. getCredentialWithUIDelegate:mockUIDelegate
  448. completion:^(FIRAuthCredential *_Nullable credential,
  449. NSError *_Nullable error) {
  450. XCTAssertTrue([NSThread isMainThread]);
  451. XCTAssertNil(error);
  452. XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
  453. FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
  454. XCTAssertEqualObjects(kFakeOAuthResponseURL,
  455. OAuthCredential.OAuthResponseURLString);
  456. [expectation fulfill];
  457. }];
  458. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  459. OCMVerifyAll(_mockBackend);
  460. }
  461. /** @fn testGetCredentialWithUIDelegateUserCancellationWithClientID
  462. @brief Tests an unsuccessful invocation of @c getCredentialWithUIDelegte:completion: due to user
  463. cancelation.
  464. */
  465. - (void)testGetCredentialWithUIDelegateUserCancellationWithClientID {
  466. id mockBundle = OCMClassMock([NSBundle class]);
  467. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  468. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  469. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  470. ]);
  471. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  472. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  473. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  474. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  475. .andCallBlock2(
  476. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  477. XCTAssertNotNil(request);
  478. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  479. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  480. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  481. kFakeAuthorizedDomain
  482. ]);
  483. callback(mockGetProjectConfigResponse, nil);
  484. });
  485. });
  486. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  487. // Expect view controller presentation by UIDelegate.
  488. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  489. UIDelegate:mockUIDelegate
  490. callbackMatcher:OCMOCK_ANY
  491. completion:OCMOCK_ANY])
  492. .andDo(^(NSInvocation *invocation) {
  493. __unsafe_unretained id unretainedArgument;
  494. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  495. // `presentURL` is at index 2.
  496. [invocation getArgument:&unretainedArgument atIndex:2];
  497. NSURL *presentURL = unretainedArgument;
  498. XCTAssertEqualObjects(presentURL.scheme, @"https");
  499. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  500. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  501. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  502. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  503. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  504. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  505. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  506. XCTAssertNotNil(params[@"v"]);
  507. XCTAssertNil(params[@"tid"]);
  508. NSString *appCheckToken = presentURL.fragment;
  509. XCTAssertNil(appCheckToken);
  510. // `callbackMatcher` is at index 4
  511. [invocation getArgument:&unretainedArgument atIndex:4];
  512. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  513. NSMutableString *redirectURL = [NSMutableString
  514. stringWithString:[kFakeReverseClientID
  515. stringByAppendingString:kFakeRedirectURLResponseURL]];
  516. // Add fake OAuthResponse to callback.
  517. [redirectURL appendString:kFakeOAuthResponseURL];
  518. // Verify that the URL is rejected by the callback matcher without the event ID.
  519. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  520. [redirectURL appendString:@"%26eventId%3D"];
  521. [redirectURL appendString:params[@"eventId"]];
  522. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  523. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  524. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  525. NSURLComponents *components = [originalComponents copy];
  526. components.query = @"https";
  527. XCTAssertFalse(callbackMatcher([components URL]));
  528. components = [originalComponents copy];
  529. components.host = @"badhost";
  530. XCTAssertFalse(callbackMatcher([components URL]));
  531. components = [originalComponents copy];
  532. components.path = @"badpath";
  533. XCTAssertFalse(callbackMatcher([components URL]));
  534. components = [originalComponents copy];
  535. components.query = @"badquery";
  536. XCTAssertFalse(callbackMatcher([components URL]));
  537. // `completion` is at index 5
  538. [invocation getArgument:&unretainedArgument atIndex:5];
  539. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  540. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  541. completion(nil, [FIRAuthErrorUtils webContextCancelledErrorWithMessage:nil]);
  542. });
  543. });
  544. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  545. [_provider getCredentialWithUIDelegate:mockUIDelegate
  546. completion:^(FIRAuthCredential *_Nullable credential,
  547. NSError *_Nullable error) {
  548. XCTAssertTrue([NSThread isMainThread]);
  549. XCTAssertNil(credential);
  550. XCTAssertEqual(FIRAuthErrorCodeWebContextCancelled, error.code);
  551. [expectation fulfill];
  552. }];
  553. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  554. OCMVerifyAll(_mockBackend);
  555. }
  556. /** @fn testGetCredentialWithUIDelegateNetworkRequestFailedWithClientID
  557. @brief Tests an unsuccessful invocation of @c getCredentialWithUIDelegte:completion: due to a
  558. failed network request within the web context.
  559. */
  560. - (void)testGetCredentialWithUIDelegateNetworkRequestFailedWithClientID {
  561. id mockBundle = OCMClassMock([NSBundle class]);
  562. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  563. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  564. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  565. ]);
  566. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  567. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  568. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  569. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  570. .andCallBlock2(
  571. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  572. XCTAssertNotNil(request);
  573. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  574. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  575. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  576. kFakeAuthorizedDomain
  577. ]);
  578. callback(mockGetProjectConfigResponse, nil);
  579. });
  580. });
  581. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  582. // Expect view controller presentation by UIDelegate.
  583. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  584. UIDelegate:mockUIDelegate
  585. callbackMatcher:OCMOCK_ANY
  586. completion:OCMOCK_ANY])
  587. .andDo(^(NSInvocation *invocation) {
  588. __unsafe_unretained id unretainedArgument;
  589. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  590. // `presentURL` is at index 2.
  591. [invocation getArgument:&unretainedArgument atIndex:2];
  592. NSURL *presentURL = unretainedArgument;
  593. XCTAssertEqualObjects(presentURL.scheme, @"https");
  594. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  595. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  596. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  597. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  598. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  599. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  600. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  601. XCTAssertNotNil(params[@"v"]);
  602. XCTAssertNil(params[@"tid"]);
  603. NSString *appCheckToken = presentURL.fragment;
  604. XCTAssertNil(appCheckToken);
  605. // `callbackMatcher` is at index 4
  606. [invocation getArgument:&unretainedArgument atIndex:4];
  607. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  608. NSMutableString *redirectURL =
  609. [NSMutableString stringWithString:kFakeRedirectURLBaseErrorString];
  610. [redirectURL appendString:kNetworkRequestFailedErrorString];
  611. // Verify that the URL is rejected by the callback matcher without the event ID.
  612. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  613. [redirectURL appendString:@"%26eventId%3D"];
  614. [redirectURL appendString:params[@"eventId"]];
  615. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  616. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  617. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  618. NSURLComponents *components = [originalComponents copy];
  619. components.query = @"https";
  620. XCTAssertFalse(callbackMatcher([components URL]));
  621. components = [originalComponents copy];
  622. components.host = @"badhost";
  623. XCTAssertFalse(callbackMatcher([components URL]));
  624. components = [originalComponents copy];
  625. components.path = @"badpath";
  626. XCTAssertFalse(callbackMatcher([components URL]));
  627. components = [originalComponents copy];
  628. components.query = @"badquery";
  629. XCTAssertFalse(callbackMatcher([components URL]));
  630. // `completion` is at index 5
  631. [invocation getArgument:&unretainedArgument atIndex:5];
  632. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  633. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  634. completion(originalComponents.URL, nil);
  635. });
  636. });
  637. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  638. [_provider getCredentialWithUIDelegate:mockUIDelegate
  639. completion:^(FIRAuthCredential *_Nullable credential,
  640. NSError *_Nullable error) {
  641. XCTAssertTrue([NSThread isMainThread]);
  642. XCTAssertNil(credential);
  643. XCTAssertEqual(FIRAuthErrorCodeWebNetworkRequestFailed, error.code);
  644. [expectation fulfill];
  645. }];
  646. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  647. OCMVerifyAll(_mockBackend);
  648. }
  649. /** @fn testGetCredentialWithUIDelegateInternalErrorWithClientID
  650. @brief Tests an unsuccessful invocation of @c getCredentialWithUIDelegte:completion: due to an
  651. internal error within the web context.
  652. */
  653. - (void)testGetCredentialWithUIDelegateInternalErrorWithClientID {
  654. id mockBundle = OCMClassMock([NSBundle class]);
  655. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  656. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  657. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  658. ]);
  659. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  660. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  661. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  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. XCTAssertNil(params[@"tid"]);
  696. NSString *appCheckToken = presentURL.fragment;
  697. XCTAssertNil(appCheckToken);
  698. // `callbackMatcher` is at index 4
  699. [invocation getArgument:&unretainedArgument atIndex:4];
  700. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  701. NSMutableString *redirectURL =
  702. [NSMutableString stringWithString:kFakeRedirectURLBaseErrorString];
  703. // Add internal error string to redirect URL.
  704. [redirectURL appendString:kInternalErrorString];
  705. // Verify that the URL is rejected by the callback matcher without the event ID.
  706. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  707. [redirectURL appendString:@"%26eventId%3D"];
  708. [redirectURL appendString:params[@"eventId"]];
  709. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  710. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  711. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  712. NSURLComponents *components = [originalComponents copy];
  713. components.query = @"https";
  714. XCTAssertFalse(callbackMatcher([components URL]));
  715. components = [originalComponents copy];
  716. components.host = @"badhost";
  717. XCTAssertFalse(callbackMatcher([components URL]));
  718. components = [originalComponents copy];
  719. components.path = @"badpath";
  720. XCTAssertFalse(callbackMatcher([components URL]));
  721. components = [originalComponents copy];
  722. components.query = @"badquery";
  723. XCTAssertFalse(callbackMatcher([components URL]));
  724. // `completion` is at index 5
  725. [invocation getArgument:&unretainedArgument atIndex:5];
  726. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  727. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  728. completion(originalComponents.URL, nil);
  729. });
  730. });
  731. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  732. [_provider getCredentialWithUIDelegate:mockUIDelegate
  733. completion:^(FIRAuthCredential *_Nullable credential,
  734. NSError *_Nullable error) {
  735. XCTAssertTrue([NSThread isMainThread]);
  736. XCTAssertNil(credential);
  737. XCTAssertEqual(FIRAuthErrorCodeWebInternalError, error.code);
  738. [expectation fulfill];
  739. }];
  740. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  741. OCMVerifyAll(_mockBackend);
  742. }
  743. /** @fn testGetCredentialWithUIDelegateInvalidClientID
  744. @brief Tests an unsuccessful invocation of @c getCredentialWithUIDelegte:completion: due to an
  745. use of an invalid client ID.
  746. */
  747. - (void)testGetCredentialWithUIDelegateInvalidClientID {
  748. id mockBundle = OCMClassMock([NSBundle class]);
  749. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  750. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  751. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  752. ]);
  753. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  754. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  755. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  756. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  757. .andCallBlock2(
  758. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  759. XCTAssertNotNil(request);
  760. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  761. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  762. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  763. kFakeAuthorizedDomain
  764. ]);
  765. callback(mockGetProjectConfigResponse, nil);
  766. });
  767. });
  768. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  769. // Expect view controller presentation by UIDelegate.
  770. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  771. UIDelegate:mockUIDelegate
  772. callbackMatcher:OCMOCK_ANY
  773. completion:OCMOCK_ANY])
  774. .andDo(^(NSInvocation *invocation) {
  775. __unsafe_unretained id unretainedArgument;
  776. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  777. // `presentURL` is at index 2.
  778. [invocation getArgument:&unretainedArgument atIndex:2];
  779. NSURL *presentURL = unretainedArgument;
  780. XCTAssertEqualObjects(presentURL.scheme, @"https");
  781. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  782. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  783. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  784. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  785. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  786. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  787. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  788. XCTAssertNotNil(params[@"v"]);
  789. XCTAssertNil(params[@"tid"]);
  790. NSString *appCheckToken = presentURL.fragment;
  791. XCTAssertNil(appCheckToken);
  792. // `callbackMatcher` is at index 4
  793. [invocation getArgument:&unretainedArgument atIndex:4];
  794. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  795. NSMutableString *redirectURL =
  796. [NSMutableString stringWithString:kFakeRedirectURLBaseErrorString];
  797. // Add invalid client ID error to redirect URL.
  798. [redirectURL appendString:kInvalidClientIDString];
  799. // Verify that the URL is rejected by the callback matcher without the event ID.
  800. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  801. [redirectURL appendString:@"%26eventId%3D"];
  802. [redirectURL appendString:params[@"eventId"]];
  803. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  804. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  805. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  806. NSURLComponents *components = [originalComponents copy];
  807. components.query = @"https";
  808. XCTAssertFalse(callbackMatcher([components URL]));
  809. components = [originalComponents copy];
  810. components.host = @"badhost";
  811. XCTAssertFalse(callbackMatcher([components URL]));
  812. components = [originalComponents copy];
  813. components.path = @"badpath";
  814. XCTAssertFalse(callbackMatcher([components URL]));
  815. components = [originalComponents copy];
  816. components.query = @"badquery";
  817. XCTAssertFalse(callbackMatcher([components URL]));
  818. // `completion` is at index 5
  819. [invocation getArgument:&unretainedArgument atIndex:5];
  820. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  821. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  822. completion(originalComponents.URL, nil);
  823. });
  824. });
  825. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  826. [_provider getCredentialWithUIDelegate:mockUIDelegate
  827. completion:^(FIRAuthCredential *_Nullable credential,
  828. NSError *_Nullable error) {
  829. XCTAssertTrue([NSThread isMainThread]);
  830. XCTAssertNil(credential);
  831. XCTAssertEqual(FIRAuthErrorCodeInvalidClientID, error.code);
  832. [expectation fulfill];
  833. }];
  834. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  835. OCMVerifyAll(_mockBackend);
  836. }
  837. /** @fn testGetCredentialWithUIDelegateUnknownErrorWithClientID
  838. @brief Tests an unsuccessful invocation of @c getCredentialWithUIDelegte:completion: due to an
  839. unknown error.
  840. */
  841. - (void)testGetCredentialWithUIDelegateUnknownErrorWithClientID {
  842. id mockBundle = OCMClassMock([NSBundle class]);
  843. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  844. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  845. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  846. ]);
  847. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  848. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  849. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  850. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  851. .andCallBlock2(
  852. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  853. XCTAssertNotNil(request);
  854. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  855. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  856. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  857. kFakeAuthorizedDomain
  858. ]);
  859. callback(mockGetProjectConfigResponse, nil);
  860. });
  861. });
  862. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  863. // Expect view controller presentation by UIDelegate.
  864. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  865. UIDelegate:mockUIDelegate
  866. callbackMatcher:OCMOCK_ANY
  867. completion:OCMOCK_ANY])
  868. .andDo(^(NSInvocation *invocation) {
  869. __unsafe_unretained id unretainedArgument;
  870. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  871. // `presentURL` is at index 2.
  872. [invocation getArgument:&unretainedArgument atIndex:2];
  873. NSURL *presentURL = unretainedArgument;
  874. XCTAssertEqualObjects(presentURL.scheme, @"https");
  875. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  876. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  877. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  878. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  879. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  880. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  881. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  882. XCTAssertNotNil(params[@"v"]);
  883. XCTAssertNil(params[@"tid"]);
  884. NSString *appCheckToken = presentURL.fragment;
  885. XCTAssertNil(appCheckToken);
  886. // `callbackMatcher` is at index 4
  887. [invocation getArgument:&unretainedArgument atIndex:4];
  888. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  889. NSMutableString *redirectURL =
  890. [NSMutableString stringWithString:kFakeRedirectURLBaseErrorString];
  891. // Add unknown error to redirect URL.
  892. [redirectURL appendString:kUnknownErrorString];
  893. // Verify that the URL is rejected by the callback matcher without the event ID.
  894. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  895. [redirectURL appendString:@"%26eventId%3D"];
  896. [redirectURL appendString:params[@"eventId"]];
  897. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  898. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  899. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  900. NSURLComponents *components = [originalComponents copy];
  901. components.query = @"https";
  902. XCTAssertFalse(callbackMatcher([components URL]));
  903. components = [originalComponents copy];
  904. components.host = @"badhost";
  905. XCTAssertFalse(callbackMatcher([components URL]));
  906. components = [originalComponents copy];
  907. components.path = @"badpath";
  908. XCTAssertFalse(callbackMatcher([components URL]));
  909. components = [originalComponents copy];
  910. components.query = @"badquery";
  911. XCTAssertFalse(callbackMatcher([components URL]));
  912. // `completion` is at index 5
  913. [invocation getArgument:&unretainedArgument atIndex:5];
  914. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  915. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  916. completion(originalComponents.URL, nil);
  917. });
  918. });
  919. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  920. [_provider getCredentialWithUIDelegate:mockUIDelegate
  921. completion:^(FIRAuthCredential *_Nullable credential,
  922. NSError *_Nullable error) {
  923. XCTAssertTrue([NSThread isMainThread]);
  924. XCTAssertNil(credential);
  925. XCTAssertEqual(FIRAuthErrorCodeWebSignInUserInteractionFailure,
  926. error.code);
  927. [expectation fulfill];
  928. }];
  929. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  930. OCMVerifyAll(_mockBackend);
  931. }
  932. /** @fn testGetCredentialWithUIDelegateWithFirebaseAppID
  933. @brief Tests a successful invocation of @c getCredentialWithUIDelegte:completion:
  934. */
  935. - (void)testGetCredentialWithUIDelegateWithFirebaseAppID {
  936. id mockBundle = OCMClassMock([NSBundle class]);
  937. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  938. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  939. @{@"CFBundleURLSchemes" : @[ kFakeEncodedFirebaseAppID ]}
  940. ]);
  941. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  942. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  943. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  944. .andCallBlock2(
  945. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  946. XCTAssertNotNil(request);
  947. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  948. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  949. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  950. kFakeAuthorizedDomain
  951. ]);
  952. callback(mockGetProjectConfigResponse, nil);
  953. });
  954. });
  955. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  956. // Expect view controller presentation by UIDelegate.
  957. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  958. UIDelegate:mockUIDelegate
  959. callbackMatcher:OCMOCK_ANY
  960. completion:OCMOCK_ANY])
  961. .andDo(^(NSInvocation *invocation) {
  962. __unsafe_unretained id unretainedArgument;
  963. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  964. // `presentURL` is at index 2.
  965. [invocation getArgument:&unretainedArgument atIndex:2];
  966. NSURL *presentURL = unretainedArgument;
  967. XCTAssertEqualObjects(presentURL.scheme, @"https");
  968. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  969. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  970. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  971. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  972. XCTAssertEqualObjects(params[@"appId"], kFakeFirebaseAppID);
  973. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  974. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  975. XCTAssertNotNil(params[@"v"]);
  976. XCTAssertNil(params[@"tid"]);
  977. NSString *appCheckToken = presentURL.fragment;
  978. XCTAssertNil(appCheckToken);
  979. // `callbackMatcher` is at index 4
  980. [invocation getArgument:&unretainedArgument atIndex:4];
  981. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  982. NSMutableString *redirectURL = [NSMutableString
  983. stringWithString:[kFakeEncodedFirebaseAppID
  984. stringByAppendingString:kFakeRedirectURLResponseURL]];
  985. // Add fake OAuthResponse to callback.
  986. [redirectURL appendString:kFakeOAuthResponseURL];
  987. // Verify that the URL is rejected by the callback matcher without the event ID.
  988. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  989. [redirectURL appendString:@"%26eventId%3D"];
  990. [redirectURL appendString:params[@"eventId"]];
  991. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  992. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  993. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  994. NSURLComponents *components = [originalComponents copy];
  995. components.query = @"https";
  996. XCTAssertFalse(callbackMatcher([components URL]));
  997. components = [originalComponents copy];
  998. components.host = @"badhost";
  999. XCTAssertFalse(callbackMatcher([components URL]));
  1000. components = [originalComponents copy];
  1001. components.path = @"badpath";
  1002. XCTAssertFalse(callbackMatcher([components URL]));
  1003. components = [originalComponents copy];
  1004. components.query = @"badquery";
  1005. XCTAssertFalse(callbackMatcher([components URL]));
  1006. // `completion` is at index 5
  1007. [invocation getArgument:&unretainedArgument atIndex:5];
  1008. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  1009. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1010. completion(originalComponents.URL, nil);
  1011. });
  1012. });
  1013. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  1014. [_provider
  1015. getCredentialWithUIDelegate:mockUIDelegate
  1016. completion:^(FIRAuthCredential *_Nullable credential,
  1017. NSError *_Nullable error) {
  1018. XCTAssertTrue([NSThread isMainThread]);
  1019. XCTAssertNil(error);
  1020. XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
  1021. FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
  1022. XCTAssertEqualObjects(kFakeOAuthResponseURL,
  1023. OAuthCredential.OAuthResponseURLString);
  1024. [expectation fulfill];
  1025. }];
  1026. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  1027. OCMVerifyAll(_mockBackend);
  1028. }
  1029. /** @fn testGetCredentialWithUIDelegateWithFirebaseAppIDWhileClientIdPresent
  1030. @brief Tests a successful invocation of @c getCredentialWithUIDelegte:completion: when the
  1031. client ID is present in the plist file, but the encoded app ID is the registered custom URL
  1032. scheme.
  1033. */
  1034. - (void)testGetCredentialWithUIDelegateWithFirebaseAppIDWhileClientIdPresent {
  1035. id mockBundle = OCMClassMock([NSBundle class]);
  1036. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  1037. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  1038. @{@"CFBundleURLSchemes" : @[ kFakeEncodedFirebaseAppID ]}
  1039. ]);
  1040. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  1041. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  1042. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  1043. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  1044. .andCallBlock2(
  1045. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  1046. XCTAssertNotNil(request);
  1047. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1048. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  1049. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  1050. kFakeAuthorizedDomain
  1051. ]);
  1052. callback(mockGetProjectConfigResponse, nil);
  1053. });
  1054. });
  1055. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  1056. // Expect view controller presentation by UIDelegate.
  1057. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  1058. UIDelegate:mockUIDelegate
  1059. callbackMatcher:OCMOCK_ANY
  1060. completion:OCMOCK_ANY])
  1061. .andDo(^(NSInvocation *invocation) {
  1062. __unsafe_unretained id unretainedArgument;
  1063. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  1064. // `presentURL` is at index 2.
  1065. [invocation getArgument:&unretainedArgument atIndex:2];
  1066. NSURL *presentURL = unretainedArgument;
  1067. XCTAssertEqualObjects(presentURL.scheme, @"https");
  1068. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  1069. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  1070. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  1071. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  1072. XCTAssertEqualObjects(params[@"appId"], kFakeFirebaseAppID);
  1073. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  1074. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  1075. XCTAssertNotNil(params[@"v"]);
  1076. XCTAssertNil(params[@"tid"]);
  1077. NSString *appCheckToken = presentURL.fragment;
  1078. XCTAssertNil(appCheckToken);
  1079. // `callbackMatcher` is at index 4
  1080. [invocation getArgument:&unretainedArgument atIndex:4];
  1081. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  1082. NSMutableString *redirectURL = [NSMutableString
  1083. stringWithString:[kFakeEncodedFirebaseAppID
  1084. stringByAppendingString:kFakeRedirectURLResponseURL]];
  1085. // Add fake OAuthResponse to callback.
  1086. [redirectURL appendString:kFakeOAuthResponseURL];
  1087. // Verify that the URL is rejected by the callback matcher without the event ID.
  1088. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  1089. [redirectURL appendString:@"%26eventId%3D"];
  1090. [redirectURL appendString:params[@"eventId"]];
  1091. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  1092. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  1093. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  1094. NSURLComponents *components = [originalComponents copy];
  1095. components.query = @"https";
  1096. XCTAssertFalse(callbackMatcher([components URL]));
  1097. components = [originalComponents copy];
  1098. components.host = @"badhost";
  1099. XCTAssertFalse(callbackMatcher([components URL]));
  1100. components = [originalComponents copy];
  1101. components.path = @"badpath";
  1102. XCTAssertFalse(callbackMatcher([components URL]));
  1103. components = [originalComponents copy];
  1104. components.query = @"badquery";
  1105. XCTAssertFalse(callbackMatcher([components URL]));
  1106. // `completion` is at index 5
  1107. [invocation getArgument:&unretainedArgument atIndex:5];
  1108. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  1109. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1110. completion(originalComponents.URL, nil);
  1111. });
  1112. });
  1113. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  1114. [_provider
  1115. getCredentialWithUIDelegate:mockUIDelegate
  1116. completion:^(FIRAuthCredential *_Nullable credential,
  1117. NSError *_Nullable error) {
  1118. XCTAssertTrue([NSThread isMainThread]);
  1119. XCTAssertNil(error);
  1120. XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
  1121. FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
  1122. XCTAssertEqualObjects(kFakeOAuthResponseURL,
  1123. OAuthCredential.OAuthResponseURLString);
  1124. [expectation fulfill];
  1125. }];
  1126. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  1127. OCMVerifyAll(_mockBackend);
  1128. }
  1129. /** @fn testGetCredentialWithUIDelegateUseEmulator
  1130. @brief Tests a successful invocation of @c getCredentialWithUIDelegte:completion: when using the
  1131. emulator.
  1132. */
  1133. - (void)testGetCredentialWithUIDelegateUseEmulator {
  1134. id mockBundle = OCMClassMock([NSBundle class]);
  1135. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  1136. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  1137. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  1138. ]);
  1139. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  1140. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  1141. NSString *emulatorHostAndPort =
  1142. [NSString stringWithFormat:@"%@:%@", kFakeEmulatorHost, kFakeEmulatorPort];
  1143. OCMStub([_mockRequestConfiguration emulatorHostAndPort]).andReturn(emulatorHostAndPort);
  1144. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  1145. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  1146. // Expect view controller presentation by UIDelegate.
  1147. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  1148. UIDelegate:mockUIDelegate
  1149. callbackMatcher:OCMOCK_ANY
  1150. completion:OCMOCK_ANY])
  1151. .andDo(^(NSInvocation *invocation) {
  1152. __unsafe_unretained id unretainedArgument;
  1153. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  1154. // `presentURL` is at index 2.
  1155. [invocation getArgument:&unretainedArgument atIndex:2];
  1156. NSURL *presentURL = unretainedArgument;
  1157. XCTAssertEqualObjects(presentURL.scheme, @"http");
  1158. XCTAssertEqualObjects(presentURL.host, kFakeEmulatorHost);
  1159. XCTAssertEqualObjects([presentURL.port stringValue], kFakeEmulatorPort);
  1160. XCTAssertEqualObjects(presentURL.path, @"/emulator/auth/handler");
  1161. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  1162. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  1163. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  1164. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  1165. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  1166. XCTAssertNotNil(params[@"v"]);
  1167. XCTAssertNil(params[@"tid"]);
  1168. NSString *appCheckToken = presentURL.fragment;
  1169. XCTAssertNil(appCheckToken);
  1170. // `callbackMatcher` is at index 4
  1171. [invocation getArgument:&unretainedArgument atIndex:4];
  1172. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  1173. NSMutableString *redirectURL = [NSMutableString
  1174. stringWithString:[kFakeReverseClientID
  1175. stringByAppendingString:kFakeRedirectURLResponseURL]];
  1176. // Add fake OAuthResponse to callback.
  1177. [redirectURL appendString:kFakeOAuthResponseURL];
  1178. // Verify that the URL is rejected by the callback matcher without the event ID.
  1179. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  1180. [redirectURL appendString:@"%26eventId%3D"];
  1181. [redirectURL appendString:params[@"eventId"]];
  1182. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  1183. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  1184. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  1185. NSURLComponents *components = [originalComponents copy];
  1186. components.query = @"https";
  1187. XCTAssertFalse(callbackMatcher([components URL]));
  1188. components = [originalComponents copy];
  1189. components.host = @"badhost";
  1190. XCTAssertFalse(callbackMatcher([components URL]));
  1191. components = [originalComponents copy];
  1192. components.path = @"badpath";
  1193. XCTAssertFalse(callbackMatcher([components URL]));
  1194. components = [originalComponents copy];
  1195. components.query = @"badquery";
  1196. XCTAssertFalse(callbackMatcher([components URL]));
  1197. // `completion` is at index 5
  1198. [invocation getArgument:&unretainedArgument atIndex:5];
  1199. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  1200. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1201. completion(originalComponents.URL, nil);
  1202. });
  1203. });
  1204. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  1205. [_provider
  1206. getCredentialWithUIDelegate:mockUIDelegate
  1207. completion:^(FIRAuthCredential *_Nullable credential,
  1208. NSError *_Nullable error) {
  1209. XCTAssertTrue([NSThread isMainThread]);
  1210. XCTAssertNil(error);
  1211. XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
  1212. FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
  1213. XCTAssertEqualObjects(kFakeOAuthResponseURL,
  1214. OAuthCredential.OAuthResponseURLString);
  1215. [expectation fulfill];
  1216. }];
  1217. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  1218. OCMVerifyAll(_mockBackend);
  1219. }
  1220. /** @fn testGetCredentialWithUIDelegateWithAppCheckToken
  1221. @brief Tests a successful invocation of @c getCredentialWithUIDelegte:completion:
  1222. */
  1223. - (void)testGetCredentialWithUIDelegateWithAppCheckToken {
  1224. id mockBundle = OCMClassMock([NSBundle class]);
  1225. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  1226. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  1227. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  1228. ]);
  1229. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  1230. OCMStub([_mockAuth tenantID]).andReturn(kFakeTenantID);
  1231. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  1232. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  1233. FIRFakeAppCheck *fakeAppCheck = [[FIRFakeAppCheck alloc] init];
  1234. OCMStub([_mockRequestConfiguration appCheck]).andReturn(fakeAppCheck);
  1235. OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
  1236. .andCallBlock2(
  1237. ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
  1238. XCTAssertNotNil(request);
  1239. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1240. id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
  1241. OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
  1242. kFakeAuthorizedDomain
  1243. ]);
  1244. callback(mockGetProjectConfigResponse, nil);
  1245. });
  1246. });
  1247. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  1248. // Expect view controller presentation by UIDelegate.
  1249. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  1250. UIDelegate:mockUIDelegate
  1251. callbackMatcher:OCMOCK_ANY
  1252. completion:OCMOCK_ANY])
  1253. .andDo(^(NSInvocation *invocation) {
  1254. __unsafe_unretained id unretainedArgument;
  1255. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  1256. // `presentURL` is at index 2.
  1257. [invocation getArgument:&unretainedArgument atIndex:2];
  1258. NSURL *presentURL = unretainedArgument;
  1259. XCTAssertEqualObjects(presentURL.scheme, @"https");
  1260. XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
  1261. XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
  1262. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  1263. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  1264. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  1265. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  1266. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  1267. XCTAssertEqualObjects(params[@"tid"], kFakeTenantID);
  1268. XCTAssertNotNil(params[@"v"]);
  1269. NSString *appCheckToken = presentURL.fragment;
  1270. XCTAssertEqualObjects(appCheckToken, [@"fac=" stringByAppendingString:kFakeAppCheckToken]);
  1271. // `callbackMatcher` is at index 4
  1272. [invocation getArgument:&unretainedArgument atIndex:4];
  1273. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  1274. NSMutableString *redirectURL = [NSMutableString
  1275. stringWithString:[kFakeReverseClientID
  1276. stringByAppendingString:kFakeRedirectURLResponseURL]];
  1277. // Add fake OAuthResponse to callback.
  1278. [redirectURL appendString:kFakeOAuthResponseURL];
  1279. // Verify that the URL is rejected by the callback matcher without the event ID.
  1280. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  1281. [redirectURL appendString:@"%26eventId%3D"];
  1282. [redirectURL appendString:params[@"eventId"]];
  1283. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  1284. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  1285. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  1286. NSURLComponents *components = [originalComponents copy];
  1287. components.query = @"https";
  1288. XCTAssertFalse(callbackMatcher([components URL]));
  1289. components = [originalComponents copy];
  1290. components.host = @"badhost";
  1291. XCTAssertFalse(callbackMatcher([components URL]));
  1292. components = [originalComponents copy];
  1293. components.path = @"badpath";
  1294. XCTAssertFalse(callbackMatcher([components URL]));
  1295. components = [originalComponents copy];
  1296. components.query = @"badquery";
  1297. XCTAssertFalse(callbackMatcher([components URL]));
  1298. // `completion` is at index 5
  1299. [invocation getArgument:&unretainedArgument atIndex:5];
  1300. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  1301. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  1302. completion(originalComponents.URL, nil);
  1303. });
  1304. });
  1305. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  1306. [_provider
  1307. getCredentialWithUIDelegate:mockUIDelegate
  1308. completion:^(FIRAuthCredential *_Nullable credential,
  1309. NSError *_Nullable error) {
  1310. XCTAssertTrue([NSThread isMainThread]);
  1311. XCTAssertNil(error);
  1312. XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
  1313. FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
  1314. XCTAssertEqualObjects(kFakeOAuthResponseURL,
  1315. OAuthCredential.OAuthResponseURLString);
  1316. [expectation fulfill];
  1317. }];
  1318. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  1319. OCMVerifyAll(_mockBackend);
  1320. }
  1321. #endif // !defined(TARGET_OS_VISION) || !TARGET_OS_VISION
  1322. @end
  1323. #endif // TARGET_OS_IOS