FIROAuthProviderTests.m 57 KB

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