Skip to content

Commit ecd0525

Browse files
committed
Add API to link/unlink provider info to/from user record.
1 parent c8c807d commit ecd0525

File tree

3 files changed

+179
-68
lines changed

3 files changed

+179
-68
lines changed

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,9 @@ public UserInfo[] getProviderData() {
190190
}
191191

192192
/**
193-
* Returns a timestamp in milliseconds since epoch, truncated down to the closest second.
193+
* Returns a timestamp in milliseconds since epoch, truncated down to the closest second.
194194
* Tokens minted before this timestamp are considered invalid.
195-
*
195+
*
196196
* @return Timestamp in milliseconds since the epoch. Tokens minted before this timestamp are
197197
* considered invalid.
198198
*/
@@ -261,6 +261,10 @@ private static void checkPassword(String password) {
261261
checkArgument(password.length() >= 6, "password must be at least 6 characters long");
262262
}
263263

264+
private static void checkProviderId(String providerId) {
265+
checkArgument(!Strings.isNullOrEmpty(providerId), "providerId cannot be null or empty");
266+
}
267+
264268
static void checkCustomClaims(Map<String,Object> customClaims) {
265269
if (customClaims == null) {
266270
return;
@@ -528,6 +532,17 @@ UpdateRequest setValidSince(long epochSeconds) {
528532
return this;
529533
}
530534

535+
UpdateRequest linkProvider(@NonNull UserProvider userProvider) {
536+
properties.put("linkProviderUserInfo", userProvider);
537+
return this;
538+
}
539+
540+
UpdateRequest deleteProvider(String providerId) {
541+
checkProviderId(providerId);
542+
properties.put("deleteProvider", ImmutableList.of(providerId));
543+
return this;
544+
}
545+
531546
Map<String, Object> getProperties(JsonFactory jsonFactory) {
532547
Map<String, Object> copy = new HashMap<>(properties);
533548
List<String> remove = new ArrayList<>();

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

Lines changed: 128 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static org.junit.Assert.assertEquals;
2020
import static org.junit.Assert.assertFalse;
21+
import static org.junit.Assert.assertNotEquals;
2122
import static org.junit.Assert.assertNotNull;
2223
import static org.junit.Assert.assertNull;
2324
import static org.junit.Assert.assertTrue;
@@ -234,72 +235,134 @@ public void testLookupUserByFederatedId() throws Exception {
234235
public void testUserLifecycle() throws Exception {
235236
// Create user
236237
UserRecord userRecord = auth.createUserAsync(new CreateRequest()).get();
237-
String uid = userRecord.getUid();
238-
239-
// Get user
240-
userRecord = auth.getUserAsync(userRecord.getUid()).get();
241-
assertEquals(uid, userRecord.getUid());
242-
assertNull(userRecord.getDisplayName());
243-
assertNull(userRecord.getEmail());
244-
assertNull(userRecord.getPhoneNumber());
245-
assertNull(userRecord.getPhotoUrl());
246-
assertFalse(userRecord.isEmailVerified());
247-
assertFalse(userRecord.isDisabled());
248-
assertTrue(userRecord.getUserMetadata().getCreationTimestamp() > 0);
249-
assertEquals(0, userRecord.getUserMetadata().getLastSignInTimestamp());
250-
assertEquals(0, userRecord.getProviderData().length);
251-
assertTrue(userRecord.getCustomClaims().isEmpty());
252-
253-
// Update user
254-
RandomUser randomUser = RandomUser.create();
255-
UpdateRequest request = userRecord.updateRequest()
256-
.setDisplayName("Updated Name")
257-
.setEmail(randomUser.email)
258-
.setPhoneNumber(randomUser.phone)
259-
.setPhotoUrl("https://example.com/photo.png")
260-
.setEmailVerified(true)
261-
.setPassword("secret");
262-
userRecord = auth.updateUserAsync(request).get();
263-
assertEquals(uid, userRecord.getUid());
264-
assertEquals("Updated Name", userRecord.getDisplayName());
265-
assertEquals(randomUser.email, userRecord.getEmail());
266-
assertEquals(randomUser.phone, userRecord.getPhoneNumber());
267-
assertEquals("https://example.com/photo.png", userRecord.getPhotoUrl());
268-
assertTrue(userRecord.isEmailVerified());
269-
assertFalse(userRecord.isDisabled());
270-
assertEquals(2, userRecord.getProviderData().length);
271-
assertTrue(userRecord.getCustomClaims().isEmpty());
272-
273-
// Get user by email
274-
userRecord = auth.getUserByEmailAsync(userRecord.getEmail()).get();
275-
assertEquals(uid, userRecord.getUid());
276-
277-
// Disable user and remove properties
278-
request = userRecord.updateRequest()
279-
.setPhotoUrl(null)
280-
.setDisplayName(null)
281-
.setPhoneNumber(null)
282-
.setDisabled(true);
283-
userRecord = auth.updateUserAsync(request).get();
284-
assertEquals(uid, userRecord.getUid());
285-
assertNull(userRecord.getDisplayName());
286-
assertEquals(randomUser.email, userRecord.getEmail());
287-
assertNull(userRecord.getPhoneNumber());
288-
assertNull(userRecord.getPhotoUrl());
289-
assertTrue(userRecord.isEmailVerified());
290-
assertTrue(userRecord.isDisabled());
291-
assertEquals(1, userRecord.getProviderData().length);
292-
assertTrue(userRecord.getCustomClaims().isEmpty());
293-
294-
// Delete user
295-
auth.deleteUserAsync(userRecord.getUid()).get();
296238
try {
297-
auth.getUserAsync(userRecord.getUid()).get();
298-
fail("No error thrown for deleted user");
299-
} catch (ExecutionException e) {
300-
assertTrue(e.getCause() instanceof FirebaseAuthException);
301-
assertEquals(FirebaseUserManager.USER_NOT_FOUND_ERROR,
302-
((FirebaseAuthException) e.getCause()).getErrorCode());
239+
String uid = userRecord.getUid();
240+
241+
// Get user
242+
userRecord = auth.getUserAsync(userRecord.getUid()).get();
243+
assertEquals(uid, userRecord.getUid());
244+
assertNull(userRecord.getDisplayName());
245+
assertNull(userRecord.getEmail());
246+
assertNull(userRecord.getPhoneNumber());
247+
assertNull(userRecord.getPhotoUrl());
248+
assertFalse(userRecord.isEmailVerified());
249+
assertFalse(userRecord.isDisabled());
250+
assertTrue(userRecord.getUserMetadata().getCreationTimestamp() > 0);
251+
assertEquals(0, userRecord.getUserMetadata().getLastSignInTimestamp());
252+
assertEquals(0, userRecord.getProviderData().length);
253+
assertTrue(userRecord.getCustomClaims().isEmpty());
254+
255+
// Update user
256+
RandomUser randomUser = RandomUser.create();
257+
UpdateRequest request = userRecord.updateRequest()
258+
.setDisplayName("Updated Name")
259+
.setEmail(randomUser.email)
260+
.setPhoneNumber(randomUser.phone)
261+
.setPhotoUrl("https://example.com/photo.png")
262+
.setEmailVerified(true)
263+
.setPassword("secret");
264+
userRecord = auth.updateUserAsync(request).get();
265+
assertEquals(uid, userRecord.getUid());
266+
assertEquals("Updated Name", userRecord.getDisplayName());
267+
assertEquals(randomUser.email, userRecord.getEmail());
268+
assertEquals(randomUser.phone, userRecord.getPhoneNumber());
269+
assertEquals("https://example.com/photo.png", userRecord.getPhotoUrl());
270+
assertTrue(userRecord.isEmailVerified());
271+
assertFalse(userRecord.isDisabled());
272+
assertEquals(2, userRecord.getProviderData().length);
273+
assertTrue(userRecord.getCustomClaims().isEmpty());
274+
275+
// Link user to IDP providers
276+
request = userRecord.updateRequest()
277+
.linkProvider(
278+
UserProvider
279+
.builder()
280+
.setUid("testuid")
281+
.setProviderId("google.com")
282+
.setEmail("[email protected]")
283+
.setDisplayName("Test User")
284+
.setPhotoUrl("https://test.com/user.png")
285+
.build());
286+
userRecord = auth.updateUserAsync(request).get();
287+
assertEquals(uid, userRecord.getUid());
288+
assertEquals("Updated Name", userRecord.getDisplayName());
289+
assertEquals(randomUser.email, userRecord.getEmail());
290+
assertEquals(randomUser.phone, userRecord.getPhoneNumber());
291+
assertEquals("https://example.com/photo.png", userRecord.getPhotoUrl());
292+
assertTrue(userRecord.isEmailVerified());
293+
assertFalse(userRecord.isDisabled());
294+
assertEquals(3, userRecord.getProviderData().length);
295+
List<String> providers = new ArrayList<>();
296+
for (UserInfo provider : userRecord.getProviderData()) {
297+
providers.add(provider.getProviderId());
298+
}
299+
assertTrue(providers.contains("google.com"));
300+
assertTrue(userRecord.getCustomClaims().isEmpty());
301+
302+
// Unlink phone provider
303+
request = userRecord.updateRequest().deleteProvider("phone");
304+
userRecord = auth.updateUserAsync(request).get();
305+
assertNull(userRecord.getPhoneNumber());
306+
assertEquals(2, userRecord.getProviderData().length);
307+
providers.clear();
308+
for (UserInfo provider : userRecord.getProviderData()) {
309+
providers.add(provider.getProviderId());
310+
}
311+
assertFalse(providers.contains("phone"));
312+
assertEquals(uid, userRecord.getUid());
313+
assertEquals("Updated Name", userRecord.getDisplayName());
314+
assertEquals(randomUser.email, userRecord.getEmail());
315+
assertEquals("https://example.com/photo.png", userRecord.getPhotoUrl());
316+
assertTrue(userRecord.isEmailVerified());
317+
assertFalse(userRecord.isDisabled());
318+
assertTrue(userRecord.getCustomClaims().isEmpty());
319+
320+
// Unlink IDP provider
321+
request = userRecord.updateRequest().deleteProvider("google.com");
322+
userRecord = auth.updateUserAsync(request).get();
323+
assertEquals(1, userRecord.getProviderData().length);
324+
assertNotEquals("google.com", userRecord.getProviderData()[0].getProviderId());
325+
assertEquals(uid, userRecord.getUid());
326+
assertEquals("Updated Name", userRecord.getDisplayName());
327+
assertEquals(randomUser.email, userRecord.getEmail());
328+
assertNull(userRecord.getPhoneNumber());
329+
assertEquals("https://example.com/photo.png", userRecord.getPhotoUrl());
330+
assertTrue(userRecord.isEmailVerified());
331+
assertFalse(userRecord.isDisabled());
332+
assertTrue(userRecord.getCustomClaims().isEmpty());
333+
334+
// Get user by email
335+
userRecord = auth.getUserByEmailAsync(userRecord.getEmail()).get();
336+
assertEquals(uid, userRecord.getUid());
337+
338+
// Disable user and remove properties
339+
request = userRecord.updateRequest()
340+
.setPhotoUrl(null)
341+
.setDisplayName(null)
342+
.setPhoneNumber(null)
343+
.setDisabled(true);
344+
userRecord = auth.updateUserAsync(request).get();
345+
assertEquals(uid, userRecord.getUid());
346+
assertNull(userRecord.getDisplayName());
347+
assertEquals(randomUser.email, userRecord.getEmail());
348+
assertNull(userRecord.getPhoneNumber());
349+
assertNull(userRecord.getPhotoUrl());
350+
assertTrue(userRecord.isEmailVerified());
351+
assertTrue(userRecord.isDisabled());
352+
assertEquals(1, userRecord.getProviderData().length);
353+
assertTrue(userRecord.getCustomClaims().isEmpty());
354+
355+
} finally {
356+
// Delete user
357+
auth.deleteUserAsync(userRecord.getUid()).get();
358+
try {
359+
auth.getUserAsync(userRecord.getUid()).get();
360+
fail("No error thrown for deleted user");
361+
} catch (ExecutionException e) {
362+
assertTrue(e.getCause() instanceof FirebaseAuthException);
363+
assertEquals(FirebaseUserManager.USER_NOT_FOUND_ERROR,
364+
((FirebaseAuthException) e.getCause()).getErrorCode());
365+
}
303366
}
304367
}
305368

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

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ public class FirebaseUserManagerTest {
7474
.build();
7575
private static final Map<String, Object> ACTION_CODE_SETTINGS_MAP =
7676
ACTION_CODE_SETTINGS.getProperties();
77+
private static final UserProvider USER_PROVIDER = UserProvider.builder()
78+
.setUid("testuid")
79+
.setProviderId("facebook.com")
80+
.setEmail("[email protected]")
81+
.setDisplayName("Test User")
82+
.setPhotoUrl("https://test.com/user.png")
83+
.build();
7784

7885
@After
7986
public void tearDown() {
@@ -826,8 +833,10 @@ public void testUserUpdater() throws IOException {
826833
.setEmailVerified(true)
827834
.setPassword("secret")
828835
.setCustomClaims(claims)
836+
.linkProvider(USER_PROVIDER)
837+
.deleteProvider("google.com")
829838
.getProperties(Utils.getDefaultJsonFactory());
830-
assertEquals(8, map.size());
839+
assertEquals(10, map.size());
831840
assertEquals(update.getUid(), map.get("localId"));
832841
assertEquals("Display Name", map.get("displayName"));
833842
assertEquals("http://test.com/example.png", map.get("photoUrl"));
@@ -836,6 +845,8 @@ public void testUserUpdater() throws IOException {
836845
assertTrue((Boolean) map.get("emailVerified"));
837846
assertEquals("secret", map.get("password"));
838847
assertEquals(Utils.getDefaultJsonFactory().toString(claims), map.get("customAttributes"));
848+
assertEquals(USER_PROVIDER, map.get("linkProviderUserInfo"));
849+
assertEquals(ImmutableList.of("google.com"), map.get("deleteProvider"));
839850
}
840851

841852
@Test
@@ -873,6 +884,28 @@ public void testEmptyCustomClaims() {
873884
assertEquals("{}", map.get("customAttributes"));
874885
}
875886

887+
@Test
888+
public void testLinkProvider() {
889+
UpdateRequest update = new UpdateRequest("test");
890+
Map<String, Object> map = update
891+
.linkProvider(USER_PROVIDER)
892+
.getProperties(Utils.getDefaultJsonFactory());
893+
assertEquals(2, map.size());
894+
assertEquals(update.getUid(), map.get("localId"));
895+
assertEquals(USER_PROVIDER, map.get("linkProviderUserInfo"));
896+
}
897+
898+
@Test
899+
public void testDeleteProvider() {
900+
UpdateRequest update = new UpdateRequest("test");
901+
Map<String, Object> map = update
902+
.deleteProvider("google.com")
903+
.getProperties(Utils.getDefaultJsonFactory());
904+
assertEquals(2, map.size());
905+
assertEquals(update.getUid(), map.get("localId"));
906+
assertEquals(ImmutableList.of("google.com"), map.get("deleteProvider"));
907+
}
908+
876909
@Test
877910
public void testDeleteDisplayName() {
878911
Map<String, Object> map = new UpdateRequest("test")

0 commit comments

Comments
 (0)