Skip to content

Commit 589f08b

Browse files
nrsimrsgowman
andauthored
feat(auth): Add API to look up users by federated ID. (#340)
* Add API to look up users by federated ID. * Apply first round of feedback. * Review feedback * review feedback * review feedback Co-authored-by: Rich Gowman <[email protected]>
1 parent 1a6a00d commit 589f08b

File tree

7 files changed

+218
-1
lines changed

7 files changed

+218
-1
lines changed

src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,64 @@ protected UserRecord execute() throws FirebaseAuthException {
554554
};
555555
}
556556

557+
/**
558+
* Gets the user data for the user corresponding to a given provider id.
559+
*
560+
* @param providerId Identifier for the given federated provider, for example,
561+
* "google.com" for the Google provider.
562+
* @param uid The user identifier with the given provider.
563+
* @return A {@link UserRecord} instance.
564+
* @throws IllegalArgumentException If the uid is null or empty, or if
565+
* the providerId is null, empty, or does not belong to a federated provider.
566+
* @throws FirebaseAuthException If an error occurs while retrieving user data.
567+
*/
568+
public UserRecord getUserByProviderUid(
569+
@NonNull String providerId, @NonNull String uid) throws FirebaseAuthException {
570+
return getUserByProviderUidOp(providerId, uid).call();
571+
}
572+
573+
/**
574+
* Gets the user data for the user corresponding to a given provider id.
575+
*
576+
* @param providerId Identifer for the given federated provider, for example,
577+
* "google.com" for the Google provider.
578+
* @param uid The user identifier with the given provider.
579+
* @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord}
580+
* instance. If an error occurs while retrieving user data or if the provider ID and uid
581+
* do not correspond to a user, the future throws a {@link FirebaseAuthException}.
582+
* @throws IllegalArgumentException If the uid is null or empty, or if
583+
* the provider ID is null, empty, or does not belong to a federated provider.
584+
*/
585+
public ApiFuture<UserRecord> getUserByProviderUidAsync(
586+
@NonNull String providerId, @NonNull String uid) {
587+
return getUserByProviderUidOp(providerId, uid).callAsync(firebaseApp);
588+
}
589+
590+
private CallableOperation<UserRecord, FirebaseAuthException> getUserByProviderUidOp(
591+
final String providerId, final String uid) {
592+
checkArgument(!Strings.isNullOrEmpty(providerId), "providerId must not be null or empty");
593+
checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty");
594+
595+
// Although we don't really advertise it, we want to also handle
596+
// non-federated idps with this call. So if we detect one of them, we'll
597+
// reroute this request appropriately.
598+
if (providerId == "phone") {
599+
return this.getUserByPhoneNumberOp(uid);
600+
} else if (providerId == "email") {
601+
return this.getUserByEmailOp(uid);
602+
}
603+
604+
checkArgument(!providerId.equals("password")
605+
&& !providerId.equals("anonymous"), "providerId must belong to a federated provider");
606+
final FirebaseUserManager userManager = getUserManager();
607+
return new CallableOperation<UserRecord, FirebaseAuthException>() {
608+
@Override
609+
protected UserRecord execute() throws FirebaseAuthException {
610+
return userManager.getUserByProviderUid(providerId, uid);
611+
}
612+
};
613+
}
614+
557615
/**
558616
* Gets a page of users starting from the specified {@code pageToken}. Page size is limited to
559617
* 1000 users.

src/main/java/com/google/firebase/auth/FirebaseUserManager.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,15 @@ Set<UserRecord> getAccountInfo(@NonNull Collection<UserIdentifier> identifiers)
155155
return results;
156156
}
157157

158+
UserRecord getUserByProviderUid(
159+
String providerId, String uid) throws FirebaseAuthException {
160+
final Map<String, Object> payload = ImmutableMap.<String, Object>of(
161+
"federatedUserId", ImmutableList.of(
162+
ImmutableMap.<String, Object>builder()
163+
.put("rawId", uid).put("providerId", providerId).build()));
164+
return lookupUserAccount(payload, uid);
165+
}
166+
158167
String createUser(UserRecord.CreateRequest request) throws FirebaseAuthException {
159168
GenericJson response = post("/accounts", request.getProperties(), GenericJson.class);
160169
return (String) response.get("localId");

src/test/java/com/google/firebase/auth/FirebaseAuthIT.java

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,35 @@ public void testCreateUserWithParams() throws Exception {
265265
checkRecreateUser(randomUser.getUid());
266266
}
267267

268+
@Test
269+
public void testLookupUserByPhone() throws Exception {
270+
UserRecord user1 = createTemporaryUser();
271+
UserRecord user2 = importTemporaryUser();
272+
273+
UserRecord lookedUpRecord = auth.getUserByPhoneNumberAsync(
274+
user1.getPhoneNumber()).get();
275+
assertEquals(user1.getUid(), lookedUpRecord.getUid());
276+
277+
lookedUpRecord = auth.getUserByPhoneNumberAsync(user2.getPhoneNumber()).get();
278+
assertEquals(user2.getUid(), lookedUpRecord.getUid());
279+
}
280+
281+
@Test
282+
public void testLookupUserByProviderUid() throws Exception {
283+
UserRecord user = importTemporaryUser();
284+
285+
UserRecord lookedUpRecord = auth.getUserByProviderUidAsync(
286+
"google.com", user.getUid() + "_google.com").get();
287+
assertEquals(user.getUid(), lookedUpRecord.getUid());
288+
assertEquals(2, lookedUpRecord.getProviderData().length);
289+
List<String> providers = new ArrayList<>();
290+
for (UserInfo provider : lookedUpRecord.getProviderData()) {
291+
providers.add(provider.getProviderId());
292+
}
293+
assertTrue(providers.contains("phone"));
294+
assertTrue(providers.contains("google.com"));
295+
}
296+
268297
@Test
269298
public void testUserLifecycle() throws Exception {
270299
// Create user
@@ -946,6 +975,54 @@ public void onSuccess(ListProviderConfigsPage<SamlProviderConfig> result) {
946975
assertNull(error.get());
947976
}
948977

978+
/**
979+
* Create a temporary user. This user will automatically be cleaned up after testing completes.
980+
*/
981+
private UserRecord createTemporaryUser() throws Exception {
982+
RandomUser randomUser = UserTestUtils.generateRandomUserInfo();
983+
984+
UserRecord.CreateRequest user = new UserRecord.CreateRequest()
985+
.setUid(randomUser.getUid())
986+
.setDisplayName("Random User")
987+
.setEmail(randomUser.getEmail())
988+
.setEmailVerified(true)
989+
.setPhoneNumber(randomUser.getPhoneNumber())
990+
.setPhotoUrl("https://example.com/photo.png")
991+
.setPassword("password");
992+
993+
return temporaryUser.create(user);
994+
}
995+
996+
/**
997+
* Import a temporary user. This user will automatically be cleaned up after testing completes.
998+
*/
999+
private UserRecord importTemporaryUser() throws Exception {
1000+
RandomUser randomUser = UserTestUtils.generateRandomUserInfo();
1001+
1002+
ImportUserRecord.Builder builder = ImportUserRecord.builder()
1003+
.setUid(randomUser.getUid())
1004+
.setDisabled(false)
1005+
.setEmail(randomUser.getEmail())
1006+
.setEmailVerified(true)
1007+
.setPhoneNumber(randomUser.getPhoneNumber())
1008+
.setUserMetadata(
1009+
new UserMetadata(/* creationTimestamp= */ 20L, /* lastSignInTimestamp= */ 20L,
1010+
/* lastRefreshTimestamp= */ 20L))
1011+
.addUserProvider(
1012+
UserProvider.builder()
1013+
.setProviderId("google.com")
1014+
.setUid(randomUser.getUid() + "_google.com")
1015+
.build());
1016+
1017+
ImportUserRecord user = builder.build();
1018+
UserImportResult result = auth.importUsersAsync(ImmutableList.of(user)).get();
1019+
assertEquals(result.getSuccessCount(), 1);
1020+
assertEquals(result.getFailureCount(), 0);
1021+
temporaryUser.registerUid(randomUser.getUid());
1022+
1023+
return auth.getUserAsync(randomUser.getUid()).get();
1024+
}
1025+
9491026
private Map<String, String> parseLinkParameters(String link) throws Exception {
9501027
Map<String, String> result = new HashMap<>();
9511028
int queryBegin = link.indexOf('?');

src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,56 @@ public void testInvalidProviderIdentifier() {
355355
}
356356
}
357357

358+
@Test
359+
public void testGetUserByProviderUidWithInvalidProviderId() throws Exception {
360+
initializeAppForUserManagement();
361+
try {
362+
FirebaseAuth.getInstance().getUserByProviderUidAsync("", "uid").get();
363+
fail("No error thrown for invalid request");
364+
} catch (IllegalArgumentException expected) {
365+
}
366+
}
367+
368+
@Test
369+
public void testGetUserByProviderUidWithInvalidProviderUid() throws Exception {
370+
initializeAppForUserManagement();
371+
try {
372+
FirebaseAuth.getInstance().getUserByProviderUidAsync("id", "").get();
373+
fail("No error thrown for invalid request");
374+
} catch (IllegalArgumentException expected) {
375+
}
376+
}
377+
378+
@Test
379+
public void testGetUserByProviderUidWithValidInput() throws Exception {
380+
TestResponseInterceptor interceptor = initializeAppForUserManagement(
381+
TestUtils.loadResource("getUser.json"));
382+
UserRecord userRecord = FirebaseAuth.getInstance()
383+
.getUserByProviderUidAsync("google.com", "google_uid").get();
384+
checkUserRecord(userRecord);
385+
checkRequestHeaders(interceptor);
386+
}
387+
388+
@Test
389+
public void testGetUserByProviderUidWithPhone() throws Exception {
390+
TestResponseInterceptor interceptor = initializeAppForUserManagement(
391+
TestUtils.loadResource("getUser.json"));
392+
UserRecord userRecord = FirebaseAuth.getInstance()
393+
.getUserByProviderUidAsync("phone", "+1234567890").get();
394+
checkUserRecord(userRecord);
395+
checkRequestHeaders(interceptor);
396+
}
397+
398+
@Test
399+
public void testGetUserByProviderUidWithEmail() throws Exception {
400+
TestResponseInterceptor interceptor = initializeAppForUserManagement(
401+
TestUtils.loadResource("getUser.json"));
402+
UserRecord userRecord = FirebaseAuth.getInstance()
403+
.getUserByProviderUidAsync("email", "[email protected]").get();
404+
checkUserRecord(userRecord);
405+
checkRequestHeaders(interceptor);
406+
}
407+
358408
@Test
359409
public void testListUsers() throws Exception {
360410
final TestResponseInterceptor interceptor = initializeAppForUserManagement(
@@ -2821,7 +2871,7 @@ private static void checkUserRecord(UserRecord userRecord) {
28212871
assertEquals("http://www.example.com/testuser/photo.png", userRecord.getPhotoUrl());
28222872
assertEquals(1234567890, userRecord.getUserMetadata().getCreationTimestamp());
28232873
assertEquals(0, userRecord.getUserMetadata().getLastSignInTimestamp());
2824-
assertEquals(2, userRecord.getProviderData().length);
2874+
assertEquals(3, userRecord.getProviderData().length);
28252875
assertFalse(userRecord.isDisabled());
28262876
assertTrue(userRecord.isEmailVerified());
28272877
assertEquals(1494364393000L, userRecord.getTokensValidAfterTimestamp());
@@ -2841,6 +2891,10 @@ private static void checkUserRecord(UserRecord userRecord) {
28412891
assertEquals("+1234567890", provider.getPhoneNumber());
28422892
assertEquals("phone", provider.getProviderId());
28432893

2894+
provider = userRecord.getProviderData()[2];
2895+
assertEquals("google_uid", provider.getUid());
2896+
assertEquals("google.com", provider.getProviderId());
2897+
28442898
Map<String, Object> claims = userRecord.getCustomClaims();
28452899
assertEquals(2, claims.size());
28462900
assertTrue((boolean) claims.get("admin"));

src/test/java/com/google/firebase/snippets/FirebaseAuthSnippets.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,16 @@ public static void getUserByPhoneNumber(
9494
// [END get_user_by_phone]
9595
}
9696

97+
public static void getUserByProviderUId(
98+
String providerId, String uid) throws FirebaseAuthException {
99+
// [START get_user_by_provider_uid]
100+
UserRecord userRecord = FirebaseAuth.getInstance().getUserByProviderUid(
101+
providerId, uid);
102+
// See the UserRecord reference doc for the contents of userRecord.
103+
System.out.println("Successfully fetched user data: " + userRecord.getUid());
104+
// [END get_user_by_provider_uid]
105+
}
106+
97107
public static void createUser() throws FirebaseAuthException {
98108
// [START create_user]
99109
CreateRequest request = new CreateRequest()

src/test/resources/getUser.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
"providerId" : "phone",
1818
"phoneNumber" : "+1234567890",
1919
"rawId" : "+1234567890"
20+
}, {
21+
"providerId" : "google.com",
22+
"rawId" : "google_uid"
2023
} ],
2124
"photoUrl" : "http://www.example.com/testuser/photo.png",
2225
"passwordHash" : "passwordhash",

src/test/resources/listUsers.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
"providerId" : "phone",
1717
"phoneNumber" : "+1234567890",
1818
"rawId" : "+1234567890"
19+
}, {
20+
"providerId" : "google.com",
21+
"rawId" : "google_uid"
1922
} ],
2023
"photoUrl" : "http://www.example.com/testuser/photo.png",
2124
"passwordHash" : "passwordHash",
@@ -43,6 +46,9 @@
4346
"providerId" : "phone",
4447
"phoneNumber" : "+1234567890",
4548
"rawId" : "+1234567890"
49+
}, {
50+
"providerId" : "google.com",
51+
"rawId" : "google_uid"
4652
} ],
4753
"photoUrl" : "http://www.example.com/testuser/photo.png",
4854
"passwordHash" : "passwordHash",

0 commit comments

Comments
 (0)