FIROAuthProviderTests.m 36 KB

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