FirebaseAuthApiTests.m 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  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 <Foundation/Foundation.h>
  17. #import <XCTest/XCTest.h>
  18. #import <FirebaseCore/FIRApp.h>
  19. #import "FirebaseAuth.h"
  20. #import "AuthCredentials.h"
  21. #ifdef NO_NETWORK
  22. #import "ITUIOSTestUtil.h"
  23. #import "ioReplayer/IORManager.h"
  24. #import "ioReplayer/IORTestCase.h"
  25. #endif
  26. #import <GTMSessionFetcher/GTMSessionFetcher.h>
  27. #import <GTMSessionFetcher/GTMSessionFetcherService.h>
  28. /** The user name string for Custom Auth testing account. */
  29. static NSString *const kCustomAuthTestingAccountUserID = KCUSTOM_AUTH_USER_ID;
  30. /** The url for obtaining a valid custom token string used to test Custom Auth. */
  31. static NSString *const kCustomTokenUrl = KCUSTOM_AUTH_TOKEN_URL;
  32. /** Facebook app access token that will be used for Facebook Graph API, which is different from
  33. * account access token.
  34. */
  35. static NSString *const kFacebookAppAccessToken = KFACEBOOK_APP_ACCESS_TOKEN;
  36. /** Facebook app ID that will be used for Facebook Graph API. */
  37. static NSString *const kFacebookAppID = KFACEBOOK_APP_ID;
  38. static NSString *const kFacebookGraphApiAuthority = @"graph.facebook.com";
  39. static NSString *const kFacebookTestAccountName = KFACEBOOK_USER_NAME;
  40. static NSString *const kGoogleTestAccountName = KGOOGLE_USER_NAME;
  41. /** The invalid custom token string for testing Custom Auth. */
  42. static NSString *const kInvalidCustomToken = @"invalid token.";
  43. /** The testing email address for testCreateAccountWithEmailAndPassword. */
  44. static NSString *const kTestingEmailToCreateUser = @"abc@xyz.com";
  45. /** The testing email address for testSignInExistingUserWithEmailAndPassword. */
  46. static NSString *const kExistingTestingEmailToSignIn = @"456@abc.com";
  47. /** Error message for invalid custom token sign in. */
  48. NSString *kInvalidTokenErrorMessage =
  49. @"The custom token format is incorrect. Please check the documentation.";
  50. NSString *kGoogleCliendId = KGOOGLE_CLIENT_ID;
  51. /** Refresh token of Google test account to exchange for access token. Refresh token never expires
  52. * unless user revokes it. If this refresh token expires, tests in record mode will fail and this
  53. * token needs to be updated.
  54. */
  55. NSString *kGoogleTestAccountRefreshToken = KGOOGLE_TEST_ACCOUNT_REFRESH_TOKEN;
  56. static NSTimeInterval const kExpectationsTimeout = 30;
  57. #ifdef NO_NETWORK
  58. #define SKIP_IF_ON_MOBILE_HARNESS \
  59. if ([ITUIOSTestUtil isOnMobileHarness]) { \
  60. NSLog(@"Skipping '%@' on mobile harness", NSStringFromSelector(_cmd)); \
  61. return; \
  62. }
  63. #else
  64. #define SKIP_IF_ON_MOBILE_HARNESS
  65. #endif
  66. #ifdef NO_NETWORK
  67. @interface ApiTests : IORTestCase
  68. #else
  69. @interface ApiTests : XCTestCase
  70. #endif
  71. @end
  72. @implementation ApiTests
  73. /** To reset the app so that each test sees the app in a clean state. */
  74. - (void)setUp {
  75. [super setUp];
  76. [self signOut];
  77. }
  78. #pragma mark - Tests
  79. /**
  80. * This test runs in replay mode by default. To run in a different mode follow the instructions
  81. * below.
  82. *
  83. * Blaze: --test_arg=\'--networkReplayMode=(replay|record|disabled|observe)\'
  84. *
  85. * Xcode:
  86. * Update the following flag in the xcscheme.
  87. * --networkReplayMode=(replay|record|disabled|observe)
  88. */
  89. - (void)testCreateAccountWithEmailAndPassword {
  90. SKIP_IF_ON_MOBILE_HARNESS
  91. FIRAuth *auth = [FIRAuth auth];
  92. if (!auth) {
  93. XCTFail(@"Could not obtain auth object.");
  94. }
  95. XCTestExpectation *expectation =
  96. [self expectationWithDescription:@"Created account with email and password."];
  97. [auth createUserWithEmail:kTestingEmailToCreateUser
  98. password:@"password"
  99. completion:^(FIRUser *user, NSError *error) {
  100. if (error) {
  101. NSLog(@"createUserWithEmail has error: %@", error);
  102. }
  103. [expectation fulfill];
  104. }];
  105. [self waitForExpectationsWithTimeout:kExpectationsTimeout
  106. handler:^(NSError *error) {
  107. if (error != nil) {
  108. XCTFail(@"Failed to wait for expectations "
  109. @"in creating account. Error: %@",
  110. error.localizedDescription);
  111. }
  112. }];
  113. XCTAssertEqualObjects(auth.currentUser.email, kTestingEmailToCreateUser);
  114. // Clean up the created Firebase user for future runs.
  115. [self deleteCurrentFirebaseUser];
  116. }
  117. - (void)testLinkAnonymousAccountToFacebookAccount {
  118. FIRAuth *auth = [FIRAuth auth];
  119. if (!auth) {
  120. XCTFail(@"Could not obtain auth object.");
  121. }
  122. [self signInAnonymously];
  123. NSDictionary *userInfoDict = [self createFacebookTestingAccount];
  124. NSString *facebookAccessToken = userInfoDict[@"access_token"];
  125. NSLog(@"Facebook testing account access token is: %@", facebookAccessToken);
  126. NSString *facebookAccountId = userInfoDict[@"id"];
  127. NSLog(@"Facebook testing account id is: %@", facebookAccountId);
  128. FIRAuthCredential *credential =
  129. [FIRFacebookAuthProvider credentialWithAccessToken:facebookAccessToken];
  130. XCTestExpectation *expectation = [self expectationWithDescription:@"Facebook linking finished."];
  131. [auth.currentUser linkWithCredential:credential
  132. completion:^(FIRUser *user, NSError *error) {
  133. if (error) {
  134. NSLog(@"Link to Facebok error: %@", error);
  135. }
  136. [expectation fulfill];
  137. }];
  138. [self waitForExpectationsWithTimeout:kExpectationsTimeout
  139. handler:^(NSError *error) {
  140. if (error != nil) {
  141. XCTFail(@"Failed to wait for expectations "
  142. @"in linking to Facebook. Error: %@",
  143. error.localizedDescription);
  144. }
  145. }];
  146. NSArray<id<FIRUserInfo>> *providerData = auth.currentUser.providerData;
  147. XCTAssertEqual([providerData count], 1);
  148. XCTAssertEqualObjects([providerData[0] providerID], @"facebook.com");
  149. // Clean up the created Firebase/Facebook user for future runs.
  150. [self deleteCurrentFirebaseUser];
  151. [self deleteFacebookTestingAccountbyId:facebookAccountId];
  152. }
  153. - (void)testSignInAnonymously {
  154. [self signInAnonymously];
  155. XCTAssertTrue([FIRAuth auth].currentUser.anonymous);
  156. [self deleteCurrentFirebaseUser];
  157. }
  158. - (void)testSignInExistingUserWithEmailAndPassword {
  159. FIRAuth *auth = [FIRAuth auth];
  160. if (!auth) {
  161. XCTFail(@"Could not obtain auth object.");
  162. }
  163. XCTestExpectation *expectation =
  164. [self expectationWithDescription:@"Signed in existing account with email and password."];
  165. [auth signInWithEmail:kExistingTestingEmailToSignIn
  166. password:@"password"
  167. completion:^(FIRUser *user, NSError *error) {
  168. if (error) {
  169. NSLog(@"Signing in existing account has error: %@", error);
  170. }
  171. [expectation fulfill];
  172. }];
  173. [self waitForExpectationsWithTimeout:kExpectationsTimeout
  174. handler:^(NSError *error) {
  175. if (error != nil) {
  176. XCTFail(@"Failed to wait for expectations "
  177. @"in signing in existing account. Error: %@",
  178. error.localizedDescription);
  179. }
  180. }];
  181. XCTAssertEqualObjects(auth.currentUser.email, kExistingTestingEmailToSignIn);
  182. }
  183. - (void)testSignInWithValidCustomAuthToken {
  184. FIRAuth *auth = [FIRAuth auth];
  185. if (!auth) {
  186. XCTFail(@"Could not obtain auth object.");
  187. }
  188. NSError *error;
  189. NSString *customToken = [NSString stringWithContentsOfURL:[NSURL URLWithString:kCustomTokenUrl]
  190. encoding:NSUTF8StringEncoding
  191. error:&error];
  192. if (!customToken) {
  193. XCTFail(@"There was an error retrieving the custom token: %@", error);
  194. }
  195. NSLog(@"The valid token is: %@", customToken);
  196. XCTestExpectation *expectation =
  197. [self expectationWithDescription:@"CustomAuthToken sign-in finished."];
  198. [auth signInWithCustomToken:customToken
  199. completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
  200. if (error) {
  201. NSLog(@"Valid token sign in error: %@", error);
  202. }
  203. [expectation fulfill];
  204. }];
  205. [self waitForExpectationsWithTimeout:kExpectationsTimeout
  206. handler:^(NSError *error) {
  207. if (error != nil) {
  208. XCTFail(@"Failed to wait for expectations "
  209. @"in CustomAuthToken sign in. Error: %@",
  210. error.localizedDescription);
  211. }
  212. }];
  213. XCTAssertEqualObjects(auth.currentUser.uid, kCustomAuthTestingAccountUserID);
  214. }
  215. - (void)testSignInWithInvalidCustomAuthToken {
  216. FIRAuth *auth = [FIRAuth auth];
  217. if (!auth) {
  218. XCTFail(@"Could not obtain auth object.");
  219. }
  220. XCTestExpectation *expectation =
  221. [self expectationWithDescription:@"Invalid CustomAuthToken sign-in finished."];
  222. [auth signInWithCustomToken:kInvalidCustomToken
  223. completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
  224. XCTAssertEqualObjects(error.localizedDescription, kInvalidTokenErrorMessage);
  225. [expectation fulfill];
  226. }];
  227. [self waitForExpectationsWithTimeout:kExpectationsTimeout
  228. handler:^(NSError *error) {
  229. if (error != nil) {
  230. XCTFail(@"Failed to wait for expectations "
  231. @"in CustomAuthToken sign in. Error: %@",
  232. error.localizedDescription);
  233. }
  234. }];
  235. }
  236. - (void)testSignInWithFaceboook {
  237. FIRAuth *auth = [FIRAuth auth];
  238. if (!auth) {
  239. XCTFail(@"Could not obtain auth object.");
  240. }
  241. NSDictionary *userInfoDict = [self createFacebookTestingAccount];
  242. NSString *facebookAccessToken = userInfoDict[@"access_token"];
  243. NSLog(@"Facebook testing account access token is: %@", facebookAccessToken);
  244. NSString *facebookAccountId = userInfoDict[@"id"];
  245. NSLog(@"Facebook testing account id is: %@", facebookAccountId);
  246. FIRAuthCredential *credential =
  247. [FIRFacebookAuthProvider credentialWithAccessToken:facebookAccessToken];
  248. XCTestExpectation *expectation = [self expectationWithDescription:@"Facebook sign-in finished."];
  249. [auth signInWithCredential:credential
  250. completion:^(FIRUser *user, NSError *error) {
  251. if (error) {
  252. NSLog(@"Facebook sign in error: %@", error);
  253. }
  254. [expectation fulfill];
  255. }];
  256. [self waitForExpectationsWithTimeout:kExpectationsTimeout
  257. handler:^(NSError *error) {
  258. if (error != nil) {
  259. XCTFail(@"Failed to wait for expectations "
  260. @"in Facebook sign in. Error: %@",
  261. error.localizedDescription);
  262. }
  263. }];
  264. XCTAssertEqualObjects(auth.currentUser.displayName, kFacebookTestAccountName);
  265. // Clean up the created Firebase/Facebook user for future runs.
  266. [self deleteCurrentFirebaseUser];
  267. [self deleteFacebookTestingAccountbyId:facebookAccountId];
  268. }
  269. - (void)testSignInWithGoogle {
  270. FIRAuth *auth = [FIRAuth auth];
  271. if (!auth) {
  272. XCTFail(@"Could not obtain auth object.");
  273. }
  274. NSDictionary *userInfoDict = [self getGoogleAccessToken];
  275. NSString *googleAccessToken = userInfoDict[@"access_token"];
  276. NSString *googleIdToken = userInfoDict[@"id_token"];
  277. FIRAuthCredential *credential =
  278. [FIRGoogleAuthProvider credentialWithIDToken:googleIdToken accessToken:googleAccessToken];
  279. XCTestExpectation *expectation =
  280. [self expectationWithDescription:@"Signing in with Google finished."];
  281. [auth signInWithCredential:credential
  282. completion:^(FIRUser *user, NSError *error) {
  283. if (error) {
  284. NSLog(@"Signing in with Google had error: %@", error);
  285. }
  286. [expectation fulfill];
  287. }];
  288. [self waitForExpectationsWithTimeout:kExpectationsTimeout
  289. handler:^(NSError *error) {
  290. if (error != nil) {
  291. XCTFail(@"Failed to wait for expectations "
  292. @"in Signing in with Google. Error: %@",
  293. error.localizedDescription);
  294. }
  295. }];
  296. XCTAssertEqualObjects(auth.currentUser.displayName, kGoogleTestAccountName);
  297. // Clean up the created Firebase/Facebook user for future runs.
  298. [self deleteCurrentFirebaseUser];
  299. }
  300. #pragma mark - Helpers
  301. /** Sign out current account. */
  302. - (void)signOut {
  303. NSError *signOutError;
  304. BOOL status = [[FIRAuth auth] signOut:&signOutError];
  305. // Just log the error because we don't want to fail the test if signing out
  306. // fails.
  307. if (!status) {
  308. NSLog(@"Error signing out: %@", signOutError);
  309. }
  310. }
  311. /** Creates a Facebook testing account using Facebook Graph API and return a dictionary that
  312. * constains "id", "access_token", "login_url", "email" and "password" of the created account.
  313. */
  314. - (NSDictionary *)createFacebookTestingAccount {
  315. // Build the URL.
  316. NSString *urltoCreateTestUser =
  317. [NSString stringWithFormat:@"https://%@/%@/accounts/test-users", kFacebookGraphApiAuthority,
  318. kFacebookAppID];
  319. // Build the POST request.
  320. NSString *bodyString =
  321. [NSString stringWithFormat:@"installed=true&name=%@&permissions=read_stream&access_token=%@",
  322. kFacebookTestAccountName, kFacebookAppAccessToken];
  323. NSData *postData = [bodyString dataUsingEncoding:NSUTF8StringEncoding];
  324. GTMSessionFetcherService *service = [[GTMSessionFetcherService alloc] init];
  325. GTMSessionFetcher *fetcher = [service fetcherWithURLString:urltoCreateTestUser];
  326. fetcher.bodyData = postData;
  327. [fetcher setRequestValue:@"text/plain" forHTTPHeaderField:@"Content-Type"];
  328. XCTestExpectation *expectation =
  329. [self expectationWithDescription:@"Creating Facebook account finished."];
  330. __block NSData *data = nil;
  331. [fetcher beginFetchWithCompletionHandler:^(NSData *receivedData, NSError *error) {
  332. if (error) {
  333. NSLog(@"Creating Facebook account finished with error: %@", error);
  334. return;
  335. }
  336. data = receivedData;
  337. [expectation fulfill];
  338. }];
  339. [self waitForExpectationsWithTimeout:kExpectationsTimeout
  340. handler:^(NSError *error) {
  341. if (error != nil) {
  342. XCTFail(@"Failed to wait for expectations "
  343. @"in creating Facebook account. Error: %@",
  344. error.localizedDescription);
  345. }
  346. }];
  347. NSString *userInfo = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  348. NSLog(@"The info of created Facebook testing account is: %@", userInfo);
  349. // Parses the access token from the JSON data.
  350. NSDictionary *userInfoDict =
  351. [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
  352. return userInfoDict;
  353. }
  354. /** Clean up the created user for tests' future runs. */
  355. - (void)deleteCurrentFirebaseUser {
  356. FIRAuth *auth = [FIRAuth auth];
  357. if (!auth) {
  358. NSLog(@"Could not obtain auth object.");
  359. }
  360. XCTestExpectation *expectation =
  361. [self expectationWithDescription:@"Delete current user finished."];
  362. [auth.currentUser deleteWithCompletion:^(NSError *_Nullable error) {
  363. if (error) {
  364. XCTFail(@"Failed to delete user. Error: %@.", error);
  365. }
  366. [expectation fulfill];
  367. }];
  368. [self waitForExpectationsWithTimeout:kExpectationsTimeout
  369. handler:^(NSError *error) {
  370. if (error != nil) {
  371. XCTFail(@"Failed to wait for expectations "
  372. @"in deleting user. Error: %@",
  373. error.localizedDescription);
  374. }
  375. }];
  376. }
  377. - (void)signInAnonymously {
  378. FIRAuth *auth = [FIRAuth auth];
  379. if (!auth) {
  380. XCTFail(@"Could not obtain auth object.");
  381. }
  382. XCTestExpectation *expectation =
  383. [self expectationWithDescription:@"Anonymousy sign-in finished."];
  384. [auth signInAnonymouslyWithCompletion:^(FIRUser *user, NSError *error) {
  385. if (error) {
  386. NSLog(@"Anonymousy sign in error: %@", error);
  387. }
  388. [expectation fulfill];
  389. }];
  390. [self waitForExpectationsWithTimeout:kExpectationsTimeout
  391. handler:^(NSError *error) {
  392. if (error != nil) {
  393. XCTFail(@"Failed to wait for expectations "
  394. @"in anonymousy sign in. Error: %@",
  395. error.localizedDescription);
  396. }
  397. }];
  398. }
  399. /** Delete a Facebook testing account by account Id using Facebook Graph API. */
  400. - (void)deleteFacebookTestingAccountbyId:(NSString *)accountId {
  401. // Build the URL.
  402. NSString *urltoDeleteTestUser =
  403. [NSString stringWithFormat:@"https://%@/%@", kFacebookGraphApiAuthority, accountId];
  404. // Build the POST request.
  405. NSString *bodyString =
  406. [NSString stringWithFormat:@"method=delete&access_token=%@", kFacebookAppAccessToken];
  407. NSData *postData = [bodyString dataUsingEncoding:NSUTF8StringEncoding];
  408. GTMSessionFetcherService *service = [[GTMSessionFetcherService alloc] init];
  409. GTMSessionFetcher *fetcher = [service fetcherWithURLString:urltoDeleteTestUser];
  410. fetcher.bodyData = postData;
  411. [fetcher setRequestValue:@"text/plain" forHTTPHeaderField:@"Content-Type"];
  412. XCTestExpectation *expectation =
  413. [self expectationWithDescription:@"Deleting Facebook account finished."];
  414. [fetcher beginFetchWithCompletionHandler:^(NSData *receivedData, NSError *error) {
  415. NSString *deleteResult =
  416. [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
  417. NSLog(@"The result of deleting Facebook account is: %@", deleteResult);
  418. if (error) {
  419. NSLog(@"Deleting Facebook account finished with error: %@", error);
  420. }
  421. [expectation fulfill];
  422. }];
  423. [self waitForExpectationsWithTimeout:kExpectationsTimeout
  424. handler:^(NSError *error) {
  425. if (error != nil) {
  426. XCTFail(@"Failed to wait for expectations "
  427. @"in deleting Facebook account. Error: %@",
  428. error.localizedDescription);
  429. }
  430. }];
  431. }
  432. /** Sends http request to Google OAuth2 token server to use refresh token to exchange for Google
  433. * access token. Returns a dictionary that constains "access_token", "token_type", "expires_in" and
  434. * "id_token".
  435. */
  436. - (NSDictionary *)getGoogleAccessToken {
  437. NSString *googleOauth2TokenServerUrl = @"https://www.googleapis.com/oauth2/v4/token";
  438. NSString *bodyString =
  439. [NSString stringWithFormat:@"client_id=%@&grant_type=refresh_token&refresh_token=%@",
  440. kGoogleCliendId, kGoogleTestAccountRefreshToken];
  441. NSData *postData = [bodyString dataUsingEncoding:NSUTF8StringEncoding];
  442. GTMSessionFetcherService *service = [[GTMSessionFetcherService alloc] init];
  443. GTMSessionFetcher *fetcher = [service fetcherWithURLString:googleOauth2TokenServerUrl];
  444. fetcher.bodyData = postData;
  445. [fetcher setRequestValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
  446. XCTestExpectation *expectation =
  447. [self expectationWithDescription:@"Exchanging Google account tokens finished."];
  448. __block NSData *data = nil;
  449. [fetcher beginFetchWithCompletionHandler:^(NSData *receivedData, NSError *error) {
  450. if (error) {
  451. NSLog(@"Exchanging Google account tokens finished with error: %@", error);
  452. return;
  453. }
  454. data = receivedData;
  455. [expectation fulfill];
  456. }];
  457. [self waitForExpectationsWithTimeout:kExpectationsTimeout
  458. handler:^(NSError *error) {
  459. if (error != nil) {
  460. XCTFail(@"Failed to wait for expectations "
  461. @"in exchanging Google account tokens. Error: %@",
  462. error.localizedDescription);
  463. }
  464. }];
  465. NSString *userInfo = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  466. NSLog(@"The info of exchanged result is: %@", userInfo);
  467. NSDictionary *userInfoDict =
  468. [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
  469. return userInfoDict;
  470. }
  471. @end