Repository: firebase/firebase-admin-java
Pull request #37 (2017-06-02 23:41:43)

Refactoring the User Management API
Following changes were suggested at the API review:
* Introduce `UserInfo` public interface type
* Rename `User` to `UserRecord`
* Rename `User.Builder` to `UserRecord.CreateRequest`
* Rename `User.Updater` to `UserRecord.UpdateRequest`
* Drop `ProviderUserInfo` from the public API
* Implement `UserInfo` at both `UserRecord` and `ProviderUserInfo`


hiranya911: Made the suggested changes.

Patch:
diff --git a/src/main/java/com/google/firebase/auth/FirebaseAuth.java b/src/main/java/com/google/firebase/auth/FirebaseAuth.java
index 3b20145..d505645 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseAuth.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseAuth.java
@@ -30,6 +30,8 @@ import com.google.common.base.Strings;
 import com.google.firebase.FirebaseApp;
 import com.google.firebase.FirebaseException;
 import com.google.firebase.ImplFirebaseTrampolines;
+import com.google.firebase.auth.UserRecord.CreateRequest;
+import com.google.firebase.auth.UserRecord.UpdateRequest;
 import com.google.firebase.auth.internal.FirebaseTokenFactory;
 import com.google.firebase.auth.internal.FirebaseTokenVerifier;
 import com.google.firebase.internal.FirebaseService;
@@ -207,17 +209,17 @@ public class FirebaseAuth {
    * Gets the user data corresponding to the specified user ID.
    *
    * @param uid A user ID string.
-   * @return A {@link Task} which will complete successfully with a {@link User} instance.
+   * @return A {@link Task} which will complete successfully with a {@link UserRecord} instance.
    *     If an error occurs while retrieving user data or if the specified user ID does not exist,
    *     the task fails with a FirebaseAuthException.
    * @throws IllegalArgumentException If the user ID string is null or empty.
    */
-  public Task<User> getUser(final String uid) {
+  public Task<UserRecord> getUser(final String uid) {
     checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty");
     return ImplFirebaseTrampolines.getToken(firebaseApp, false).continueWith(
-        new Continuation<GetTokenResult, User>() {
+        new Continuation<GetTokenResult, UserRecord>() {
           @Override
-          public User then(Task<GetTokenResult> task) throws Exception {
+          public UserRecord then(Task<GetTokenResult> task) throws Exception {
             return userManager.getUserById(uid, task.getResult().getToken());
           }
         });
@@ -227,38 +229,39 @@ public class FirebaseAuth {
    * Gets the user data corresponding to the specified user email.
    *
    * @param email A user email address string.
-   * @return A {@link Task} which will complete successfully with a {@link User} instance.
+   * @return A {@link Task} which will complete successfully with a {@link UserRecord} instance.
    *     If an error occurs while retrieving user data or if the email address does not correspond
    *     to a user, the task fails with a FirebaseAuthException.
    * @throws IllegalArgumentException If the email is null or empty.
    */
-  public Task<User> getUserByEmail(final String email) {
+  public Task<UserRecord> getUserByEmail(final String email) {
     checkArgument(!Strings.isNullOrEmpty(email), "email must not be null or empty");
     return ImplFirebaseTrampolines.getToken(firebaseApp, false).continueWith(
-        new Continuation<GetTokenResult, User>() {
+        new Continuation<GetTokenResult, UserRecord>() {
           @Override
-          public User then(Task<GetTokenResult> task) throws Exception {
+          public UserRecord then(Task<GetTokenResult> task) throws Exception {
             return userManager.getUserByEmail(email, task.getResult().getToken());
           }
         });
   }
 
   /**
-   * Creates a new user account with the attributes contained in the specified {@link User.Builder}.
+   * Creates a new user account with the attributes contained in the specified
+   * {@link CreateRequest}.
    *
-   * @param builder A non-null {@link User.Builder} instance.
-   * @return A {@link Task} which will complete successfully with a {@link User} instance
+   * @param request A non-null {@link CreateRequest} instance.
+   * @return A {@link Task} which will complete successfully with a {@link UserRecord} instance
    *     corresponding to the newly created account. If an error occurs while creating the user
    *     account, the task fails with a FirebaseAuthException.
-   * @throws NullPointerException if the provided builder is null.
+   * @throws NullPointerException if the provided request is null.
    */
-  public Task<User> createUser(final User.Builder builder) {
-    checkNotNull(builder, "builder must not be null");
+  public Task<UserRecord> createUser(final CreateRequest request) {
+    checkNotNull(request, "create request must not be null");
     return ImplFirebaseTrampolines.getToken(firebaseApp, false).continueWith(
-        new Continuation<GetTokenResult, User>() {
+        new Continuation<GetTokenResult, UserRecord>() {
           @Override
-          public User then(Task<GetTokenResult> task) throws Exception {
-            String uid = userManager.createUser(builder, task.getResult().getToken());
+          public UserRecord then(Task<GetTokenResult> task) throws Exception {
+            String uid = userManager.createUser(request, task.getResult().getToken());
             return userManager.getUserById(uid, task.getResult().getToken());
           }
         });
@@ -266,22 +269,22 @@ public class FirebaseAuth {
 
   /**
    * Updates an existing user account with the attributes contained in the specified
-   * {@link User.Updater}.
+   * {@link UpdateRequest}.
    *
-   * @param updater A non-null {@link User.Updater} instance.
-   * @return A {@link Task} which will complete successfully with a {@link User} instance
+   * @param request A non-null {@link UpdateRequest} instance.
+   * @return A {@link Task} which will complete successfully with a {@link UserRecord} instance
    *     corresponding to the updated user account. If an error occurs while updating the user
    *     account, the task fails with a FirebaseAuthException.
-   * @throws NullPointerException if the provided updater is null.
+   * @throws NullPointerException if the provided update request is null.
    */
-  public Task<User> updateUser(final User.Updater updater) {
-    checkNotNull(updater, "updater must not be null");
+  public Task<UserRecord> updateUser(final UpdateRequest request) {
+    checkNotNull(request, "update request must not be null");
     return ImplFirebaseTrampolines.getToken(firebaseApp, false).continueWith(
-        new Continuation<GetTokenResult, User>() {
+        new Continuation<GetTokenResult, UserRecord>() {
           @Override
-          public User then(Task<GetTokenResult> task) throws Exception {
-            userManager.updateUser(updater, task.getResult().getToken());
-            return userManager.getUserById(updater.getUid(), task.getResult().getToken());
+          public UserRecord then(Task<GetTokenResult> task) throws Exception {
+            userManager.updateUser(request, task.getResult().getToken());
+            return userManager.getUserById(request.getUid(), task.getResult().getToken());
           }
         });
   }
diff --git a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
index f3be905..ff57a63 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
@@ -31,6 +31,8 @@ import com.google.api.client.json.JsonObjectParser;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.firebase.auth.UserRecord.CreateRequest;
+import com.google.firebase.auth.UserRecord.UpdateRequest;
 import com.google.firebase.auth.internal.GetAccountInfoResponse;
 
 import java.io.IOException;
@@ -68,7 +70,7 @@ class FirebaseUserManager {
     this.requestFactory = transport.createRequestFactory();
   }
 
-  User getUserById(String uid, String token) throws FirebaseAuthException {
+  UserRecord getUserById(String uid, String token) throws FirebaseAuthException {
     final Map<String, Object> payload = ImmutableMap.<String, Object>of(
         "localId", ImmutableList.of(uid));
     GetAccountInfoResponse response;
@@ -84,10 +86,10 @@ class FirebaseUserManager {
       throw new FirebaseAuthException(USER_NOT_FOUND_ERROR,
           "No user record found for the provided user ID: " + uid);
     }
-    return new User(response.getUsers().get(0));
+    return new UserRecord(response.getUsers().get(0));
   }
 
-  User getUserByEmail(String email, String token) throws FirebaseAuthException {
+  UserRecord getUserByEmail(String email, String token) throws FirebaseAuthException {
     final Map<String, Object> payload = ImmutableMap.<String, Object>of(
         "email", ImmutableList.of(email));
     GetAccountInfoResponse response;
@@ -102,13 +104,13 @@ class FirebaseUserManager {
       throw new FirebaseAuthException(USER_NOT_FOUND_ERROR,
           "No user record found for the provided email: " + email);
     }
-    return new User(response.getUsers().get(0));
+    return new UserRecord(response.getUsers().get(0));
   }
 
-  String createUser(User.Builder builder, String token) throws FirebaseAuthException {
+  String createUser(CreateRequest request, String token) throws FirebaseAuthException {
     GenericJson response;
     try {
-      response = post("signupNewUser", token, builder.build(), GenericJson.class);
+      response = post("signupNewUser", token, request.getProperties(), GenericJson.class);
     } catch (IOException e) {
       throw new FirebaseAuthException(USER_CREATE_ERROR,
           "IO error while creating user account", e);
@@ -123,18 +125,18 @@ class FirebaseUserManager {
     throw new FirebaseAuthException(USER_CREATE_ERROR, "Failed to create new user");
   }
 
-  void updateUser(User.Updater updater, String token) throws FirebaseAuthException {
+  void updateUser(UpdateRequest request, String token) throws FirebaseAuthException {
     GenericJson response;
     try {
-      response = post("setAccountInfo", token, updater.update(), GenericJson.class);
+      response = post("setAccountInfo", token, request.getProperties(), GenericJson.class);
     } catch (IOException e) {
       throw new FirebaseAuthException(USER_UPDATE_ERROR,
-          "IO error while updating user: " + updater.getUid(), e);
+          "IO error while updating user: " + request.getUid(), e);
     }
 
-    if (response == null || !updater.getUid().equals(response.get("localId"))) {
+    if (response == null || !request.getUid().equals(response.get("localId"))) {
       throw new FirebaseAuthException(USER_UPDATE_ERROR,
-          "Failed to update user: " + updater.getUid());
+          "Failed to update user: " + request.getUid());
     }
   }
 
diff --git a/src/main/java/com/google/firebase/auth/ProviderUserInfo.java b/src/main/java/com/google/firebase/auth/ProviderUserInfo.java
index 25b1f65..07ed154 100644
--- a/src/main/java/com/google/firebase/auth/ProviderUserInfo.java
+++ b/src/main/java/com/google/firebase/auth/ProviderUserInfo.java
@@ -23,7 +23,7 @@ import com.google.firebase.internal.Nullable;
  * Contains metadata regarding how a user is known by a particular identity provider (IdP).
  * Instances of this class are immutable and thread safe.
  */
-public class ProviderUserInfo {
+class ProviderUserInfo implements UserInfo {
 
   private final String uid;
   private final String displayName;
@@ -39,51 +39,30 @@ public class ProviderUserInfo {
     this.providerId = response.getProviderId();
   }
 
-  /**
-   * Returns the user's unique ID assigned by the identity provider.
-   *
-   * @return a user ID string.
-   */
+  @Override
   public String getUid() {
     return uid;
   }
 
-  /**
-   * Returns the user's display name.
-   *
-   * @return a display name string or null.
-   */
   @Nullable
+  @Override
   public String getDisplayName() {
     return displayName;
   }
 
-  /**
-   * Returns the user's email address.
-   *
-   * @return an email address string or null.
-   */
   @Nullable
+  @Override
   public String getEmail() {
     return email;
   }
 
-  /**
-   * Returns the user's photo URL.
-   *
-   * @return a URL string or null.
-   */
   @Nullable
+  @Override
   public String getPhotoUrl() {
     return photoUrl;
   }
 
-  /**
-   * Returns the ID of the identity provider. This can be a short domain name (e.g. google.com) or
-   * the identifier of an OpenID identity provider.
-   *
-   * @return an ID string that uniquely identifies the identity provider.
-   */
+  @Override
   public String getProviderId() {
     return providerId;
   }
diff --git a/src/main/java/com/google/firebase/auth/UserInfo.java b/src/main/java/com/google/firebase/auth/UserInfo.java
new file mode 100644
index 0000000..e433400
--- /dev/null
+++ b/src/main/java/com/google/firebase/auth/UserInfo.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.firebase.auth;
+
+import com.google.firebase.internal.Nullable;
+
+/**
+ * A collection of standard profile information for a user. Used to expose profile information
+ * returned by an identity provider.
+ */
+public interface UserInfo {
+
+  /**
+   * Returns the user's unique ID assigned by the identity provider.
+   *
+   * @return a user ID string.
+   */
+  String getUid();
+
+  /**
+   * Returns the user's display name, if available.
+   *
+   * @return a display name string or null.
+   */
+  @Nullable
+  String getDisplayName();
+
+  /**
+   * Returns the user's email address, if available.
+   *
+   * @return an email address string or null.
+   */
+  @Nullable
+  String getEmail();
+
+  /**
+   * Returns the user's photo URL, if available.
+   *
+   * @return a URL string or null.
+   */
+  @Nullable
+  String getPhotoUrl();
+
+  /**
+   * Returns the ID of the identity provider. This can be a short domain name (e.g. google.com) or
+   * the identifier of an OpenID identity provider.
+   *
+   * @return an ID string that uniquely identifies the identity provider.
+   */
+  String getProviderId();
+
+}
diff --git a/src/main/java/com/google/firebase/auth/User.java b/src/main/java/com/google/firebase/auth/UserRecord.java
similarity index 77%
rename from src/main/java/com/google/firebase/auth/User.java
rename to src/main/java/com/google/firebase/auth/UserRecord.java
index 0fda9fc..e672054 100644
--- a/src/main/java/com/google/firebase/auth/User.java
+++ b/src/main/java/com/google/firebase/auth/UserRecord.java
@@ -36,8 +36,9 @@ import java.util.Map;
  * Contains metadata associated with a Firebase user account. Instances of this class are immutable
  * and thread safe.
  */
-public class User {
+public class UserRecord implements UserInfo {
 
+  private static final String PROVIDER_ID = "firebase";
   private static final Map<String, String> REMOVABLE_FIELDS = ImmutableMap.of(
       "displayName", "DISPLAY_NAME",
       "photoUrl", "PHOTO_URL");
@@ -51,7 +52,7 @@ public class User {
   private final ProviderUserInfo[] providers;
   private final UserMetadata userMetadata;
 
-  User(GetAccountInfoResponse.User response) {
+  UserRecord(GetAccountInfoResponse.User response) {
     checkNotNull(response, "Response must not be null");
     checkArgument(!Strings.isNullOrEmpty(response.getUid()), "uid must not be null or empty");
     this.uid = response.getUid();
@@ -76,16 +77,28 @@ public class User {
    *
    * @return a non-null, non-empty user ID string.
    */
+  @Override
   public String getUid() {
     return uid;
   }
 
+  /**
+   * Returns the provider ID of this user.
+   *
+   * @return a constant provider ID value.
+   */
+  @Override
+  public String getProviderId() {
+    return PROVIDER_ID;
+  }
+
   /**
    * Returns the email address associated with this user.
    *
    * @return an email address string or null.
    */
   @Nullable
+  @Override
   public String getEmail() {
     return email;
   }
@@ -105,6 +118,7 @@ public class User {
    * @return a display name string or null.
    */
   @Nullable
+  @Override
   public String getDisplayName() {
     return displayName;
   }
@@ -115,6 +129,7 @@ public class User {
    * @return a URL string or null.
    */
   @Nullable
+  @Override
   public String getPhotoUrl() {
     return photoUrl;
   }
@@ -129,11 +144,12 @@ public class User {
   }
 
   /**
-   * Returns the identity providers associated with this user.
+   * Returns an array of UserInfo objects that represents the identities from different identity
+   * providers that are linked to this user.
    *
-   * @return an array of {@link ProviderUserInfo} instances, which may be empty.
+   * @return an array of {@link UserInfo} instances, which may be empty.
    */
-  public ProviderUserInfo[] getProviderData() {
+  public UserInfo[] getProviderData() {
     return providers;
   }
 
@@ -147,37 +163,13 @@ public class User {
   }
 
   /**
-   * Returns a new {@link User.Updater} instance, which can be used to update the attributes
+   * Returns a new {@link UpdateRequest}, which can be used to update the attributes
    * of this user.
    *
-   * @return a non-null User.Updater instance.
-   */
-  public Updater updater() {
-    return new Updater(uid);
-  }
-
-  /**
-   * Returns a new {@link User.Updater} instance, which can be used to update the attributes
-   * of the user identified by the specified user ID. This method allows updating attributes of
-   * a user account, without first having to call {@link FirebaseAuth#getUser(String)}.
-   *
-   * @param uid a non-null, non-empty user ID string.
-   * @return a non-null User.Updater instance.
-   * @throws IllegalArgumentException If the user ID is null or empty.
+   * @return a non-null UserRecord.UpdateRequest instance.
    */
-  public static Updater updater(String uid) {
-    return new Updater(uid);
-  }
-
-  /**
-   * Returns a new {@link User.Builder} instance, which can be used to create a new user. The
-   * returned builder should be passed to {@link FirebaseAuth#createUser(Builder)} to register
-   * the user information persistently.
-   *
-   * @return a non-null User.Builder instance.
-   */
-  public static Builder builder() {
-    return new Builder();
+  public UpdateRequest updateRequest() {
+    return new UpdateRequest(uid);
   }
 
   private static void checkEmail(String email) {
@@ -191,15 +183,20 @@ public class User {
   }
 
   /**
-   * A builder class for creating new user accounts. Set the initial attributes of the new user
-   * account by calling various setter methods available in this class. None of the attributes
+   * A specification class for creating new user accounts. Set the initial attributes of the new
+   * user account by calling various setter methods available in this class. None of the attributes
    * are required.
    */
-  public static class Builder {
+  public static class CreateRequest {
 
     private final Map<String,Object> properties = new HashMap<>();
 
-    private Builder() {
+    /**
+     * Creates a new {@link CreateRequest}, which can be used to create a new user. The returned
+     * object should be passed to {@link FirebaseAuth#createUser(CreateRequest)} to register
+     * the user information persistently.
+     */
+    public CreateRequest() {
     }
 
     /**
@@ -208,7 +205,7 @@ public class User {
      * @param uid a non-null, non-empty user ID that uniquely identifies the new user. The user ID
      *     must not be longer than 128 characters.
      */
-    public Builder setUid(String uid) {
+    public CreateRequest setUid(String uid) {
       checkArgument(!Strings.isNullOrEmpty(uid), "uid cannot be null or empty");
       checkArgument(uid.length() <= 128, "UID cannot be longer than 128 characters");
       properties.put("localId", uid);
@@ -220,7 +217,7 @@ public class User {
      *
      * @param email a non-null, non-empty email address string.
      */
-    public Builder setEmail(String email) {
+    public CreateRequest setEmail(String email) {
       checkEmail(email);
       properties.put("email", email);
       return this;
@@ -231,7 +228,7 @@ public class User {
      *
      * @param emailVerified a boolean indicating the email verification status.
      */
-    public Builder setEmailVerified(boolean emailVerified) {
+    public CreateRequest setEmailVerified(boolean emailVerified) {
       properties.put("emailVerified", emailVerified);
       return this;
     }
@@ -241,7 +238,7 @@ public class User {
      *
      * @param displayName a non-null, non-empty display name string.
      */
-    public Builder setDisplayName(String displayName) {
+    public CreateRequest setDisplayName(String displayName) {
       checkNotNull(displayName, "displayName cannot be null or empty");
       properties.put("displayName", displayName);
       return this;
@@ -252,7 +249,7 @@ public class User {
      *
      * @param photoUrl a non-null, non-empty URL string.
      */
-    public Builder setPhotoUrl(String photoUrl) {
+    public CreateRequest setPhotoUrl(String photoUrl) {
       checkArgument(!Strings.isNullOrEmpty(photoUrl), "photoUrl cannot be null or empty");
       try {
         new URL(photoUrl);
@@ -268,7 +265,7 @@ public class User {
      *
      * @param disabled a boolean indicating whether the new account should be disabled.
      */
-    public Builder setDisabled(boolean disabled) {
+    public CreateRequest setDisabled(boolean disabled) {
       properties.put("disabled", disabled);
       return this;
     }
@@ -278,27 +275,35 @@ public class User {
      *
      * @param password a password string that is at least 6 characters long.
      */
-    public Builder setPassword(String password) {
+    public CreateRequest setPassword(String password) {
       checkPassword(password);
       properties.put("password", password);
       return this;
     }
 
-    Map<String, Object> build() {
+    Map<String, Object> getProperties() {
       return ImmutableMap.copyOf(properties);
     }
   }
 
   /**
    * A class for updating the attributes of an existing user. An instance of this class can be
-   * obtained via a {@link User} object, or from a user ID string. Specify the changes to be
+   * obtained via a {@link UserRecord} object, or from a user ID string. Specify the changes to be
    * made in the user account by calling the various setter methods available in this class.
    */
-  public static class Updater {
+  public static class UpdateRequest {
 
     private final Map<String,Object> properties = new HashMap<>();
 
-    private Updater(String uid) {
+    /**
+     * Creates a new {@link UpdateRequest}, which can be used to update the attributes
+     * of the user identified by the specified user ID. This method allows updating attributes of
+     * a user account, without first having to call {@link FirebaseAuth#getUser(String)}.
+     *
+     * @param uid a non-null, non-empty user ID string.
+     * @throws IllegalArgumentException If the user ID is null or empty.
+     */
+    public UpdateRequest(String uid) {
       checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty");
       properties.put("localId", uid);
     }
@@ -312,7 +317,7 @@ public class User {
      *
      * @param email a non-null, non-empty email address to be associated with the user.
      */
-    public Updater setEmail(String email) {
+    public UpdateRequest setEmail(String email) {
       checkEmail(email);
       properties.put("email", email);
       return this;
@@ -323,7 +328,7 @@ public class User {
      *
      * @param emailVerified a boolean indicating whether the email address has been verified.
      */
-    public Updater setEmailVerified(boolean emailVerified) {
+    public UpdateRequest setEmailVerified(boolean emailVerified) {
       properties.put("emailVerified", emailVerified);
       return this;
     }
@@ -334,7 +339,7 @@ public class User {
      *
      * @param displayName a display name string or null
      */
-    public Updater setDisplayName(String displayName) {
+    public UpdateRequest setDisplayName(String displayName) {
       properties.put("displayName", displayName);
       return this;
     }
@@ -345,7 +350,7 @@ public class User {
      *
      * @param photoUrl a valid URL string or null
      */
-    public Updater setPhotoUrl(String photoUrl) {
+    public UpdateRequest setPhotoUrl(String photoUrl) {
       if (photoUrl != null) {
         try {
           new URL(photoUrl);
@@ -362,7 +367,7 @@ public class User {
      *
      * @param disabled a boolean indicating whether this account should be disabled.
      */
-    public Updater setDisabled(boolean disabled) {
+    public UpdateRequest setDisabled(boolean disabled) {
       properties.put("disableUser", disabled);
       return this;
     }
@@ -372,13 +377,13 @@ public class User {
      *
      * @param password a new password string that is at least 6 characters long.
      */
-    public Updater setPassword(String password) {
+    public UpdateRequest setPassword(String password) {
       checkPassword(password);
       properties.put("password", password);
       return this;
     }
 
-    Map<String, Object> update() {
+    Map<String, Object> getProperties() {
       Map<String, Object> copy = new HashMap<>(properties);
       List<String> remove = new ArrayList<>();
       for (Map.Entry<String, String> entry : REMOVABLE_FIELDS.entrySet()) {


Test patch:
diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java
index 59f1051..ed15672 100644
--- a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java
+++ b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java
@@ -23,6 +23,8 @@ import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import com.google.firebase.FirebaseApp;
+import com.google.firebase.auth.UserRecord.CreateRequest;
+import com.google.firebase.auth.UserRecord.UpdateRequest;
 import com.google.firebase.tasks.Tasks;
 import com.google.firebase.testing.IntegrationTestUtils;
 import java.util.UUID;
@@ -67,7 +69,7 @@ public class FirebaseAuthIT {
   @Test
   public void testUpdateNonExistingUser() throws Exception {
     try {
-      Tasks.await(auth.updateUser(User.updater("non.existing")));
+      Tasks.await(auth.updateUser(new UpdateRequest("non.existing")));
       fail("No error thrown for non existing uid");
     } catch (ExecutionException e) {
       assertTrue(e.getCause() instanceof FirebaseAuthException);
@@ -93,7 +95,7 @@ public class FirebaseAuthIT {
     String randomId = UUID.randomUUID().toString().replaceAll("-", "");
     String userEmail = ("test" + randomId.substring(0, 12) + "@example." + randomId.substring(12)
         + ".com").toLowerCase();
-    User.Builder builder = User.builder()
+    CreateRequest user = new CreateRequest()
         .setUid(randomId)
         .setEmail(userEmail)
         .setDisplayName("Random User")
@@ -101,24 +103,24 @@ public class FirebaseAuthIT {
         .setEmailVerified(true)
         .setPassword("password");
 
-    User user = Tasks.await(auth.createUser(builder));
+    UserRecord userRecord = Tasks.await(auth.createUser(user));
     try {
-      assertEquals(randomId, user.getUid());
-      assertEquals("Random User", user.getDisplayName());
-      assertEquals(userEmail, user.getEmail());
-      assertEquals("https://example.com/photo.png", user.getPhotoUrl());
-      assertTrue(user.isEmailVerified());
-      assertFalse(user.isDisabled());
+      assertEquals(randomId, userRecord.getUid());
+      assertEquals("Random User", userRecord.getDisplayName());
+      assertEquals(userEmail, userRecord.getEmail());
+      assertEquals("https://example.com/photo.png", userRecord.getPhotoUrl());
+      assertTrue(userRecord.isEmailVerified());
+      assertFalse(userRecord.isDisabled());
 
       checkRecreate(randomId);
     } finally {
-      Tasks.await(auth.deleteUser(user.getUid()));
+      Tasks.await(auth.deleteUser(userRecord.getUid()));
     }
   }
 
   private void checkRecreate(String uid) throws Exception {
     try {
-      Tasks.await(auth.createUser(User.builder().setUid(uid)));
+      Tasks.await(auth.createUser(new CreateRequest().setUid(uid)));
       fail("No error thrown for creating user with existing ID");
     } catch (ExecutionException e) {
       assertTrue(e.getCause() instanceof FirebaseAuthException);
@@ -130,55 +132,59 @@ public class FirebaseAuthIT {
   @Test
   public void testUserLifecycle() throws Exception {
     // Create user
-    User user = Tasks.await(auth.createUser(User.builder()));
-    String uid = user.getUid();
+    UserRecord userRecord = Tasks.await(auth.createUser(new CreateRequest()));
+    String uid = userRecord.getUid();
 
     // Get user
-    user = Tasks.await(auth.getUser(user.getUid()));
-    assertEquals(uid, user.getUid());
-    assertNull(user.getDisplayName());
-    assertNull(user.getEmail());
-    assertNull(user.getPhotoUrl());
-    assertFalse(user.isEmailVerified());
-    assertFalse(user.isDisabled());
-    assertTrue(user.getUserMetadata().getCreationTimestamp() > 0);
-    assertEquals(0, user.getUserMetadata().getLastSignInTimestamp());
+    userRecord = Tasks.await(auth.getUser(userRecord.getUid()));
+    assertEquals(uid, userRecord.getUid());
+    assertNull(userRecord.getDisplayName());
+    assertNull(userRecord.getEmail());
+    assertNull(userRecord.getPhotoUrl());
+    assertFalse(userRecord.isEmailVerified());
+    assertFalse(userRecord.isDisabled());
+    assertTrue(userRecord.getUserMetadata().getCreationTimestamp() > 0);
+    assertEquals(0, userRecord.getUserMetadata().getLastSignInTimestamp());
 
     // Update user
     String randomId = UUID.randomUUID().toString().replaceAll("-", "");
     String userEmail = ("test" + randomId.substring(0, 12) + "@example." + randomId.substring(12)
         + ".com").toLowerCase();
-    User.Updater updater = user.updater()
+    UpdateRequest request = userRecord.updateRequest()
         .setDisplayName("Updated Name")
         .setEmail(userEmail)
         .setPhotoUrl("https://example.com/photo.png")
         .setEmailVerified(true)
         .setPassword("secret");
-    user = Tasks.await(auth.updateUser(updater));
-    assertEquals(uid, user.getUid());
-    assertEquals("Updated Name", user.getDisplayName());
-    assertEquals(userEmail, user.getEmail());
-    assertEquals("https://example.com/photo.png", user.getPhotoUrl());
-    assertTrue(user.isEmailVerified());
-    assertFalse(user.isDisabled());
+    userRecord = Tasks.await(auth.updateUser(request));
+    assertEquals(uid, userRecord.getUid());
+    assertEquals("Updated Name", userRecord.getDisplayName());
+    assertEquals(userEmail, userRecord.getEmail());
+    assertEquals("https://example.com/photo.png", userRecord.getPhotoUrl());
+    assertTrue(userRecord.isEmailVerified());
+    assertFalse(userRecord.isDisabled());
+
+    // Get user by email
+    userRecord = Tasks.await(auth.getUserByEmail(userRecord.getEmail()));
+    assertEquals(uid, userRecord.getUid());
 
     // Disable user and remove properties
-    updater = user.updater()
+    request = userRecord.updateRequest()
         .setPhotoUrl(null)
         .setDisplayName(null)
         .setDisabled(true);
-    user = Tasks.await(auth.updateUser(updater));
-    assertEquals(uid, user.getUid());
-    assertNull(user.getDisplayName());
-    assertEquals(userEmail, user.getEmail());
-    assertNull(user.getPhotoUrl());
-    assertTrue(user.isEmailVerified());
-    assertTrue(user.isDisabled());
+    userRecord = Tasks.await(auth.updateUser(request));
+    assertEquals(uid, userRecord.getUid());
+    assertNull(userRecord.getDisplayName());
+    assertEquals(userEmail, userRecord.getEmail());
+    assertNull(userRecord.getPhotoUrl());
+    assertTrue(userRecord.isEmailVerified());
+    assertTrue(userRecord.isDisabled());
 
     // Delete user
-    Tasks.await(auth.deleteUser(user.getUid()));
+    Tasks.await(auth.deleteUser(userRecord.getUid()));
     try {
-      Tasks.await(auth.getUser(user.getUid()));
+      Tasks.await(auth.getUser(userRecord.getUid()));
       fail("No error thrown for deleted user");
     } catch (ExecutionException e) {
       assertTrue(e.getCause() instanceof FirebaseAuthException);
diff --git a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java
index 8b0466a..8858f51 100644
--- a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java
+++ b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java
@@ -25,6 +25,8 @@ import com.google.api.client.json.gson.GsonFactory;
 import com.google.api.client.testing.http.MockHttpTransport;
 import com.google.api.client.testing.http.MockLowLevelHttpResponse;
 import com.google.common.collect.ImmutableList;
+import com.google.firebase.auth.UserRecord.CreateRequest;
+import com.google.firebase.auth.UserRecord.UpdateRequest;
 import com.google.firebase.testing.TestUtils;
 
 import java.io.IOException;
@@ -43,8 +45,8 @@ public class FirebaseUserManagerTest {
         .setLowLevelHttpResponse(response)
         .build();
     FirebaseUserManager userManager = new FirebaseUserManager(gson, transport);
-    User user = userManager.getUserById("testuser", "token");
-    checkUser(user);
+    UserRecord userRecord = userManager.getUserById("testuser", "token");
+    checkUserRecord(userRecord);
   }
 
   @Test
@@ -71,8 +73,8 @@ public class FirebaseUserManagerTest {
         .setLowLevelHttpResponse(response)
         .build();
     FirebaseUserManager userManager = new FirebaseUserManager(gson, transport);
-    User user = userManager.getUserByEmail("testuser@example.com", "token");
-    checkUser(user);
+    UserRecord userRecord = userManager.getUserByEmail("testuser@example.com", "token");
+    checkUserRecord(userRecord);
   }
 
   @Test
@@ -83,7 +85,7 @@ public class FirebaseUserManagerTest {
         .setLowLevelHttpResponse(response)
         .build();
     FirebaseUserManager userManager = new FirebaseUserManager(gson, transport);
-    String uid = userManager.createUser(User.builder(), "token");
+    String uid = userManager.createUser(new CreateRequest(), "token");
     assertEquals("testuser", uid);
   }
 
@@ -96,7 +98,7 @@ public class FirebaseUserManagerTest {
         .build();
     FirebaseUserManager userManager = new FirebaseUserManager(gson, transport);
     // should not throw
-    userManager.updateUser(User.updater("testuser"), "token");
+    userManager.updateUser(new UpdateRequest("testuser"), "token");
   }
 
   @Test
@@ -137,21 +139,20 @@ public class FirebaseUserManagerTest {
 
   @Test
   public void testUserBuilder() {
-    Map<String, Object> map = User.builder()
-        .build();
+    Map<String, Object> map = new CreateRequest().getProperties();
     assertTrue(map.isEmpty());
   }
 
   @Test
   public void testUserBuilderWithParams() {
-    Map<String, Object> map = User.builder()
+    Map<String, Object> map = new CreateRequest()
         .setUid("TestUid")
         .setDisplayName("Display Name")
         .setPhotoUrl("http://test.com/example.png")
         .setEmail("test@example.com")
         .setEmailVerified(true)
         .setPassword("secret")
-        .build();
+        .getProperties();
     assertEquals(6, map.size());
     assertEquals("TestUid", map.get("localId"));
     assertEquals("Display Name", map.get("displayName"));
@@ -163,23 +164,23 @@ public class FirebaseUserManagerTest {
 
   @Test
   public void testInvalidUid() {
-    User.Builder builder = User.builder();
+    CreateRequest user = new CreateRequest();
     try {
-      builder.setUid(null);
+      user.setUid(null);
       fail("No error thrown for null uid");
     } catch (Exception ignore) {
       // expected
     }
 
     try {
-      builder.setUid("");
+      user.setUid("");
       fail("No error thrown for empty uid");
     } catch (Exception ignore) {
       // expected
     }
 
     try {
-      builder.setUid(String.format("%0129d", 0));
+      user.setUid(String.format("%0129d", 0));
       fail("No error thrown for long uid");
     } catch (Exception ignore) {
       // expected
@@ -188,9 +189,9 @@ public class FirebaseUserManagerTest {
 
   @Test
   public void testInvalidDisplayName() {
-    User.Builder builder = User.builder();
+    CreateRequest user = new CreateRequest();
     try {
-      builder.setDisplayName(null);
+      user.setDisplayName(null);
       fail("No error thrown for null display name");
     } catch (Exception ignore) {
       // expected
@@ -199,23 +200,23 @@ public class FirebaseUserManagerTest {
 
   @Test
   public void testInvalidPhotoUrl() {
-    User.Builder builder = User.builder();
+    CreateRequest user = new CreateRequest();
     try {
-      builder.setPhotoUrl(null);
+      user.setPhotoUrl(null);
       fail("No error thrown for null photo url");
     } catch (Exception ignore) {
       // expected
     }
 
     try {
-      builder.setPhotoUrl("");
+      user.setPhotoUrl("");
       fail("No error thrown for invalid photo url");
     } catch (Exception ignore) {
       // expected
     }
 
     try {
-      builder.setPhotoUrl("not-a-url");
+      user.setPhotoUrl("not-a-url");
       fail("No error thrown for invalid photo url");
     } catch (Exception ignore) {
       // expected
@@ -224,23 +225,23 @@ public class FirebaseUserManagerTest {
 
   @Test
   public void testInvalidEmail() {
-    User.Builder builder = User.builder();
+    CreateRequest user = new CreateRequest();
     try {
-      builder.setEmail(null);
+      user.setEmail(null);
       fail("No error thrown for null email");
     } catch (Exception ignore) {
       // expected
     }
 
     try {
-      builder.setEmail("");
+      user.setEmail("");
       fail("No error thrown for invalid email");
     } catch (Exception ignore) {
       // expected
     }
 
     try {
-      builder.setEmail("not-an-email");
+      user.setEmail("not-an-email");
       fail("No error thrown for invalid email");
     } catch (Exception ignore) {
       // expected
@@ -249,16 +250,16 @@ public class FirebaseUserManagerTest {
 
   @Test
   public void testInvalidPassword() {
-    User.Builder builder = User.builder();
+    CreateRequest user = new CreateRequest();
     try {
-      builder.setPassword(null);
+      user.setPassword(null);
       fail("No error thrown for null password");
     } catch (Exception ignore) {
       // expected
     }
 
     try {
-      builder.setPassword("aaaaa");
+      user.setPassword("aaaaa");
       fail("No error thrown for short password");
     } catch (Exception ignore) {
       // expected
@@ -267,16 +268,16 @@ public class FirebaseUserManagerTest {
 
   @Test
   public void testUserUpdater() {
-    User.Updater updater = User.updater("test");
-    Map<String, Object> map = updater
+    UpdateRequest update = new UpdateRequest("test");
+    Map<String, Object> map = update
         .setDisplayName("Display Name")
         .setPhotoUrl("http://test.com/example.png")
         .setEmail("test@example.com")
         .setEmailVerified(true)
         .setPassword("secret")
-        .update();
+        .getProperties();
     assertEquals(6, map.size());
-    assertEquals(updater.getUid(), map.get("localId"));
+    assertEquals(update.getUid(), map.get("localId"));
     assertEquals("Display Name", map.get("displayName"));
     assertEquals("http://test.com/example.png", map.get("photoUrl"));
     assertEquals("test@example.com", map.get("email"));
@@ -286,32 +287,32 @@ public class FirebaseUserManagerTest {
 
   @Test
   public void testDeleteDisplayName() {
-    Map<String, Object> map = User.updater("test")
+    Map<String, Object> map = new UpdateRequest("test")
         .setDisplayName(null)
-        .update();
+        .getProperties();
     assertEquals(ImmutableList.of("DISPLAY_NAME"), map.get("deleteAttribute"));
   }
 
   @Test
   public void testDeletePhotoUrl() {
-    Map<String, Object> map = User.updater("test")
+    Map<String, Object> map = new UpdateRequest("test")
         .setPhotoUrl(null)
-        .update();
+        .getProperties();
     assertEquals(ImmutableList.of("PHOTO_URL"), map.get("deleteAttribute"));
   }
 
   @Test
   public void testInvalidUpdatePhotoUrl() {
-    User.Updater updater = User.updater("test");
+    UpdateRequest update = new UpdateRequest("test");
     try {
-      updater.setPhotoUrl("");
+      update.setPhotoUrl("");
       fail("No error thrown for invalid photo url");
     } catch (Exception ignore) {
       // expected
     }
 
     try {
-      updater.setPhotoUrl("not-a-url");
+      update.setPhotoUrl("not-a-url");
       fail("No error thrown for invalid photo url");
     } catch (Exception ignore) {
       // expected
@@ -320,23 +321,23 @@ public class FirebaseUserManagerTest {
 
   @Test
   public void testInvalidUpdateEmail() {
-    User.Updater updater = User.updater("test");
+    UpdateRequest update = new UpdateRequest("test");
     try {
-      updater.setEmail(null);
+      update.setEmail(null);
       fail("No error thrown for null email");
     } catch (Exception ignore) {
       // expected
     }
 
     try {
-      updater.setEmail("");
+      update.setEmail("");
       fail("No error thrown for invalid email");
     } catch (Exception ignore) {
       // expected
     }
 
     try {
-      updater.setEmail("not-an-email");
+      update.setEmail("not-an-email");
       fail("No error thrown for invalid email");
     } catch (Exception ignore) {
       // expected
@@ -345,34 +346,34 @@ public class FirebaseUserManagerTest {
 
   @Test
   public void testInvalidUpdatePassword() {
-    User.Updater updater = User.updater("test");
+    UpdateRequest update = new UpdateRequest("test");
     try {
-      updater.setPassword(null);
+      update.setPassword(null);
       fail("No error thrown for null password");
     } catch (Exception ignore) {
       // expected
     }
 
     try {
-      updater.setPassword("aaaaa");
+      update.setPassword("aaaaa");
       fail("No error thrown for short password");
     } catch (Exception ignore) {
       // expected
     }
   }
 
-  private void checkUser(User user) {
-    assertEquals("testuser", user.getUid());
-    assertEquals("testuser@example.com", user.getEmail());
-    assertEquals("Test User", user.getDisplayName());
-    assertEquals("http://www.example.com/testuser/photo.png", user.getPhotoUrl());
-    assertEquals(1234567890, user.getUserMetadata().getCreationTimestamp());
-    assertEquals(0, user.getUserMetadata().getLastSignInTimestamp());
-    assertEquals(1, user.getProviderData().length);
-    assertFalse(user.isDisabled());
-    assertTrue(user.isEmailVerified());
-
-    ProviderUserInfo provider = user.getProviderData()[0];
+  private void checkUserRecord(UserRecord userRecord) {
+    assertEquals("testuser", userRecord.getUid());
+    assertEquals("testuser@example.com", userRecord.getEmail());
+    assertEquals("Test User", userRecord.getDisplayName());
+    assertEquals("http://www.example.com/testuser/photo.png", userRecord.getPhotoUrl());
+    assertEquals(1234567890, userRecord.getUserMetadata().getCreationTimestamp());
+    assertEquals(0, userRecord.getUserMetadata().getLastSignInTimestamp());
+    assertEquals(1, userRecord.getProviderData().length);
+    assertFalse(userRecord.isDisabled());
+    assertTrue(userRecord.isEmailVerified());
+
+    UserInfo provider = userRecord.getProviderData()[0];
     assertEquals("testuser@example.com", provider.getUid());
     assertEquals("testuser@example.com", provider.getEmail());
     assertEquals("Test User", provider.getDisplayName());