FIROAuthProviderTests.m 70 KB

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