Skip to content

Commit 52608d5

Browse files
committed
Add API to link/unlink provider info to/from user record.
1 parent d01b062 commit 52608d5

File tree

3 files changed

+179
-69
lines changed

3 files changed

+179
-69
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 & 66 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;
@@ -178,73 +179,134 @@ public void testCreateUserWithParams() throws Exception {
178179
public void testUserLifecycle() throws Exception {
179180
// Create user
180181
UserRecord userRecord = auth.createUserAsync(new CreateRequest()).get();
181-
String uid = userRecord.getUid();
182-
183-
// Get user
184-
userRecord = auth.getUserAsync(userRecord.getUid()).get();
185-
assertEquals(uid, userRecord.getUid());
186-
assertNull(userRecord.getDisplayName());
187-
assertNull(userRecord.getEmail());
188-
assertNull(userRecord.getPhoneNumber());
189-
assertNull(userRecord.getPhotoUrl());
190-
assertFalse(userRecord.isEmailVerified());
191-
assertFalse(userRecord.isDisabled());
192-
assertTrue(userRecord.getUserMetadata().getCreationTimestamp() > 0);
193-
assertEquals(0, userRecord.getUserMetadata().getLastSignInTimestamp());
194-
assertEquals(0, userRecord.getProviderData().length);
195-
assertTrue(userRecord.getCustomClaims().isEmpty());
196-
197-
// Update user
198-
RandomUser randomUser = RandomUser.create();
199-
String phone = randomPhoneNumber();
200-
UpdateRequest request = userRecord.updateRequest()
201-
.setDisplayName("Updated Name")
202-
.setEmail(randomUser.email)
203-
.setPhoneNumber(phone)
204-
.setPhotoUrl("https://example.com/photo.png")
205-
.setEmailVerified(true)
206-
.setPassword("secret");
207-
userRecord = auth.updateUserAsync(request).get();
208-
assertEquals(uid, userRecord.getUid());
209-
assertEquals("Updated Name", userRecord.getDisplayName());
210-
assertEquals(randomUser.email, userRecord.getEmail());
211-
assertEquals(phone, userRecord.getPhoneNumber());
212-
assertEquals("https://example.com/photo.png", userRecord.getPhotoUrl());
213-
assertTrue(userRecord.isEmailVerified());
214-
assertFalse(userRecord.isDisabled());
215-
assertEquals(2, userRecord.getProviderData().length);
216-
assertTrue(userRecord.getCustomClaims().isEmpty());
217-
218-
// Get user by email
219-
userRecord = auth.getUserByEmailAsync(userRecord.getEmail()).get();
220-
assertEquals(uid, userRecord.getUid());
221-
222-
// Disable user and remove properties
223-
request = userRecord.updateRequest()
224-
.setPhotoUrl(null)
225-
.setDisplayName(null)
226-
.setPhoneNumber(null)
227-
.setDisabled(true);
228-
userRecord = auth.updateUserAsync(request).get();
229-
assertEquals(uid, userRecord.getUid());
230-
assertNull(userRecord.getDisplayName());
231-
assertEquals(randomUser.email, userRecord.getEmail());
232-
assertNull(userRecord.getPhoneNumber());
233-
assertNull(userRecord.getPhotoUrl());
234-
assertTrue(userRecord.isEmailVerified());
235-
assertTrue(userRecord.isDisabled());
236-
assertEquals(1, userRecord.getProviderData().length);
237-
assertTrue(userRecord.getCustomClaims().isEmpty());
238-
239-
// Delete user
240-
auth.deleteUserAsync(userRecord.getUid()).get();
241182
try {
242-
auth.getUserAsync(userRecord.getUid()).get();
243-
fail("No error thrown for deleted user");
244-
} catch (ExecutionException e) {
245-
assertTrue(e.getCause() instanceof FirebaseAuthException);
246-
assertEquals(FirebaseUserManager.USER_NOT_FOUND_ERROR,
247-
((FirebaseAuthException) e.getCause()).getErrorCode());
183+
String uid = userRecord.getUid();
184+
185+
// Get user
186+
userRecord = auth.getUserAsync(userRecord.getUid()).get();
187+
assertEquals(uid, userRecord.getUid());
188+
assertNull(userRecord.getDisplayName());
189+
assertNull(userRecord.getEmail());
190+
assertNull(userRecord.getPhoneNumber());
191+
assertNull(userRecord.getPhotoUrl());
192+
assertFalse(userRecord.isEmailVerified());
193+
assertFalse(userRecord.isDisabled());
194+
assertTrue(userRecord.getUserMetadata().getCreationTimestamp() > 0);
195+
assertEquals(0, userRecord.getUserMetadata().getLastSignInTimestamp());
196+
assertEquals(0, userRecord.getProviderData().length);
197+
assertTrue(userRecord.getCustomClaims().isEmpty());
198+
199+
// Update user
200+
RandomUser randomUser = RandomUser.create();
201+
UpdateRequest request = userRecord.updateRequest()
202+
.setDisplayName("Updated Name")
203+
.setEmail(randomUser.email)
204+
.setPhoneNumber(randomUser.phone)
205+
.setPhotoUrl("https://example.com/photo.png")
206+
.setEmailVerified(true)
207+
.setPassword("secret");
208+
userRecord = auth.updateUserAsync(request).get();
209+
assertEquals(uid, userRecord.getUid());
210+
assertEquals("Updated Name", userRecord.getDisplayName());
211+
assertEquals(randomUser.email, userRecord.getEmail());
212+
assertEquals(randomUser.phone, userRecord.getPhoneNumber());
213+
assertEquals("https://example.com/photo.png", userRecord.getPhotoUrl());
214+
assertTrue(userRecord.isEmailVerified());
215+
assertFalse(userRecord.isDisabled());
216+
assertEquals(2, userRecord.getProviderData().length);
217+
assertTrue(userRecord.getCustomClaims().isEmpty());
218+
219+
// Link user to IDP providers
220+
request = userRecord.updateRequest()
221+
.linkProvider(
222+
UserProvider
223+
.builder()
224+
.setUid("testuid")
225+
.setProviderId("google.com")
226+
.setEmail("[email protected]")
227+
.setDisplayName("Test User")
228+
.setPhotoUrl("https://test.com/user.png")
229+
.build());
230+
userRecord = auth.updateUserAsync(request).get();
231+
assertEquals(uid, userRecord.getUid());
232+
assertEquals("Updated Name", userRecord.getDisplayName());
233+
assertEquals(randomUser.email, userRecord.getEmail());
234+
assertEquals(randomUser.phone, userRecord.getPhoneNumber());
235+
assertEquals("https://example.com/photo.png", userRecord.getPhotoUrl());
236+
assertTrue(userRecord.isEmailVerified());
237+
assertFalse(userRecord.isDisabled());
238+
assertEquals(3, userRecord.getProviderData().length);
239+
List<String> providers = new ArrayList<>();
240+
for (UserInfo provider : userRecord.getProviderData()) {
241+
providers.add(provider.getProviderId());
242+
}
243+
assertTrue(providers.contains("google.com"));
244+
assertTrue(userRecord.getCustomClaims().isEmpty());
245+
246+
// Unlink phone provider
247+
request = userRecord.updateRequest().deleteProvider("phone");
248+
userRecord = auth.updateUserAsync(request).get();
249+
assertNull(userRecord.getPhoneNumber());
250+
assertEquals(2, userRecord.getProviderData().length);
251+
providers.clear();
252+
for (UserInfo provider : userRecord.getProviderData()) {
253+
providers.add(provider.getProviderId());
254+
}
255+
assertFalse(providers.contains("phone"));
256+
assertEquals(uid, userRecord.getUid());
257+
assertEquals("Updated Name", userRecord.getDisplayName());
258+
assertEquals(randomUser.email, userRecord.getEmail());
259+
assertEquals("https://example.com/photo.png", userRecord.getPhotoUrl());
260+
assertTrue(userRecord.isEmailVerified());
261+
assertFalse(userRecord.isDisabled());
262+
assertTrue(userRecord.getCustomClaims().isEmpty());
263+
264+
// Unlink IDP provider
265+
request = userRecord.updateRequest().deleteProvider("google.com");
266+
userRecord = auth.updateUserAsync(request).get();
267+
assertEquals(1, userRecord.getProviderData().length);
268+
assertNotEquals("google.com", userRecord.getProviderData()[0].getProviderId());
269+
assertEquals(uid, userRecord.getUid());
270+
assertEquals("Updated Name", userRecord.getDisplayName());
271+
assertEquals(randomUser.email, userRecord.getEmail());
272+
assertNull(userRecord.getPhoneNumber());
273+
assertEquals("https://example.com/photo.png", userRecord.getPhotoUrl());
274+
assertTrue(userRecord.isEmailVerified());
275+
assertFalse(userRecord.isDisabled());
276+
assertTrue(userRecord.getCustomClaims().isEmpty());
277+
278+
// Get user by email
279+
userRecord = auth.getUserByEmailAsync(userRecord.getEmail()).get();
280+
assertEquals(uid, userRecord.getUid());
281+
282+
// Disable user and remove properties
283+
request = userRecord.updateRequest()
284+
.setPhotoUrl(null)
285+
.setDisplayName(null)
286+
.setPhoneNumber(null)
287+
.setDisabled(true);
288+
userRecord = auth.updateUserAsync(request).get();
289+
assertEquals(uid, userRecord.getUid());
290+
assertNull(userRecord.getDisplayName());
291+
assertEquals(randomUser.email, userRecord.getEmail());
292+
assertNull(userRecord.getPhoneNumber());
293+
assertNull(userRecord.getPhotoUrl());
294+
assertTrue(userRecord.isEmailVerified());
295+
assertTrue(userRecord.isDisabled());
296+
assertEquals(1, userRecord.getProviderData().length);
297+
assertTrue(userRecord.getCustomClaims().isEmpty());
298+
299+
} finally {
300+
// Delete user
301+
auth.deleteUserAsync(userRecord.getUid()).get();
302+
try {
303+
auth.getUserAsync(userRecord.getUid()).get();
304+
fail("No error thrown for deleted user");
305+
} catch (ExecutionException e) {
306+
assertTrue(e.getCause() instanceof FirebaseAuthException);
307+
assertEquals(FirebaseUserManager.USER_NOT_FOUND_ERROR,
308+
((FirebaseAuthException) e.getCause()).getErrorCode());
309+
}
248310
}
249311
}
250312

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)