FIROAuthProviderTests.m 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <TargetConditionals.h>
  17. #if TARGET_OS_IOS
  18. #import <XCTest/XCTest.h>
  19. #import "OCMock.h"
  20. #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRAuthErrors.h"
  21. #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRAuthUIDelegate.h"
  22. #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIROAuthProvider.h"
  23. #import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
  24. #import "FirebaseAuth/Sources/Auth/FIRAuthGlobalWorkQueue.h"
  25. #import "FirebaseAuth/Sources/Auth/FIRAuth_Internal.h"
  26. #import "FirebaseAuth/Sources/AuthProvider/OAuth/FIROAuthCredential_Internal.h"
  27. #import "FirebaseAuth/Sources/Backend/FIRAuthBackend.h"
  28. #import "FirebaseAuth/Sources/Backend/FIRAuthRequestConfiguration.h"
  29. #import "FirebaseAuth/Sources/Backend/RPC/FIRGetProjectConfigRequest.h"
  30. #import "FirebaseAuth/Sources/Backend/RPC/FIRGetProjectConfigResponse.h"
  31. #import "FirebaseAuth/Sources/Utilities/FIRAuthErrorUtils.h"
  32. #import "FirebaseAuth/Sources/Utilities/FIRAuthURLPresenter.h"
  33. #import "FirebaseAuth/Sources/Utilities/FIRAuthWebUtils.h"
  34. #import "FirebaseAuth/Tests/Unit/OCMStubRecorder+FIRAuthUnitTests.h"
  35. /** @var kExpectationTimeout
  36. @brief The maximum time waiting for expectations to fulfill.
  37. */
  38. static const NSTimeInterval kExpectationTimeout = 1;
  39. /** @var kFakeAuthorizedDomain
  40. @brief A fake authorized domain for the app.
  41. */
  42. static NSString *const kFakeAuthorizedDomain = @"test.firebaseapp.com";
  43. /** @var kFakeBundleID
  44. @brief A fake bundle ID.
  45. */
  46. static NSString *const kFakeBundleID = @"com.firebaseapp.example";
  47. /** @var kFakeAccessToken
  48. @brief A fake access token for testing.
  49. */
  50. static NSString *const kFakeAccessToken = @"fakeAccessToken";
  51. /** @var kFakeIDToken
  52. @brief A fake ID token for testing.
  53. */
  54. static NSString *const kFakeIDToken = @"fakeIDToken";
  55. /** @var kFakeProviderID
  56. @brief A fake provider ID for testing.
  57. */
  58. static NSString *const kFakeProviderID = @"fakeProviderID";
  59. /** @var kFakeAPIKey
  60. @brief A fake API key.
  61. */
  62. static NSString *const kFakeAPIKey = @"asdfghjkl";
  63. /** @var 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. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  234. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  235. id mockBundle = OCMClassMock([NSBundle class]);
  236. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  237. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  238. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  239. ]);
  240. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  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. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  330. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  331. id mockBundle = OCMClassMock([NSBundle class]);
  332. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  333. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  334. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  335. ]);
  336. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  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. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  422. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  423. id mockBundle = OCMClassMock([NSBundle class]);
  424. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  425. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  426. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  427. ]);
  428. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  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. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  512. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  513. id mockBundle = OCMClassMock([NSBundle class]);
  514. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  515. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  516. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  517. ]);
  518. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  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. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  603. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  604. id mockBundle = OCMClassMock([NSBundle class]);
  605. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  606. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  607. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  608. ]);
  609. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  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. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  694. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  695. id mockBundle = OCMClassMock([NSBundle class]);
  696. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  697. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  698. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  699. ]);
  700. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  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. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  785. id mockBundle = OCMClassMock([NSBundle class]);
  786. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  787. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  788. @{@"CFBundleURLSchemes" : @[ kFakeEncodedFirebaseAppID ]}
  789. ]);
  790. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  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 testGetCredentialWithUIDelegateUseEmulator
  875. @brief Tests a successful invocation of @c getCredentialWithUIDelegte:completion: when using the
  876. emulator.
  877. */
  878. - (void)testGetCredentialWithUIDelegateUseEmulator {
  879. OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
  880. NSString *emulatorHostAndPort =
  881. [NSString stringWithFormat:@"%@:%@", kFakeEmulatorHost, kFakeEmulatorPort];
  882. OCMStub([_mockRequestConfiguration emulatorHostAndPort]).andReturn(emulatorHostAndPort);
  883. _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
  884. id mockBundle = OCMClassMock([NSBundle class]);
  885. OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
  886. OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
  887. @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
  888. ]);
  889. OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
  890. id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
  891. // Expect view controller presentation by UIDelegate.
  892. OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
  893. UIDelegate:mockUIDelegate
  894. callbackMatcher:OCMOCK_ANY
  895. completion:OCMOCK_ANY])
  896. .andDo(^(NSInvocation *invocation) {
  897. __unsafe_unretained id unretainedArgument;
  898. // Indices 0 and 1 indicate the hidden arguments self and _cmd.
  899. // `presentURL` is at index 2.
  900. [invocation getArgument:&unretainedArgument atIndex:2];
  901. NSURL *presentURL = unretainedArgument;
  902. XCTAssertEqualObjects(presentURL.scheme, @"http");
  903. XCTAssertEqualObjects(presentURL.host, kFakeEmulatorHost);
  904. XCTAssertEqualObjects([presentURL.port stringValue], kFakeEmulatorPort);
  905. XCTAssertEqualObjects(presentURL.path, @"/emulator/auth/handler");
  906. NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
  907. XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
  908. XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
  909. XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
  910. XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
  911. XCTAssertNotNil(params[@"v"]);
  912. // `callbackMatcher` is at index 4
  913. [invocation getArgument:&unretainedArgument atIndex:4];
  914. FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
  915. NSMutableString *redirectURL = [NSMutableString
  916. stringWithString:[kFakeReverseClientID
  917. stringByAppendingString:kFakeRedirectURLResponseURL]];
  918. // Add fake OAuthResponse to callback.
  919. [redirectURL appendString:kFakeOAuthResponseURL];
  920. // Verify that the URL is rejected by the callback matcher without the event ID.
  921. XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
  922. [redirectURL appendString:@"%26eventId%3D"];
  923. [redirectURL appendString:params[@"eventId"]];
  924. NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
  925. // Verify that the URL is accepted by the callback matcher with the matching event ID.
  926. XCTAssertTrue(callbackMatcher([originalComponents URL]));
  927. NSURLComponents *components = [originalComponents copy];
  928. components.query = @"https";
  929. XCTAssertFalse(callbackMatcher([components URL]));
  930. components = [originalComponents copy];
  931. components.host = @"badhost";
  932. XCTAssertFalse(callbackMatcher([components URL]));
  933. components = [originalComponents copy];
  934. components.path = @"badpath";
  935. XCTAssertFalse(callbackMatcher([components URL]));
  936. components = [originalComponents copy];
  937. components.query = @"badquery";
  938. XCTAssertFalse(callbackMatcher([components URL]));
  939. // `completion` is at index 5
  940. [invocation getArgument:&unretainedArgument atIndex:5];
  941. FIRAuthURLPresentationCompletion completion = unretainedArgument;
  942. dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
  943. completion(originalComponents.URL, nil);
  944. });
  945. });
  946. XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
  947. [_provider
  948. getCredentialWithUIDelegate:mockUIDelegate
  949. completion:^(FIRAuthCredential *_Nullable credential,
  950. NSError *_Nullable error) {
  951. XCTAssertTrue([NSThread isMainThread]);
  952. XCTAssertNil(error);
  953. XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
  954. FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
  955. XCTAssertEqualObjects(kFakeOAuthResponseURL,
  956. OAuthCredential.OAuthResponseURLString);
  957. [expectation fulfill];
  958. }];
  959. [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
  960. OCMVerifyAll(_mockBackend);
  961. }
  962. @end
  963. #endif