From 5484ab18238a5fcad2776be5daed222f55266ec2 Mon Sep 17 00:00:00 2001 From: Narges Simjour Date: Mon, 16 Dec 2019 20:17:55 -0500 Subject: [PATCH 1/5] Add API to look up users by federated ID. --- .../google/firebase/auth/FirebaseAuth.java | 50 ++++++ .../firebase/auth/FirebaseUserManager.java | 17 ++ .../google/firebase/auth/FirebaseAuthIT.java | 154 +++++++++++++++--- .../snippets/FirebaseAuthSnippets.java | 10 ++ 4 files changed, 208 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/google/firebase/auth/FirebaseAuth.java b/src/main/java/com/google/firebase/auth/FirebaseAuth.java index 4c488cfea..0dcf7b695 100644 --- a/src/main/java/com/google/firebase/auth/FirebaseAuth.java +++ b/src/main/java/com/google/firebase/auth/FirebaseAuth.java @@ -605,6 +605,56 @@ protected UserRecord execute() throws FirebaseAuthException { }; } + /** + * Gets the user data corresponding to the specified user federated identifier. + * + * @param providerUid The user identifier with the given provider. + * @param providerId Identifier for the given federated provider, for example, + * "google.com" for the Google provider. + * @return A {@link UserRecord} instance. + * @throws IllegalArgumentException If the providerUid is null or empty, or if + * the providerId is null, empty, or does not belong to a federated provider. + * @throws FirebaseAuthException If an error occurs while retrieving user data. + */ + public UserRecord getUserByFederatedId( + @NonNull String providerUid, @NonNull String providerId) throws FirebaseAuthException { + return getUserByFederatedIdOp(providerUid, providerId).call(); + } + + /** + * Gets the user data corresponding to the specified user federated identifier. + * + * @param providerUid The user identifier with the given provider. + * @param providerId Identifer for the given federated provider, for example, + * "google.com" for the Google provider. + * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord} + * instance. If an error occurs while retrieving user data or if the uid and provider ID + * do not correspond to a user, the future throws a {@link FirebaseAuthException}. + * @throws IllegalArgumentException If the providerUid is null or empty, or if + * the provider ID is null, empty, or does not belong to a federated provider. + */ + public ApiFuture getUserByFederatedIdAsync( + @NonNull String providerUid, @NonNull String providerId) { + return getUserByFederatedIdOp(providerUid, providerId).callAsync(firebaseApp); + } + + private CallableOperation getUserByFederatedIdOp( + final String providerUid, final String providerId) { + checkNotDestroyed(); + checkArgument(!Strings.isNullOrEmpty(providerUid), "providerUid must not be null or empty"); + checkArgument(!Strings.isNullOrEmpty(providerId), "providerId must not be null or empty"); + checkArgument(!providerId.equals("phone") + && !providerId.equals("password") + && !providerId.equals("anonymous"), "providerId must belong to a federated provider"); + final FirebaseUserManager userManager = getUserManager(); + return new CallableOperation() { + @Override + protected UserRecord execute() throws FirebaseAuthException { + return userManager.getUserByFederatedId(providerUid, providerId); + } + }; + } + /** * Gets a page of users starting from the specified {@code pageToken}. Page size will be * limited to 1000 users. diff --git a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java index 8298499b4..2fb658365 100644 --- a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java +++ b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java @@ -164,6 +164,23 @@ UserRecord getUserByPhoneNumber(String phoneNumber) throws FirebaseAuthException return new UserRecord(response.getUsers().get(0), jsonFactory); } + UserRecord getUserByFederatedId( + String providerUid, String providerId) throws FirebaseAuthException { + final Map payload = ImmutableMap.of( + "federatedUserId", ImmutableList.of( + ImmutableMap.builder() + .put("rawId", providerUid).put("providerId", providerId).build())); + + GetAccountInfoResponse response = post( + "/accounts:lookup", payload, GetAccountInfoResponse.class); + if (response == null || response.getUsers() == null || response.getUsers().isEmpty()) { + throw new FirebaseAuthException(USER_NOT_FOUND_ERROR, + "No user record found for providerUid " + providerUid + + " and federated provider ID " + providerId); + } + return new UserRecord(response.getUsers().get(0), jsonFactory); + } + String createUser(CreateRequest request) throws FirebaseAuthException { GenericJson response = post( "/accounts", request.getProperties(), GenericJson.class); diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java index badb9ead6..c25b6d0f6 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java +++ b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java @@ -49,6 +49,7 @@ import com.google.firebase.auth.UserRecord.CreateRequest; import com.google.firebase.auth.UserRecord.UpdateRequest; import com.google.firebase.auth.hash.Scrypt; +import com.google.firebase.internal.Nullable; import com.google.firebase.testing.IntegrationTestUtils; import java.io.IOException; import java.net.URLDecoder; @@ -140,37 +141,98 @@ public void testDeleteNonExistingUser() throws Exception { @Test public void testCreateUserWithParams() throws Exception { RandomUser randomUser = RandomUser.create(); - String phone = randomPhoneNumber(); - CreateRequest user = new CreateRequest() - .setUid(randomUser.uid) - .setEmail(randomUser.email) - .setPhoneNumber(phone) - .setDisplayName("Random User") - .setPhotoUrl("https://example.com/photo.png") - .setEmailVerified(true) - .setPassword("password"); - - UserRecord userRecord = auth.createUserAsync(user).get(); + String randomPhoneNumber = randomPhoneNumber(); try { - assertEquals(randomUser.uid, userRecord.getUid()); - assertEquals("Random User", userRecord.getDisplayName()); - assertEquals(randomUser.email, userRecord.getEmail()); - assertEquals(phone, userRecord.getPhoneNumber()); - assertEquals("https://example.com/photo.png", userRecord.getPhotoUrl()); - assertTrue(userRecord.isEmailVerified()); - assertFalse(userRecord.isDisabled()); - - assertEquals(2, userRecord.getProviderData().length); + UserRecord user = createUser(randomUser.uid, randomPhoneNumber, randomUser.email); + assertEquals(randomUser.uid, user.getUid()); + assertEquals("Random User", user.getDisplayName()); + assertEquals(randomUser.email, user.getEmail()); + assertEquals(randomPhoneNumber, user.getPhoneNumber()); + assertEquals("https://example.com/photo.png", user.getPhotoUrl()); + assertTrue(user.isEmailVerified()); + assertFalse(user.isDisabled()); + + assertEquals(2, user.getProviderData().length); List providers = new ArrayList<>(); - for (UserInfo provider : userRecord.getProviderData()) { + for (UserInfo provider : user.getProviderData()) { providers.add(provider.getProviderId()); } assertTrue(providers.contains("password")); assertTrue(providers.contains("phone")); - checkRecreate(randomUser.uid); + checkRecreate(user.getUid()); } finally { - auth.deleteUserAsync(userRecord.getUid()).get(); + auth.deleteUserAsync(randomUser.uid).get(); + } + } + + @Test + public void testLookupUserByPhone() throws Exception { + RandomUser randomUser1 = RandomUser.create(); + String phoneNumber1 = null; + RandomUser randomUser2 = RandomUser.create(); + String randomPhoneNumber2 = randomPhoneNumber(); + RandomUser randomUser3 = RandomUser.create(); + String randomPhoneNumber3 = randomPhoneNumber(); + try { + UserRecord user1 = createUser( + randomUser1.uid, /* phoneNumber= */ null, randomUser1.email); + UserRecord user2 = createUser( + randomUser2.uid, randomPhoneNumber2, randomUser2.email); + UserImportResult user3 = importUser( + randomUser3.uid, randomPhoneNumber3, randomUser3.email, "google.com"); + + UserRecord lookedUpRecord = auth.getUserByPhoneNumberAsync(randomPhoneNumber2).get(); + assertEquals(lookedUpRecord.getUid(), randomUser2.uid); + + lookedUpRecord = auth.getUserByPhoneNumberAsync(randomPhoneNumber3).get(); + assertEquals(lookedUpRecord.getUid(), randomUser3.uid); + } finally { + auth.deleteUserAsync(randomUser1.uid).get(); + auth.deleteUserAsync(randomUser2.uid).get(); + auth.deleteUserAsync(randomUser3.uid).get(); + } + } + + @Test + public void testLookupUserByFederatedId() throws Exception { + RandomUser randomUser1 = RandomUser.create(); + String phoneNumber1 = null; + RandomUser randomUser2 = RandomUser.create(); + String randomPhoneNumber2 = randomPhoneNumber(); + RandomUser randomUser3 = RandomUser.create(); + String randomPhoneNumber3 = randomPhoneNumber(); + try { + UserRecord user1 = createUser( + randomUser1.uid, /* phoneNumber= */ null, randomUser1.email); + UserRecord user2 = createUser( + randomUser2.uid, randomPhoneNumber2, randomUser2.email); + UserImportResult user3 = importUser( + randomUser3.uid, randomPhoneNumber3, randomUser3.email, "google.com"); + + UserRecord lookedUpRecord = auth.getUserByFederatedIdAsync( + randomUser3.uid + "_google.com", "google.com").get(); + assertEquals(lookedUpRecord.getUid(), randomUser3.uid); + assertEquals(2, lookedUpRecord.getProviderData().length); + List providers = new ArrayList<>(); + for (UserInfo provider : lookedUpRecord.getProviderData()) { + providers.add(provider.getProviderId()); + } + assertTrue(providers.contains("phone")); + assertTrue(providers.contains("google.com")); + + try { + // Verify that lookup by federated identifier does not accept "phone". + lookedUpRecord = auth.getUserByFederatedIdAsync( + randomPhoneNumber3, "phone").get(); + fail("No error thrown for non-federated provider"); + } catch (IllegalArgumentException ignored) { + // expected + } + } finally { + auth.deleteUserAsync(randomUser1.uid).get(); + auth.deleteUserAsync(randomUser2.uid).get(); + auth.deleteUserAsync(randomUser3.uid).get(); } } @@ -590,6 +652,52 @@ public void testGenerateSignInWithEmailLink() throws Exception { } } + private UserRecord createUser( + String uid, + @Nullable String phoneNumber, + @Nullable String email) throws Exception { + RandomUser randomUser = RandomUser.create(); + CreateRequest user = new CreateRequest() + .setUid(uid) + .setDisplayName("Random User") + .setPhotoUrl("https://example.com/photo.png") + .setPassword("password"); + if (phoneNumber != null) { + user.setPhoneNumber(phoneNumber); + } + if (email != null) { + user.setEmail(email); + user.setEmailVerified(true); + } + return auth.createUserAsync(user).get(); + } + + private UserImportResult importUser( + String uid, + @Nullable String phoneNumber, + @Nullable String email, + String providerId) throws Exception { + ImportUserRecord.Builder builder = ImportUserRecord.builder() + .setUid(uid) + .setDisabled(false) + .setUserMetadata( + new UserMetadata(/* creationTimestamp= */ 20L, /* lastSignInTimestamp= */ 20L)) + .addUserProvider( + UserProvider.builder() + .setProviderId(providerId) + .setUid(uid + "_" + providerId) + .build()); + if (phoneNumber != null) { + builder.setPhoneNumber(phoneNumber); + } + if (email != null) { + builder.setEmail(email); + builder.setEmailVerified(true); + } + ImportUserRecord user = builder.build(); + return auth.importUsersAsync(ImmutableList.of(user)).get(); + } + private Map parseLinkParameters(String link) throws Exception { Map result = new HashMap<>(); int queryBegin = link.indexOf('?'); diff --git a/src/test/java/com/google/firebase/snippets/FirebaseAuthSnippets.java b/src/test/java/com/google/firebase/snippets/FirebaseAuthSnippets.java index fec3f5d2d..46f4083f2 100644 --- a/src/test/java/com/google/firebase/snippets/FirebaseAuthSnippets.java +++ b/src/test/java/com/google/firebase/snippets/FirebaseAuthSnippets.java @@ -85,6 +85,16 @@ public static void getUserByPhoneNumber( // [END get_user_by_phone] } + public static void getUserByFederatedId( + String providerUid, String providerId) throws FirebaseAuthException { + // [START get_user_by_federated_id] + UserRecord userRecord = FirebaseAuth.getInstance().getUserByFederatedId( + providerUid, providerId); + // See the UserRecord reference doc for the contents of userRecord. + System.out.println("Successfully fetched user data: " + userRecord.getUid()); + // [END get_user_by_federated_id] + } + public static void createUser() throws FirebaseAuthException { // [START create_user] CreateRequest request = new CreateRequest() From debda036c1859305465861976e132c6f6cd64bde Mon Sep 17 00:00:00 2001 From: Narges Simjour Date: Wed, 8 Jan 2020 14:28:44 -0500 Subject: [PATCH 2/5] Apply first round of feedback. --- .../google/firebase/auth/FirebaseAuth.java | 4 +- .../google/firebase/auth/FirebaseAuthIT.java | 68 ++++++++----------- 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/src/main/java/com/google/firebase/auth/FirebaseAuth.java b/src/main/java/com/google/firebase/auth/FirebaseAuth.java index 0dcf7b695..725b6bbd4 100644 --- a/src/main/java/com/google/firebase/auth/FirebaseAuth.java +++ b/src/main/java/com/google/firebase/auth/FirebaseAuth.java @@ -606,7 +606,7 @@ protected UserRecord execute() throws FirebaseAuthException { } /** - * Gets the user data corresponding to the specified user federated identifier. + * Gets the user data corresponding to the specified user\'s federated identifier. * * @param providerUid The user identifier with the given provider. * @param providerId Identifier for the given federated provider, for example, @@ -622,7 +622,7 @@ public UserRecord getUserByFederatedId( } /** - * Gets the user data corresponding to the specified user federated identifier. + * Gets the user data corresponding to the specified user\'s federated identifier. * * @param providerUid The user identifier with the given provider. * @param providerId Identifer for the given federated provider, for example, diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java index c25b6d0f6..a126f594c 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java +++ b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java @@ -141,13 +141,12 @@ public void testDeleteNonExistingUser() throws Exception { @Test public void testCreateUserWithParams() throws Exception { RandomUser randomUser = RandomUser.create(); - String randomPhoneNumber = randomPhoneNumber(); try { - UserRecord user = createUser(randomUser.uid, randomPhoneNumber, randomUser.email); + UserRecord user = createUser(randomUser.uid, randomUser.phone, randomUser.email); assertEquals(randomUser.uid, user.getUid()); assertEquals("Random User", user.getDisplayName()); assertEquals(randomUser.email, user.getEmail()); - assertEquals(randomPhoneNumber, user.getPhoneNumber()); + assertEquals(randomUser.phone, user.getPhoneNumber()); assertEquals("https://example.com/photo.png", user.getPhotoUrl()); assertTrue(user.isEmailVerified()); assertFalse(user.isDisabled()); @@ -169,24 +168,22 @@ public void testCreateUserWithParams() throws Exception { @Test public void testLookupUserByPhone() throws Exception { RandomUser randomUser1 = RandomUser.create(); - String phoneNumber1 = null; RandomUser randomUser2 = RandomUser.create(); - String randomPhoneNumber2 = randomPhoneNumber(); RandomUser randomUser3 = RandomUser.create(); - String randomPhoneNumber3 = randomPhoneNumber(); try { UserRecord user1 = createUser( randomUser1.uid, /* phoneNumber= */ null, randomUser1.email); UserRecord user2 = createUser( - randomUser2.uid, randomPhoneNumber2, randomUser2.email); + randomUser2.uid, randomUser2.phone, randomUser2.email); UserImportResult user3 = importUser( - randomUser3.uid, randomPhoneNumber3, randomUser3.email, "google.com"); + randomUser3.uid, randomUser3.phone, randomUser3.email, + "google.com", randomUser3.uid + "_google.com"); - UserRecord lookedUpRecord = auth.getUserByPhoneNumberAsync(randomPhoneNumber2).get(); - assertEquals(lookedUpRecord.getUid(), randomUser2.uid); + UserRecord lookedUpRecord = auth.getUserByPhoneNumberAsync(randomUser2.phone).get(); + assertEquals(randomUser2.uid, lookedUpRecord.getUid()); - lookedUpRecord = auth.getUserByPhoneNumberAsync(randomPhoneNumber3).get(); - assertEquals(lookedUpRecord.getUid(), randomUser3.uid); + lookedUpRecord = auth.getUserByPhoneNumberAsync(randomUser3.phone).get(); + assertEquals(randomUser3.uid, lookedUpRecord.getUid()); } finally { auth.deleteUserAsync(randomUser1.uid).get(); auth.deleteUserAsync(randomUser2.uid).get(); @@ -197,22 +194,20 @@ public void testLookupUserByPhone() throws Exception { @Test public void testLookupUserByFederatedId() throws Exception { RandomUser randomUser1 = RandomUser.create(); - String phoneNumber1 = null; RandomUser randomUser2 = RandomUser.create(); - String randomPhoneNumber2 = randomPhoneNumber(); RandomUser randomUser3 = RandomUser.create(); - String randomPhoneNumber3 = randomPhoneNumber(); try { UserRecord user1 = createUser( randomUser1.uid, /* phoneNumber= */ null, randomUser1.email); UserRecord user2 = createUser( - randomUser2.uid, randomPhoneNumber2, randomUser2.email); + randomUser2.uid, randomUser2.phone, randomUser2.email); UserImportResult user3 = importUser( - randomUser3.uid, randomPhoneNumber3, randomUser3.email, "google.com"); + randomUser3.uid, randomUser3.phone, randomUser3.email, + "google.com", randomUser3.uid + "_google.com"); UserRecord lookedUpRecord = auth.getUserByFederatedIdAsync( randomUser3.uid + "_google.com", "google.com").get(); - assertEquals(lookedUpRecord.getUid(), randomUser3.uid); + assertEquals(randomUser3.uid, lookedUpRecord.getUid()); assertEquals(2, lookedUpRecord.getProviderData().length); List providers = new ArrayList<>(); for (UserInfo provider : lookedUpRecord.getProviderData()) { @@ -224,10 +219,9 @@ public void testLookupUserByFederatedId() throws Exception { try { // Verify that lookup by federated identifier does not accept "phone". lookedUpRecord = auth.getUserByFederatedIdAsync( - randomPhoneNumber3, "phone").get(); + randomUser3.phone, "phone").get(); fail("No error thrown for non-federated provider"); - } catch (IllegalArgumentException ignored) { - // expected + } catch (IllegalArgumentException expected) { } } finally { auth.deleteUserAsync(randomUser1.uid).get(); @@ -258,11 +252,10 @@ public void testUserLifecycle() throws Exception { // Update user RandomUser randomUser = RandomUser.create(); - String phone = randomPhoneNumber(); UpdateRequest request = userRecord.updateRequest() .setDisplayName("Updated Name") .setEmail(randomUser.email) - .setPhoneNumber(phone) + .setPhoneNumber(randomUser.phone) .setPhotoUrl("https://example.com/photo.png") .setEmailVerified(true) .setPassword("secret"); @@ -270,7 +263,7 @@ public void testUserLifecycle() throws Exception { assertEquals(uid, userRecord.getUid()); assertEquals("Updated Name", userRecord.getDisplayName()); assertEquals(randomUser.email, userRecord.getEmail()); - assertEquals(phone, userRecord.getPhoneNumber()); + assertEquals(randomUser.phone, userRecord.getPhoneNumber()); assertEquals("https://example.com/photo.png", userRecord.getPhotoUrl()); assertTrue(userRecord.isEmailVerified()); assertFalse(userRecord.isDisabled()); @@ -656,7 +649,6 @@ private UserRecord createUser( String uid, @Nullable String phoneNumber, @Nullable String email) throws Exception { - RandomUser randomUser = RandomUser.create(); CreateRequest user = new CreateRequest() .setUid(uid) .setDisplayName("Random User") @@ -676,7 +668,8 @@ private UserImportResult importUser( String uid, @Nullable String phoneNumber, @Nullable String email, - String providerId) throws Exception { + String providerId, + String providerUid) throws Exception { ImportUserRecord.Builder builder = ImportUserRecord.builder() .setUid(uid) .setDisabled(false) @@ -685,7 +678,7 @@ private UserImportResult importUser( .addUserProvider( UserProvider.builder() .setProviderId(providerId) - .setUid(uid + "_" + providerId) + .setUid(providerUid) .build()); if (phoneNumber != null) { builder.setPhoneNumber(phoneNumber); @@ -713,15 +706,6 @@ private Map parseLinkParameters(String link) throws Exception { return result; } - private String randomPhoneNumber() { - Random random = new Random(); - StringBuilder builder = new StringBuilder("+1"); - for (int i = 0; i < 10; i++) { - builder.append(random.nextInt(10)); - } - return builder.toString(); - } - private String signInWithCustomToken(String customToken) throws IOException { GenericUrl url = new GenericUrl(VERIFY_CUSTOM_TOKEN_URL + "?key=" + IntegrationTestUtils.getApiKey()); @@ -805,17 +789,25 @@ private void checkRecreate(String uid) throws Exception { private static class RandomUser { private final String uid; private final String email; + private final String phone; - private RandomUser(String uid, String email) { + private RandomUser(String uid, String email, String phone) { this.uid = uid; this.email = email; + this.phone = phone; } static RandomUser create() { final String uid = UUID.randomUUID().toString().replaceAll("-", ""); final String email = ("test" + uid.substring(0, 12) + "@example." + uid.substring(12) + ".com").toLowerCase(); - return new RandomUser(uid, email); + Random random = new Random(); + StringBuilder builder = new StringBuilder("+1"); + for (int i = 0; i < 10; i++) { + builder.append(random.nextInt(10)); + } + final String phone = builder.toString(); + return new RandomUser(uid, email, phone); } } } From b3af254ce5346cfe2dfb62020e2d2d51b745f1ea Mon Sep 17 00:00:00 2001 From: Rich Gowman Date: Tue, 16 Mar 2021 16:46:29 -0400 Subject: [PATCH 3/5] Review feedback --- .../google/firebase/auth/FirebaseAuth.java | 43 ++++++++------ .../firebase/auth/FirebaseUserManager.java | 8 +-- .../google/firebase/auth/FirebaseAuthIT.java | 10 ++-- .../auth/FirebaseUserManagerTest.java | 56 ++++++++++++++++++- src/test/resources/getUser.json | 3 + src/test/resources/listUsers.json | 6 ++ 6 files changed, 98 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/google/firebase/auth/FirebaseAuth.java b/src/main/java/com/google/firebase/auth/FirebaseAuth.java index 725b6bbd4..c6464a4ef 100644 --- a/src/main/java/com/google/firebase/auth/FirebaseAuth.java +++ b/src/main/java/com/google/firebase/auth/FirebaseAuth.java @@ -606,51 +606,60 @@ protected UserRecord execute() throws FirebaseAuthException { } /** - * Gets the user data corresponding to the specified user\'s federated identifier. + * Gets the user data for the user corresponding to a given provider id. * - * @param providerUid The user identifier with the given provider. * @param providerId Identifier for the given federated provider, for example, * "google.com" for the Google provider. + * @param uid The user identifier with the given provider. * @return A {@link UserRecord} instance. - * @throws IllegalArgumentException If the providerUid is null or empty, or if + * @throws IllegalArgumentException If the uid is null or empty, or if * the providerId is null, empty, or does not belong to a federated provider. * @throws FirebaseAuthException If an error occurs while retrieving user data. */ - public UserRecord getUserByFederatedId( - @NonNull String providerUid, @NonNull String providerId) throws FirebaseAuthException { + public UserRecord getUserByProviderUid( + @NonNull String providerId, @NonNull String uid) throws FirebaseAuthException { return getUserByFederatedIdOp(providerUid, providerId).call(); } /** - * Gets the user data corresponding to the specified user\'s federated identifier. + * Gets the user data for the user corresponding to a given provider id. * - * @param providerUid The user identifier with the given provider. * @param providerId Identifer for the given federated provider, for example, * "google.com" for the Google provider. + * @param uid The user identifier with the given provider. * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord} - * instance. If an error occurs while retrieving user data or if the uid and provider ID + * instance. If an error occurs while retrieving user data or if the provider ID and uid * do not correspond to a user, the future throws a {@link FirebaseAuthException}. - * @throws IllegalArgumentException If the providerUid is null or empty, or if + * @throws IllegalArgumentException If the uid is null or empty, or if * the provider ID is null, empty, or does not belong to a federated provider. */ - public ApiFuture getUserByFederatedIdAsync( - @NonNull String providerUid, @NonNull String providerId) { - return getUserByFederatedIdOp(providerUid, providerId).callAsync(firebaseApp); + public ApiFuture getUserByProviderUidAsync( + @NonNull String providerId, @NonNull String uid) { + return getUserByFederatedIdOp(providerId, uid).callAsync(firebaseApp); } private CallableOperation getUserByFederatedIdOp( - final String providerUid, final String providerId) { + final String providerId, final String uid) { checkNotDestroyed(); - checkArgument(!Strings.isNullOrEmpty(providerUid), "providerUid must not be null or empty"); checkArgument(!Strings.isNullOrEmpty(providerId), "providerId must not be null or empty"); - checkArgument(!providerId.equals("phone") - && !providerId.equals("password") + checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty"); + + // Although we don't really advertise it, we want to also handle + // non-federated idps with this call. So if we detect one of them, we'll + // reroute this request appropriately. + if (providerId == "phone") { + return this.getUserByPhoneNumberOp(uid); + } else if (providerId == "email") { + return this.getUserByEmailOp(uid); + } + + checkArgument(!providerId.equals("password") && !providerId.equals("anonymous"), "providerId must belong to a federated provider"); final FirebaseUserManager userManager = getUserManager(); return new CallableOperation() { @Override protected UserRecord execute() throws FirebaseAuthException { - return userManager.getUserByFederatedId(providerUid, providerId); + return userManager.getUserByFederatedId(providerId, uid); } }; } diff --git a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java index 2fb658365..450069ab8 100644 --- a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java +++ b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java @@ -165,18 +165,18 @@ UserRecord getUserByPhoneNumber(String phoneNumber) throws FirebaseAuthException } UserRecord getUserByFederatedId( - String providerUid, String providerId) throws FirebaseAuthException { + String providerId, String uid) throws FirebaseAuthException { final Map payload = ImmutableMap.of( "federatedUserId", ImmutableList.of( ImmutableMap.builder() - .put("rawId", providerUid).put("providerId", providerId).build())); + .put("rawId", uid).put("providerId", providerId).build())); GetAccountInfoResponse response = post( "/accounts:lookup", payload, GetAccountInfoResponse.class); if (response == null || response.getUsers() == null || response.getUsers().isEmpty()) { throw new FirebaseAuthException(USER_NOT_FOUND_ERROR, - "No user record found for providerUid " + providerUid - + " and federated provider ID " + providerId); + "No user record found for uid " + uid + + " and provider ID " + providerId); } return new UserRecord(response.getUsers().get(0), jsonFactory); } diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java index a126f594c..821a4118c 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java +++ b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java @@ -197,12 +197,9 @@ public void testLookupUserByFederatedId() throws Exception { RandomUser randomUser2 = RandomUser.create(); RandomUser randomUser3 = RandomUser.create(); try { - UserRecord user1 = createUser( - randomUser1.uid, /* phoneNumber= */ null, randomUser1.email); - UserRecord user2 = createUser( - randomUser2.uid, randomUser2.phone, randomUser2.email); - UserImportResult user3 = importUser( - randomUser3.uid, randomUser3.phone, randomUser3.email, + createUser(randomUser1.uid, /* phoneNumber= */ null, randomUser1.email); + createUser(randomUser2.uid, randomUser2.phone, randomUser2.email); + importUser(randomUser3.uid, randomUser3.phone, randomUser3.email, "google.com", randomUser3.uid + "_google.com"); UserRecord lookedUpRecord = auth.getUserByFederatedIdAsync( @@ -224,6 +221,7 @@ public void testLookupUserByFederatedId() throws Exception { } catch (IllegalArgumentException expected) { } } finally { + // TODO(rsgowman): We can switch this to using bulk delete. auth.deleteUserAsync(randomUser1.uid).get(); auth.deleteUserAsync(randomUser2.uid).get(); auth.deleteUserAsync(randomUser3.uid).get(); diff --git a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java index 97ff7447a..e0be6a87e 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java @@ -166,6 +166,56 @@ public void testGetUserByPhoneNumberWithNotFoundError() throws Exception { } } + @Test + public void testGetUserByProviderUidWithInvalidProviderId() throws Exception { + initializeAppForUserManagement(); + try { + FirebaseAuth.getInstance().getUserByProviderUidAsync("", "uid").get(); + fail("No error thrown for invalid request"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testGetUserByProviderUidWithInvalidProviderUid() throws Exception { + initializeAppForUserManagement(); + try { + FirebaseAuth.getInstance().getUserByProviderUidAsync("id", "").get(); + fail("No error thrown for invalid request"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testGetUserByProviderUidWithValidInput() throws Exception { + TestResponseInterceptor interceptor = initializeAppForUserManagement( + TestUtils.loadResource("getUser.json")); + UserRecord userRecord = FirebaseAuth.getInstance() + .getUserByProviderUidAsync("google.com", "google_uid").get(); + checkUserRecord(userRecord); + checkRequestHeaders(interceptor); + } + + @Test + public void testGetUserByProviderUidWithPhone() throws Exception { + TestResponseInterceptor interceptor = initializeAppForUserManagement( + TestUtils.loadResource("getUser.json")); + UserRecord userRecord = FirebaseAuth.getInstance() + .getUserByProviderUidAsync("phone", "+1234567890").get(); + checkUserRecord(userRecord); + checkRequestHeaders(interceptor); + } + + @Test + public void testGetUserByProviderUidWithEmail() throws Exception { + TestResponseInterceptor interceptor = initializeAppForUserManagement( + TestUtils.loadResource("getUser.json")); + UserRecord userRecord = FirebaseAuth.getInstance() + .getUserByProviderUidAsync("email", "testuser@example.com").get(); + checkUserRecord(userRecord); + checkRequestHeaders(interceptor); + } + @Test public void testListUsers() throws Exception { final TestResponseInterceptor interceptor = initializeAppForUserManagement( @@ -1239,7 +1289,7 @@ private static void checkUserRecord(UserRecord userRecord) { assertEquals("http://www.example.com/testuser/photo.png", userRecord.getPhotoUrl()); assertEquals(1234567890, userRecord.getUserMetadata().getCreationTimestamp()); assertEquals(0, userRecord.getUserMetadata().getLastSignInTimestamp()); - assertEquals(2, userRecord.getProviderData().length); + assertEquals(3, userRecord.getProviderData().length); assertFalse(userRecord.isDisabled()); assertTrue(userRecord.isEmailVerified()); assertEquals(1494364393000L, userRecord.getTokensValidAfterTimestamp()); @@ -1258,6 +1308,10 @@ private static void checkUserRecord(UserRecord userRecord) { assertEquals("+1234567890", provider.getPhoneNumber()); assertEquals("phone", provider.getProviderId()); + provider = userRecord.getProviderData()[2]; + assertEquals("google_uid", provider.getUid()); + assertEquals("google.com", provider.getProviderId()); + Map claims = userRecord.getCustomClaims(); assertEquals(2, claims.size()); assertTrue((boolean) claims.get("admin")); diff --git a/src/test/resources/getUser.json b/src/test/resources/getUser.json index 019c665be..aa078bb8c 100644 --- a/src/test/resources/getUser.json +++ b/src/test/resources/getUser.json @@ -17,6 +17,9 @@ "providerId" : "phone", "phoneNumber" : "+1234567890", "rawId" : "+1234567890" + }, { + "providerId" : "google.com", + "rawId" : "google_uid" } ], "photoUrl" : "http://www.example.com/testuser/photo.png", "passwordHash" : "passwordhash", diff --git a/src/test/resources/listUsers.json b/src/test/resources/listUsers.json index 06d0b34c9..f15900330 100644 --- a/src/test/resources/listUsers.json +++ b/src/test/resources/listUsers.json @@ -16,6 +16,9 @@ "providerId" : "phone", "phoneNumber" : "+1234567890", "rawId" : "+1234567890" + }, { + "providerId" : "google.com", + "rawId" : "google_uid" } ], "photoUrl" : "http://www.example.com/testuser/photo.png", "passwordHash" : "passwordHash", @@ -42,6 +45,9 @@ "providerId" : "phone", "phoneNumber" : "+1234567890", "rawId" : "+1234567890" + }, { + "providerId" : "google.com", + "rawId" : "google_uid" } ], "photoUrl" : "http://www.example.com/testuser/photo.png", "passwordHash" : "passwordHash", From c1025e89859e226fd49a8dedc0af560d09e96885 Mon Sep 17 00:00:00 2001 From: Rich Gowman Date: Wed, 31 Mar 2021 15:50:31 -0400 Subject: [PATCH 4/5] review feedback --- .../firebase/auth/AbstractFirebaseAuth.java | 8 +- .../firebase/auth/FirebaseUserManager.java | 2 +- .../google/firebase/auth/FirebaseAuthIT.java | 105 ++++++++---------- 3 files changed, 50 insertions(+), 65 deletions(-) diff --git a/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java b/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java index ed44744f9..b2e46695e 100644 --- a/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java +++ b/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java @@ -566,7 +566,7 @@ protected UserRecord execute() throws FirebaseAuthException { */ public UserRecord getUserByProviderUid( @NonNull String providerId, @NonNull String uid) throws FirebaseAuthException { - return getUserByFederatedIdOp(providerId, uid).call(); + return getUserByProviderUidOp(providerId, uid).call(); } /** @@ -583,10 +583,10 @@ public UserRecord getUserByProviderUid( */ public ApiFuture getUserByProviderUidAsync( @NonNull String providerId, @NonNull String uid) { - return getUserByFederatedIdOp(providerId, uid).callAsync(firebaseApp); + return getUserByProviderUidOp(providerId, uid).callAsync(firebaseApp); } - private CallableOperation getUserByFederatedIdOp( + private CallableOperation getUserByProviderUidOp( final String providerId, final String uid) { checkArgument(!Strings.isNullOrEmpty(providerId), "providerId must not be null or empty"); checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty"); @@ -606,7 +606,7 @@ private CallableOperation getUserByFederatedI return new CallableOperation() { @Override protected UserRecord execute() throws FirebaseAuthException { - return userManager.getUserByFederatedId(providerId, uid); + return userManager.getUserByProviderUid(providerId, uid); } }; } diff --git a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java index 275f58318..fb6ca966d 100644 --- a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java +++ b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java @@ -145,7 +145,7 @@ Set getAccountInfo(@NonNull Collection identifiers) return results; } - UserRecord getUserByFederatedId( + UserRecord getUserByProviderUid( String providerId, String uid) throws FirebaseAuthException { final Map payload = ImmutableMap.of( "federatedUserId", ImmutableList.of( diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java index 557988a63..5bbf90eb9 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java +++ b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java @@ -267,45 +267,37 @@ public void testCreateUserWithParams() throws Exception { @Test public void testLookupUserByPhone() throws Exception { - RandomUser randomUser1 = UserTestUtils.generateRandomUserInfo(); - RandomUser randomUser2 = UserTestUtils.generateRandomUserInfo(); - RandomUser randomUser3 = UserTestUtils.generateRandomUserInfo(); + UserRecord user1 = null; + UserRecord user2 = null; try { - createUser(randomUser1.getUid(), /* phoneNumber= */ null, randomUser1.getEmail()); - createUser(randomUser2.getUid(), randomUser2.getPhoneNumber(), randomUser2.getEmail()); - importUser(randomUser3.getUid(), randomUser3.getPhoneNumber(), randomUser3.getEmail(), - "google.com", randomUser3.getUid() + "_google.com"); + user1 = createRandomUser(); + user2 = importRandomUser(); UserRecord lookedUpRecord = auth.getUserByPhoneNumberAsync( - randomUser2.getPhoneNumber()).get(); - assertEquals(randomUser2.getUid(), lookedUpRecord.getUid()); + user1.getPhoneNumber()).get(); + assertEquals(user1.getUid(), lookedUpRecord.getUid()); - lookedUpRecord = auth.getUserByPhoneNumberAsync(randomUser3.getPhoneNumber()).get(); - assertEquals(randomUser3.getUid(), lookedUpRecord.getUid()); + lookedUpRecord = auth.getUserByPhoneNumberAsync(user2.getPhoneNumber()).get(); + assertEquals(user2.getUid(), lookedUpRecord.getUid()); } finally { - auth.deleteUserAsync(randomUser1.getUid()).get(); - auth.deleteUserAsync(randomUser2.getUid()).get(); - auth.deleteUserAsync(randomUser3.getUid()).get(); + if (user1 != null) { + auth.deleteUserAsync(user1.getUid()).get(); + } + if (user2 != null) { + auth.deleteUserAsync(user2.getUid()).get(); + } } } @Test - public void testLookupUserByFederatedId() throws Exception { - RandomUser randomUser1 = UserTestUtils.generateRandomUserInfo(); - RandomUser randomUser2 = UserTestUtils.generateRandomUserInfo(); - RandomUser randomUser3 = UserTestUtils.generateRandomUserInfo(); + public void testLookupUserByProviderUid() throws Exception { + UserRecord user = null; try { - createUser( - randomUser1.getUid(), /* phoneNumber= */ null, randomUser1.getEmail()); - createUser( - randomUser2.getUid(), randomUser2.getPhoneNumber(), randomUser2.getEmail()); - importUser( - randomUser3.getUid(), randomUser3.getPhoneNumber(), randomUser3.getEmail(), - "google.com", randomUser3.getUid() + "_google.com"); + user = importRandomUser(); UserRecord lookedUpRecord = auth.getUserByProviderUidAsync( - "google.com", randomUser3.getUid() + "_google.com").get(); - assertEquals(randomUser3.getUid(), lookedUpRecord.getUid()); + "google.com", user.getUid() + "_google.com").get(); + assertEquals(user.getUid(), lookedUpRecord.getUid()); assertEquals(2, lookedUpRecord.getProviderData().length); List providers = new ArrayList<>(); for (UserInfo provider : lookedUpRecord.getProviderData()) { @@ -315,10 +307,9 @@ public void testLookupUserByFederatedId() throws Exception { assertTrue(providers.contains("google.com")); } finally { - // TODO(rsgowman): We can switch this to using bulk delete. - auth.deleteUserAsync(randomUser1.getUid()).get(); - auth.deleteUserAsync(randomUser2.getUid()).get(); - auth.deleteUserAsync(randomUser3.getUid()).get(); + if (user != null) { + auth.deleteUserAsync(user.getUid()).get(); + } } } @@ -986,51 +977,45 @@ public void onSuccess(ListProviderConfigsPage result) { assertNull(error.get()); } - private UserRecord createUser( - String uid, - @Nullable String phoneNumber, - @Nullable String email) throws Exception { + private UserRecord createRandomUser() throws Exception { + RandomUser randomUser = UserTestUtils.generateRandomUserInfo(); + UserRecord.CreateRequest user = new UserRecord.CreateRequest() - .setUid(uid) + .setUid(randomUser.getUid()) .setDisplayName("Random User") + .setEmail(randomUser.getEmail()) + .setEmailVerified(true) + .setPhoneNumber(randomUser.getPhoneNumber()) .setPhotoUrl("https://example.com/photo.png") .setPassword("password"); - if (phoneNumber != null) { - user.setPhoneNumber(phoneNumber); - } - if (email != null) { - user.setEmail(email); - user.setEmailVerified(true); - } + return auth.createUserAsync(user).get(); } - private UserImportResult importUser( - String uid, - @Nullable String phoneNumber, - @Nullable String email, - String providerId, - String providerUid) throws Exception { + private UserRecord importRandomUser() throws Exception { + RandomUser randomUser = UserTestUtils.generateRandomUserInfo(); + ImportUserRecord.Builder builder = ImportUserRecord.builder() - .setUid(uid) + .setUid(randomUser.getUid()) .setDisabled(false) + .setEmail(randomUser.getEmail()) + .setEmailVerified(true) + .setPhoneNumber(randomUser.getPhoneNumber()) .setUserMetadata( new UserMetadata(/* creationTimestamp= */ 20L, /* lastSignInTimestamp= */ 20L, /* lastRefreshTimestamp= */ 20L)) .addUserProvider( UserProvider.builder() - .setProviderId(providerId) - .setUid(providerUid) + .setProviderId("google.com") + .setUid(randomUser.getUid() + "_google.com") .build()); - if (phoneNumber != null) { - builder.setPhoneNumber(phoneNumber); - } - if (email != null) { - builder.setEmail(email); - builder.setEmailVerified(true); - } + ImportUserRecord user = builder.build(); - return auth.importUsersAsync(ImmutableList.of(user)).get(); + UserImportResult result = auth.importUsersAsync(ImmutableList.of(user)).get(); + assertEquals(result.getSuccessCount(), 1); + assertEquals(result.getFailureCount(), 0); + + return auth.getUserAsync(randomUser.getUid()).get(); } private Map parseLinkParameters(String link) throws Exception { From f9b69a06eabed8f18ce9eb4328aff44d37acf862 Mon Sep 17 00:00:00 2001 From: Rich Gowman Date: Fri, 9 Apr 2021 15:00:13 -0400 Subject: [PATCH 5/5] review feedback --- .../google/firebase/auth/FirebaseAuthIT.java | 66 ++++++++----------- 1 file changed, 27 insertions(+), 39 deletions(-) diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java index 5bbf90eb9..d4349f40e 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java +++ b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java @@ -267,50 +267,31 @@ public void testCreateUserWithParams() throws Exception { @Test public void testLookupUserByPhone() throws Exception { - UserRecord user1 = null; - UserRecord user2 = null; - try { - user1 = createRandomUser(); - user2 = importRandomUser(); + UserRecord user1 = createTemporaryUser(); + UserRecord user2 = importTemporaryUser(); - UserRecord lookedUpRecord = auth.getUserByPhoneNumberAsync( - user1.getPhoneNumber()).get(); - assertEquals(user1.getUid(), lookedUpRecord.getUid()); + UserRecord lookedUpRecord = auth.getUserByPhoneNumberAsync( + user1.getPhoneNumber()).get(); + assertEquals(user1.getUid(), lookedUpRecord.getUid()); - lookedUpRecord = auth.getUserByPhoneNumberAsync(user2.getPhoneNumber()).get(); - assertEquals(user2.getUid(), lookedUpRecord.getUid()); - } finally { - if (user1 != null) { - auth.deleteUserAsync(user1.getUid()).get(); - } - if (user2 != null) { - auth.deleteUserAsync(user2.getUid()).get(); - } - } + lookedUpRecord = auth.getUserByPhoneNumberAsync(user2.getPhoneNumber()).get(); + assertEquals(user2.getUid(), lookedUpRecord.getUid()); } @Test public void testLookupUserByProviderUid() throws Exception { - UserRecord user = null; - try { - user = importRandomUser(); - - UserRecord lookedUpRecord = auth.getUserByProviderUidAsync( - "google.com", user.getUid() + "_google.com").get(); - assertEquals(user.getUid(), lookedUpRecord.getUid()); - assertEquals(2, lookedUpRecord.getProviderData().length); - List providers = new ArrayList<>(); - for (UserInfo provider : lookedUpRecord.getProviderData()) { - providers.add(provider.getProviderId()); - } - assertTrue(providers.contains("phone")); - assertTrue(providers.contains("google.com")); + UserRecord user = importTemporaryUser(); - } finally { - if (user != null) { - auth.deleteUserAsync(user.getUid()).get(); - } + UserRecord lookedUpRecord = auth.getUserByProviderUidAsync( + "google.com", user.getUid() + "_google.com").get(); + assertEquals(user.getUid(), lookedUpRecord.getUid()); + assertEquals(2, lookedUpRecord.getProviderData().length); + List providers = new ArrayList<>(); + for (UserInfo provider : lookedUpRecord.getProviderData()) { + providers.add(provider.getProviderId()); } + assertTrue(providers.contains("phone")); + assertTrue(providers.contains("google.com")); } @Test @@ -977,7 +958,10 @@ public void onSuccess(ListProviderConfigsPage result) { assertNull(error.get()); } - private UserRecord createRandomUser() throws Exception { + /** + * Create a temporary user. This user will automatically be cleaned up after testing completes. + */ + private UserRecord createTemporaryUser() throws Exception { RandomUser randomUser = UserTestUtils.generateRandomUserInfo(); UserRecord.CreateRequest user = new UserRecord.CreateRequest() @@ -989,10 +973,13 @@ private UserRecord createRandomUser() throws Exception { .setPhotoUrl("https://example.com/photo.png") .setPassword("password"); - return auth.createUserAsync(user).get(); + return temporaryUser.create(user); } - private UserRecord importRandomUser() throws Exception { + /** + * Import a temporary user. This user will automatically be cleaned up after testing completes. + */ + private UserRecord importTemporaryUser() throws Exception { RandomUser randomUser = UserTestUtils.generateRandomUserInfo(); ImportUserRecord.Builder builder = ImportUserRecord.builder() @@ -1014,6 +1001,7 @@ private UserRecord importRandomUser() throws Exception { UserImportResult result = auth.importUsersAsync(ImmutableList.of(user)).get(); assertEquals(result.getSuccessCount(), 1); assertEquals(result.getFailureCount(), 0); + temporaryUser.registerUid(randomUser.getUid()); return auth.getUserAsync(randomUser.getUid()).get(); }