FIROAuthProviderTests.m 39 KB

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