Repository: firebase/firebase-admin-java
Pull request #119 (2018-01-02 17:27:54)

Auto init
Adding the FIREBASE_CONFIG default options behavior.

an app can now be initialized with FirebaseApp.initializeApp()
It will use the ApplicationDefaultCredential and the options values that are in the file named in FIREBASE_CONFIG (if that file exists.)


Patch:
diff --git a/pom.xml b/pom.xml
index 1238a75..5271313 100644
--- a/pom.xml
+++ b/pom.xml
@@ -340,7 +340,7 @@
                             <goal>verify</goal>
                         </goals>
                     </execution>                    
-                </executions>                
+                </executions>
             </plugin>
             <plugin>
                 <artifactId>maven-javadoc-plugin</artifactId>
diff --git a/src/main/java/com/google/firebase/FirebaseApp.java b/src/main/java/com/google/firebase/FirebaseApp.java
index 029b1ea..0386ab3 100644
--- a/src/main/java/com/google/firebase/FirebaseApp.java
+++ b/src/main/java/com/google/firebase/FirebaseApp.java
@@ -21,6 +21,9 @@ import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import com.google.api.client.googleapis.util.Utils;
+import com.google.api.client.json.JsonFactory;
+import com.google.api.client.json.JsonParser;
 import com.google.auth.oauth2.AccessToken;
 import com.google.auth.oauth2.GoogleCredentials;
 import com.google.auth.oauth2.OAuth2Credentials;
@@ -41,6 +44,9 @@ import com.google.firebase.internal.Nullable;
 import com.google.firebase.internal.RevivingScheduledExecutor;
 import com.google.firebase.tasks.Task;
 import com.google.firebase.tasks.Tasks;
+
+import java.io.FileNotFoundException;
+import java.io.FileReader;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -79,6 +85,7 @@ public class FirebaseApp {
   private static final Map<String, FirebaseApp> instances = new HashMap<>();
 
   public static final String DEFAULT_APP_NAME = "[DEFAULT]";
+  static final String FIREBASE_CONFIG_ENV_VAR = "FIREBASE_CONFIG";
 
   private static final TokenRefresher.Factory DEFAULT_TOKEN_REFRESHER_FACTORY =
       new TokenRefresher.Factory();
@@ -163,13 +170,35 @@ public class FirebaseApp {
     }
   }
 
+  /**
+   * Initializes the default {@link FirebaseApp} instance using default credentials and 
+   * default {@link FirebaseOptions} and the {@link #DEFAULT_APP_NAME}.
+   * Uses Application Default Credentials and also attempts to load {@link FirebaseOptions}
+   * from the environment. This is done by looking up the {@code FIREBASE_CONFIG} environment
+   * variable. If the value of the variable starts with <code>'{'</code>, it is parsed as a JSON
+   * object. Otherwise it is treated as a file name and the JSON content is read from the 
+   * corresponding file.
+   */
+  public static FirebaseApp initializeApp() {
+    return initializeApp(DEFAULT_APP_NAME);
+  }
+
+  /**
+   * Initializes a {@link FirebaseApp} instance using default credentials and default
+   * {@link FirebaseOptions} with a specified name.
+   * Uses Application Default Credentials and also attempts to load {@link FirebaseOptions}
+   * from the environment. This is done by looking up the {@code FIREBASE_CONFIG} environment
+   * variable. If the value of the variable starts with <code>'{'</code>, it is parsed as a JSON
+   * object. Otherwise it is treated as a file name and the JSON content is read from the
+   * corresponding file.
+   */
+  public static FirebaseApp initializeApp(String name) {
+    return initializeApp(getOptionsFromEnvironment(), name);
+  }
+
   /**
    * Initializes the default {@link FirebaseApp} instance. Same as {@link
-   * #initializeApp(FirebaseOptions, String)}, but it uses {@link #DEFAULT_APP_NAME} as name. *
-   *
-   * <p>The creation of the default instance is automatically triggered at app startup time, if
-   * Firebase configuration values are available from resources - populated from
-   * google-services.json.
+   * #initializeApp(FirebaseOptions, String)}, but uses {@link #DEFAULT_APP_NAME} as name.
    */
   public static FirebaseApp initializeApp(FirebaseOptions options) {
     return initializeApp(options, DEFAULT_APP_NAME);
@@ -542,4 +571,34 @@ public class FirebaseApp {
       STOPPED
     }
   }
+
+  private static FirebaseOptions getOptionsFromEnvironment() {
+    String defaultConfig = System.getenv(FIREBASE_CONFIG_ENV_VAR);
+    try { 
+      if (Strings.isNullOrEmpty(defaultConfig)) {
+        return new FirebaseOptions.Builder()
+          .setCredentials(GoogleCredentials.getApplicationDefault())
+          .build();
+      }
+      JsonFactory jsonFactory = Utils.getDefaultJsonFactory();
+      FirebaseOptions.Builder builder = new FirebaseOptions.Builder();
+      JsonParser parser;
+      if (defaultConfig.startsWith("{")) {
+        parser = jsonFactory.createJsonParser(defaultConfig);
+      } else {
+        FileReader reader;
+        reader = new FileReader(defaultConfig);
+        parser = jsonFactory.createJsonParser(reader);    
+      }
+      parser.parseAndClose(builder);
+      builder.setCredentials(GoogleCredentials.getApplicationDefault());
+      
+      return builder.build();
+
+    } catch (FileNotFoundException e) {
+      throw new IllegalStateException(e);
+    } catch (IOException e) {
+      throw new IllegalStateException(e);
+    }
+  }
 }
diff --git a/src/main/java/com/google/firebase/FirebaseOptions.java b/src/main/java/com/google/firebase/FirebaseOptions.java
index 30f05ca..edcd07f 100644
--- a/src/main/java/com/google/firebase/FirebaseOptions.java
+++ b/src/main/java/com/google/firebase/FirebaseOptions.java
@@ -22,6 +22,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
 import com.google.api.client.googleapis.util.Utils;
 import com.google.api.client.http.HttpTransport;
 import com.google.api.client.json.JsonFactory;
+import com.google.api.client.util.Key;
 import com.google.auth.oauth2.GoogleCredentials;
 import com.google.common.base.Strings;
 import com.google.firebase.auth.FirebaseCredential;
@@ -136,12 +137,19 @@ public final class FirebaseOptions {
    * Builder for constructing {@link FirebaseOptions}. 
    */
   public static final class Builder {
-
+    @Key("databaseAuthVariableOverride")
+    private Map<String, Object> databaseAuthVariableOverride = new HashMap<>();
+    
+    @Key("databaseUrl")
     private String databaseUrl;
+
+    @Key("projectId")
+    private String projectId;
+    
+    @Key("storageBucket")
     private String storageBucket;
+    
     private GoogleCredentials credentials;
-    private Map<String, Object> databaseAuthVariableOverride = new HashMap<>();
-    private String projectId;
     private HttpTransport httpTransport = Utils.getDefaultTransport();
     private JsonFactory jsonFactory = Utils.getDefaultJsonFactory();
     private ThreadManager threadManager = FirebaseThreadManagers.DEFAULT_THREAD_MANAGER;
@@ -150,7 +158,7 @@ public final class FirebaseOptions {
     public Builder() {}
 
     /**
-     * Initializes the builder's values from the options object. *
+     * Initializes the builder's values from the options object.
      *
      * <p>The new builder is not backed by this objects values, that is changes made to the new
      * builder don't change the values of the origin object.
diff --git a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
index 5ee2f9b..a1a78c7 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
@@ -23,6 +23,7 @@ import com.google.api.client.http.GenericUrl;
 import com.google.api.client.http.HttpRequest;
 import com.google.api.client.http.HttpRequestFactory;
 import com.google.api.client.http.HttpResponse;
+import com.google.api.client.http.HttpResponseException;
 import com.google.api.client.http.HttpResponseInterceptor;
 import com.google.api.client.http.HttpTransport;
 import com.google.api.client.http.json.JsonHttpContent;
@@ -40,6 +41,7 @@ import com.google.firebase.auth.UserRecord.UpdateRequest;
 import com.google.firebase.auth.internal.DownloadAccountResponse;
 import com.google.firebase.auth.internal.GetAccountInfoResponse;
 
+import com.google.firebase.auth.internal.HttpErrorResponse;
 import com.google.firebase.internal.SdkUtils;
 import java.io.IOException;
 import java.util.List;
@@ -54,12 +56,27 @@ import java.util.Map;
  */
 class FirebaseUserManager {
 
-  static final String USER_NOT_FOUND_ERROR = "USER_NOT_FOUND_ERROR";
-  static final String USER_CREATE_ERROR = "USER_CREATE_ERROR";
-  static final String USER_UPDATE_ERROR = "USER_UPDATE_ERROR";
-  static final String USER_DELETE_ERROR = "USER_DELETE_ERROR";
-  static final String LIST_USERS_ERROR = "LIST_USERS_ERROR";
-  static final String INTERNAL_ERROR = "INTERNAL_ERROR";
+  static final String USER_NOT_FOUND_ERROR = "user-not-found";
+  static final String INTERNAL_ERROR = "internal-error";
+
+  // Map of server-side error codes to SDK error codes.
+  // SDK error codes defined at: https://firebase.google.com/docs/auth/admin/errors
+  private static final Map<String, String> ERROR_CODES = ImmutableMap.<String, String>builder()
+      .put("CLAIMS_TOO_LARGE", "claims-too-large")
+      .put("CONFIGURATION_NOT_FOUND", "project-not-found")
+      .put("INSUFFICIENT_PERMISSION", "insufficient-permission")
+      .put("DUPLICATE_EMAIL", "email-already-exists")
+      .put("DUPLICATE_LOCAL_ID", "uid-already-exists")
+      .put("EMAIL_EXISTS", "email-already-exists")
+      .put("INVALID_CLAIMS", "invalid-claims")
+      .put("INVALID_EMAIL", "invalid-email")
+      .put("INVALID_PAGE_SELECTION", "invalid-page-token")
+      .put("INVALID_PHONE_NUMBER", "invalid-phone-number")
+      .put("PHONE_NUMBER_EXISTS", "phone-number-already-exists")
+      .put("PROJECT_NOT_FOUND", "project-not-found")
+      .put("USER_NOT_FOUND", USER_NOT_FOUND_ERROR)
+      .put("WEAK_PASSWORD", "invalid-password")
+      .build();
 
   static final int MAX_LIST_USERS_RESULTS = 1000;
 
@@ -97,14 +114,8 @@ class FirebaseUserManager {
   UserRecord getUserById(String uid) throws FirebaseAuthException {
     final Map<String, Object> payload = ImmutableMap.<String, Object>of(
         "localId", ImmutableList.of(uid));
-    GetAccountInfoResponse response;
-    try {
-      response = post("getAccountInfo", payload, GetAccountInfoResponse.class);
-    } catch (IOException e) {
-      throw new FirebaseAuthException(INTERNAL_ERROR,
-          "IO error while retrieving user with ID: " + uid, e);
-    }
-
+    GetAccountInfoResponse response = post(
+        "getAccountInfo", payload, GetAccountInfoResponse.class);
     if (response == null || response.getUsers() == null || response.getUsers().isEmpty()) {
       throw new FirebaseAuthException(USER_NOT_FOUND_ERROR,
           "No user record found for the provided user ID: " + uid);
@@ -115,14 +126,8 @@ class FirebaseUserManager {
   UserRecord getUserByEmail(String email) throws FirebaseAuthException {
     final Map<String, Object> payload = ImmutableMap.<String, Object>of(
         "email", ImmutableList.of(email));
-    GetAccountInfoResponse response;
-    try {
-      response = post("getAccountInfo", payload, GetAccountInfoResponse.class);
-    } catch (IOException e) {
-      throw new FirebaseAuthException(INTERNAL_ERROR,
-          "IO error while retrieving user with email: " + email, e);
-    }
-
+    GetAccountInfoResponse response = post(
+        "getAccountInfo", payload, GetAccountInfoResponse.class);
     if (response == null || response.getUsers() == null || response.getUsers().isEmpty()) {
       throw new FirebaseAuthException(USER_NOT_FOUND_ERROR,
           "No user record found for the provided email: " + email);
@@ -133,14 +138,8 @@ class FirebaseUserManager {
   UserRecord getUserByPhoneNumber(String phoneNumber) throws FirebaseAuthException {
     final Map<String, Object> payload = ImmutableMap.<String, Object>of(
         "phoneNumber", ImmutableList.of(phoneNumber));
-    GetAccountInfoResponse response;
-    try {
-      response = post("getAccountInfo", payload, GetAccountInfoResponse.class);
-    } catch (IOException e) {
-      throw new FirebaseAuthException(INTERNAL_ERROR,
-          "IO error while retrieving user with phone number: " + phoneNumber, e);
-    }
-
+    GetAccountInfoResponse response = post(
+        "getAccountInfo", payload, GetAccountInfoResponse.class);
     if (response == null || response.getUsers() == null || response.getUsers().isEmpty()) {
       throw new FirebaseAuthException(USER_NOT_FOUND_ERROR,
           "No user record found for the provided phone number: " + phoneNumber);
@@ -149,50 +148,31 @@ class FirebaseUserManager {
   }
 
   String createUser(CreateRequest request) throws FirebaseAuthException {
-    GenericJson response;
-    try {
-      response = post("signupNewUser", request.getProperties(), GenericJson.class);
-    } catch (IOException e) {
-      throw new FirebaseAuthException(USER_CREATE_ERROR,
-          "IO error while creating user account", e);
-    }
-
+    GenericJson response = post(
+        "signupNewUser", request.getProperties(), GenericJson.class);
     if (response != null) {
       String uid = (String) response.get("localId");
       if (!Strings.isNullOrEmpty(uid)) {
         return uid;
       }
     }
-    throw new FirebaseAuthException(USER_CREATE_ERROR, "Failed to create new user");
+    throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to create new user");
   }
 
   void updateUser(UpdateRequest request, JsonFactory jsonFactory) throws FirebaseAuthException {
-    GenericJson response;
-    try {
-      response = post("setAccountInfo", request.getProperties(jsonFactory), GenericJson.class);
-    } catch (IOException e) {
-      throw new FirebaseAuthException(USER_UPDATE_ERROR,
-          "IO error while updating user: " + request.getUid(), e);
-    }
-
+    GenericJson response = post(
+        "setAccountInfo", request.getProperties(jsonFactory), GenericJson.class);
     if (response == null || !request.getUid().equals(response.get("localId"))) {
-      throw new FirebaseAuthException(USER_UPDATE_ERROR,
-          "Failed to update user: " + request.getUid());
+      throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to update user: " + request.getUid());
     }
   }
 
   void deleteUser(String uid) throws FirebaseAuthException {
     final Map<String, Object> payload = ImmutableMap.<String, Object>of("localId", uid);
-    GenericJson response;
-    try {
-      response = post("deleteAccount", payload, GenericJson.class);
-    } catch (IOException e) {
-      throw new FirebaseAuthException(USER_DELETE_ERROR,
-          "IO error while deleting user: " + uid, e);
-    }
+    GenericJson response = post(
+        "deleteAccount", payload, GenericJson.class);
     if (response == null || !response.containsKey("kind")) {
-      throw new FirebaseAuthException(USER_DELETE_ERROR,
-          "Failed to delete user: " + uid);
+      throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to delete user: " + uid);
     }
   }
 
@@ -204,36 +184,60 @@ class FirebaseUserManager {
       builder.put("nextPageToken", pageToken);
     }
 
-    DownloadAccountResponse response;
-    try {
-      response = post("downloadAccount", builder.build(), DownloadAccountResponse.class);
-      if (response == null) {
-        throw new FirebaseAuthException(LIST_USERS_ERROR,
-            "Unexpected response from download user account API.");
-      }
-      return response;
-    } catch (IOException e) {
-      throw new FirebaseAuthException(LIST_USERS_ERROR,
-          "IO error while downloading user accounts.", e);
+    DownloadAccountResponse response = post(
+        "downloadAccount", builder.build(), DownloadAccountResponse.class);
+    if (response == null) {
+      throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to retrieve users.");
     }
+    return response;
   }
 
-  private <T> T post(String path, Object content, Class<T> clazz) throws IOException {
+  private <T> T post(String path, Object content, Class<T> clazz) throws FirebaseAuthException {
     checkArgument(!Strings.isNullOrEmpty(path), "path must not be null or empty");
     checkNotNull(content, "content must not be null");
     checkNotNull(clazz, "response class must not be null");
 
     GenericUrl url = new GenericUrl(ID_TOOLKIT_URL + path);
-    HttpRequest request = requestFactory.buildPostRequest(url,
-        new JsonHttpContent(jsonFactory, content));
-    request.setParser(new JsonObjectParser(jsonFactory));
-    request.getHeaders().set(CLIENT_VERSION_HEADER, clientVersion);
-    request.setResponseInterceptor(interceptor);
-    HttpResponse response = request.execute();
+    HttpResponse response = null;
     try {
+      HttpRequest request = requestFactory.buildPostRequest(url,
+          new JsonHttpContent(jsonFactory, content));
+      request.setParser(new JsonObjectParser(jsonFactory));
+      request.getHeaders().set(CLIENT_VERSION_HEADER, clientVersion);
+      request.setResponseInterceptor(interceptor);
+      response = request.execute();
       return response.parseAs(clazz);
+    } catch (HttpResponseException e) {
+      // Server responded with an HTTP error
+      handleHttpError(e);
+      return null;
+    } catch (IOException e) {
+      // All other IO errors (Connection refused, reset, parse error etc.)
+      throw new FirebaseAuthException(
+          INTERNAL_ERROR, "Error while calling user management backend service", e);
     } finally {
-      response.disconnect();
+      if (response != null) {
+        try {
+          response.disconnect();
+        } catch (IOException ignored) {
+          // Ignored
+        }
+      }
+    }
+  }
+
+  private void handleHttpError(HttpResponseException e) throws FirebaseAuthException {
+    try {
+      HttpErrorResponse response = jsonFactory.fromString(e.getContent(), HttpErrorResponse.class);
+      String code = ERROR_CODES.get(response.getErrorCode());
+      if (code != null) {
+        throw new FirebaseAuthException(code, "User management service responded with an error", e);
+      }
+    } catch (IOException ignored) {
+      // Ignored
     }
+    String msg = String.format(
+        "Unexpected HTTP response with status: %d; body: %s", e.getStatusCode(), e.getContent());
+    throw new FirebaseAuthException(INTERNAL_ERROR, msg, e);
   }
 }
diff --git a/src/main/java/com/google/firebase/auth/internal/HttpErrorResponse.java b/src/main/java/com/google/firebase/auth/internal/HttpErrorResponse.java
new file mode 100644
index 0000000..d4be4b4
--- /dev/null
+++ b/src/main/java/com/google/firebase/auth/internal/HttpErrorResponse.java
@@ -0,0 +1,49 @@
+/*
+ * 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.internal;
+
+import com.google.api.client.util.Key;
+import com.google.common.base.Strings;
+
+/**
+ * JSON data binding for JSON error messages sent by Google identity toolkit service.
+ */
+public class HttpErrorResponse {
+
+  @Key("error")
+  private Error error;
+
+  public String getErrorCode() {
+    if (error != null) {
+      if (!Strings.isNullOrEmpty(error.getCode())) {
+        return error.getCode();
+      }
+    }
+    return "unknown";
+  }
+
+  public static class Error {
+
+    @Key("message")
+    private String code;
+
+    public String getCode() {
+      return code;
+    }
+  }
+
+}


Test patch:
diff --git a/src/test/java/com/google/firebase/FirebaseAppTest.java b/src/test/java/com/google/firebase/FirebaseAppTest.java
index 732db65..eb7a1a0 100644
--- a/src/test/java/com/google/firebase/FirebaseAppTest.java
+++ b/src/test/java/com/google/firebase/FirebaseAppTest.java
@@ -34,6 +34,7 @@ import com.google.auth.oauth2.GoogleCredentials;
 import com.google.auth.oauth2.OAuth2Credentials;
 import com.google.auth.oauth2.OAuth2Credentials.CredentialsChangedListener;
 import com.google.common.base.Defaults;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.io.BaseEncoding;
 import com.google.firebase.FirebaseApp.TokenRefresher;
 import com.google.firebase.FirebaseOptions.Builder;
@@ -41,6 +42,7 @@ import com.google.firebase.database.FirebaseDatabase;
 import com.google.firebase.testing.FirebaseAppRule;
 import com.google.firebase.testing.ServiceAccount;
 import com.google.firebase.testing.TestUtils;
+import java.io.File;
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -50,6 +52,7 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
@@ -419,6 +422,114 @@ public class FirebaseAppTest {
     assertEquals(0, refresher.cancelCalls);
   }
 
+  @Test(expected = IllegalArgumentException.class)
+  public void testEmptyFirebaseConfigFile() {
+    setFirebaseConfigEnvironmentVariable("firebase_config_empty.json");
+    FirebaseApp.initializeApp();
+  }  
+  
+  @Test
+  public void testEmptyFirebaseConfigString() {
+    setFirebaseConfigEnvironmentVariable("");
+    FirebaseApp firebaseApp = FirebaseApp.initializeApp();
+    assertEquals(null, firebaseApp.getOptions().getProjectId());
+    assertEquals(null, firebaseApp.getOptions().getStorageBucket());
+    assertEquals(null, firebaseApp.getOptions().getDatabaseUrl());
+    assertTrue(firebaseApp.getOptions().getDatabaseAuthVariableOverride().isEmpty()); 
+  }
+
+  @Test
+  public void testEmptyFirebaseConfigJSONObject() {
+    setFirebaseConfigEnvironmentVariable("{}");
+    FirebaseApp firebaseApp = FirebaseApp.initializeApp();
+    assertEquals(null, firebaseApp.getOptions().getProjectId());
+    assertEquals(null, firebaseApp.getOptions().getStorageBucket());
+    assertEquals(null, firebaseApp.getOptions().getDatabaseUrl());
+    assertTrue(firebaseApp.getOptions().getDatabaseAuthVariableOverride().isEmpty());
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void testInvalidFirebaseConfigFile() {
+    setFirebaseConfigEnvironmentVariable("firebase_config_invalid.json");
+    FirebaseApp.initializeApp();
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void testInvalidFirebaseConfigString() {
+    setFirebaseConfigEnvironmentVariable("{,,");
+    FirebaseApp.initializeApp();
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void testFirebaseConfigMissingFile() {
+    setFirebaseConfigEnvironmentVariable("no_such.json");
+    FirebaseApp.initializeApp();
+  }
+
+  @Test
+  public void testFirebaseConfigFileWithSomeKeysMissing() {
+    setFirebaseConfigEnvironmentVariable("firebase_config_partial.json");
+    FirebaseApp firebaseApp = FirebaseApp.initializeApp();
+    assertEquals("hipster-chat-mock", firebaseApp.getOptions().getProjectId());
+    assertEquals("https://hipster-chat.firebaseio.mock", firebaseApp.getOptions().getDatabaseUrl());
+  }
+
+  @Test
+  public void testValidFirebaseConfigFile() {
+    setFirebaseConfigEnvironmentVariable("firebase_config.json");
+    FirebaseApp firebaseApp = FirebaseApp.initializeApp();
+    assertEquals("hipster-chat-mock", firebaseApp.getOptions().getProjectId());
+    assertEquals("hipster-chat.appspot.mock", firebaseApp.getOptions().getStorageBucket());
+    assertEquals("https://hipster-chat.firebaseio.mock", firebaseApp.getOptions().getDatabaseUrl());
+    assertEquals("testuser", firebaseApp.getOptions().getDatabaseAuthVariableOverride().get("uid"));
+  }
+
+  @Test
+  public void testEnvironmentVariableIgnored() {
+    setFirebaseConfigEnvironmentVariable("firebase_config.json");
+    FirebaseApp firebaseApp = FirebaseApp.initializeApp(OPTIONS);
+    assertEquals(null, firebaseApp.getOptions().getProjectId());
+    assertEquals(null, firebaseApp.getOptions().getStorageBucket());
+    assertEquals(null, firebaseApp.getOptions().getDatabaseUrl());
+    assertTrue(firebaseApp.getOptions().getDatabaseAuthVariableOverride().isEmpty());
+  }
+
+  @Test
+  public void testValidFirebaseConfigString() {
+    setFirebaseConfigEnvironmentVariable("{" 
+        + "\"databaseAuthVariableOverride\": {" 
+        +   "\"uid\":" 
+        +   "\"testuser\"" 
+        + "}," 
+        + "\"databaseUrl\": \"https://hipster-chat.firebaseio.mock\"," 
+        + "\"projectId\": \"hipster-chat-mock\"," 
+        + "\"storageBucket\": \"hipster-chat.appspot.mock\"" 
+        + "}");
+    FirebaseApp firebaseApp = FirebaseApp.initializeApp();
+    assertEquals("hipster-chat-mock", firebaseApp.getOptions().getProjectId());
+    assertEquals("hipster-chat.appspot.mock", firebaseApp.getOptions().getStorageBucket());
+    assertEquals("https://hipster-chat.firebaseio.mock", firebaseApp.getOptions().getDatabaseUrl());
+    assertEquals("testuser", 
+        firebaseApp.getOptions().getDatabaseAuthVariableOverride().get("uid"));
+  }
+
+  @Test
+  public void testFirebaseConfigFileIgnoresInvalidKey() {
+    setFirebaseConfigEnvironmentVariable("firebase_config_invalid_key.json");
+    FirebaseApp firebaseApp = FirebaseApp.initializeApp();
+    assertEquals("hipster-chat-mock", firebaseApp.getOptions().getProjectId());
+  }
+
+  @Test
+  public void testFirebaseConfigStringIgnoresInvalidKey() {
+    setFirebaseConfigEnvironmentVariable("{"
+        + "\"databaseUareL\": \"https://hipster-chat.firebaseio.mock\","
+        + "\"projectId\": \"hipster-chat-mock\""
+        + "}");
+    FirebaseApp firebaseApp = FirebaseApp.initializeApp();
+    assertEquals("hipster-chat-mock", firebaseApp.getOptions().getProjectId());
+  }
+
   @Test(expected = IllegalArgumentException.class)
   public void testFirebaseExceptionNullDetail() {
     new FirebaseException(null);
@@ -428,6 +539,20 @@ public class FirebaseAppTest {
   public void testFirebaseExceptionEmptyDetail() {
     new FirebaseException("");
   }
+  
+
+  private void setFirebaseConfigEnvironmentVariable(String configJSON) {
+    String configValue = "";
+    if (configJSON.isEmpty() || configJSON.startsWith("{")) {
+      configValue = configJSON;
+    } else {
+      configValue  = new File("src/test/resources/" + configJSON)
+                   .getAbsolutePath();
+    }
+    Map<String, String> environmentVariables =
+        ImmutableMap.of(FirebaseApp.FIREBASE_CONFIG_ENV_VAR , configValue);
+    TestUtils.setEnvironmentVariables(environmentVariables);
+  }
 
   private static class MockTokenRefresher extends TokenRefresher {
 
diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java
index 9833ffc..ea5e8b7 100644
--- a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java
+++ b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java
@@ -100,7 +100,7 @@ public class FirebaseAuthIT {
       fail("No error thrown for non existing uid");
     } catch (ExecutionException e) {
       assertTrue(e.getCause() instanceof FirebaseAuthException);
-      assertEquals(FirebaseUserManager.USER_UPDATE_ERROR,
+      assertEquals(FirebaseUserManager.USER_NOT_FOUND_ERROR,
           ((FirebaseAuthException) e.getCause()).getErrorCode());
     }
   }
@@ -112,7 +112,7 @@ public class FirebaseAuthIT {
       fail("No error thrown for non existing uid");
     } catch (ExecutionException e) {
       assertTrue(e.getCause() instanceof FirebaseAuthException);
-      assertEquals(FirebaseUserManager.USER_DELETE_ERROR,
+      assertEquals(FirebaseUserManager.USER_NOT_FOUND_ERROR,
           ((FirebaseAuthException) e.getCause()).getErrorCode());
     }
   }
@@ -392,8 +392,7 @@ public class FirebaseAuthIT {
       fail("No error thrown for creating user with existing ID");
     } catch (ExecutionException e) {
       assertTrue(e.getCause() instanceof FirebaseAuthException);
-      assertEquals(FirebaseUserManager.USER_CREATE_ERROR,
-          ((FirebaseAuthException) e.getCause()).getErrorCode());
+      assertEquals("uid-already-exists", ((FirebaseAuthException) e.getCause()).getErrorCode());
     }
   }
 
diff --git a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java
index f84072f..85a8d7f 100644
--- a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java
+++ b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java
@@ -24,6 +24,7 @@ import static org.junit.Assert.fail;
 
 import com.google.api.client.googleapis.util.Utils;
 import com.google.api.client.http.HttpHeaders;
+import com.google.api.client.http.HttpResponseException;
 import com.google.api.client.json.GenericJson;
 import com.google.api.client.json.JsonFactory;
 import com.google.api.client.json.gson.GsonFactory;
@@ -43,6 +44,7 @@ import com.google.firebase.testing.TestUtils;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.math.BigDecimal;
+import java.util.List;
 import java.util.Map;
 
 import org.junit.Test;
@@ -294,66 +296,92 @@ public class FirebaseUserManagerTest {
 
   @Test
   public void testGetUserHttpError() throws Exception {
-    Map<UserManagerOp, String> operations = ImmutableMap.<UserManagerOp, String>builder()
-        .put(new UserManagerOp() {
+    List<UserManagerOp> operations = ImmutableList.<UserManagerOp>builder()
+        .add(new UserManagerOp() {
           @Override
           public void call(FirebaseUserManager userManager) throws Exception {
             userManager.getUserById("testuser");
           }
-        }, FirebaseUserManager.INTERNAL_ERROR)
-        .put(new UserManagerOp() {
+        })
+        .add(new UserManagerOp() {
           @Override
           public void call(FirebaseUserManager userManager) throws Exception {
             userManager.getUserByEmail("testuser@example.com");
           }
-        }, FirebaseUserManager.INTERNAL_ERROR)
-        .put(new UserManagerOp() {
+        })
+        .add(new UserManagerOp() {
           @Override
           public void call(FirebaseUserManager userManager) throws Exception {
             userManager.getUserByPhoneNumber("+1234567890");
           }
-        }, FirebaseUserManager.INTERNAL_ERROR)
-        .put(new UserManagerOp() {
+        })
+        .add(new UserManagerOp() {
           @Override
           public void call(FirebaseUserManager userManager) throws Exception {
             userManager.createUser(new CreateRequest());
           }
-        }, FirebaseUserManager.USER_CREATE_ERROR)
-        .put(new UserManagerOp() {
+        })
+        .add(new UserManagerOp() {
           @Override
           public void call(FirebaseUserManager userManager) throws Exception {
             userManager.updateUser(new UpdateRequest("test"), Utils.getDefaultJsonFactory());
           }
-        }, FirebaseUserManager.USER_UPDATE_ERROR)
-        .put(new UserManagerOp() {
+        })
+        .add(new UserManagerOp() {
           @Override
           public void call(FirebaseUserManager userManager) throws Exception {
             userManager.deleteUser("testuser");
           }
-        }, FirebaseUserManager.USER_DELETE_ERROR)
+        })
+        .add(new UserManagerOp() {
+          @Override
+          public void call(FirebaseUserManager userManager) throws Exception {
+            userManager.listUsers(1000, null);
+          }
+        })
+        .build();
+
+    MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
+    MockHttpTransport transport = new MockHttpTransport.Builder()
+        .setLowLevelHttpResponse(response)
         .build();
+    FirebaseUserManager userManager = new FirebaseUserManager(gson, transport, credentials);
+
+    // Test for common HTTP error codes
     for (int code : ImmutableList.of(302, 400, 401, 404, 500)) {
-      MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
-      response.setContent("{}");
-      response.setStatusCode(code);
-      MockHttpTransport transport = new MockHttpTransport.Builder()
-          .setLowLevelHttpResponse(response)
-          .build();
-      FirebaseUserManager userManager = new FirebaseUserManager(gson, transport, credentials);
-      for (Map.Entry<UserManagerOp, String> entry : operations.entrySet()) {
+      for (UserManagerOp operation : operations) {
+        // Need to reset these every iteration
+        response.setContent("{}");
+        response.setStatusCode(code);
         try {
-          entry.getKey().call(userManager);
+          operation.call(userManager);
           fail("No error thrown for HTTP error");
         }  catch (FirebaseAuthException e) {
-          assertTrue(e.getCause() instanceof IOException);
-          assertEquals(entry.getValue(), e.getErrorCode());
+          String msg = String.format("Unexpected HTTP response with status: %d; body: {}", code);
+          assertEquals(msg, e.getMessage());
+          assertTrue(e.getCause() instanceof HttpResponseException);
+          assertEquals(FirebaseUserManager.INTERNAL_ERROR, e.getErrorCode());
         }
       }
     }
+
+    // Test error payload parsing
+    for (UserManagerOp operation : operations) {
+      response.setContent("{\"error\": {\"message\": \"USER_NOT_FOUND\"}}");
+      response.setStatusCode(500);
+      try {
+        operation.call(userManager);
+        fail("No error thrown for HTTP error");
+      }  catch (FirebaseAuthException e) {
+        assertEquals("User management service responded with an error", e.getMessage());
+        assertTrue(e.getCause() instanceof HttpResponseException);
+        assertEquals(FirebaseUserManager.USER_NOT_FOUND_ERROR, e.getErrorCode());
+      }
+    }
   }
 
   @Test
-  public void testGetUserJsonError() throws Exception {
+  public void testGetUserMalformedJsonError() throws Exception {
     MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
     response.setContent("{\"not\" json}");
     MockHttpTransport transport = new MockHttpTransport.Builder()
@@ -365,6 +393,27 @@ public class FirebaseUserManagerTest {
       fail("No error thrown for JSON error");
     }  catch (FirebaseAuthException e) {
       assertTrue(e.getCause() instanceof IOException);
+      assertEquals(FirebaseUserManager.INTERNAL_ERROR, e.getErrorCode());
+    }
+  }
+
+  @Test
+  public void testGetUserUnexpectedHttpError() throws Exception {
+    MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
+    response.setContent("{\"not\" json}");
+    response.setStatusCode(500);
+    MockHttpTransport transport = new MockHttpTransport.Builder()
+        .setLowLevelHttpResponse(response)
+        .build();
+    FirebaseUserManager userManager = new FirebaseUserManager(gson, transport, credentials);
+    try {
+      userManager.getUserById("testuser");
+      fail("No error thrown for JSON error");
+    }  catch (FirebaseAuthException e) {
+      assertTrue(e.getCause() instanceof HttpResponseException);
+      assertEquals("Unexpected HTTP response with status: 500; body: {\"not\" json}",
+          e.getMessage());
+      assertEquals(FirebaseUserManager.INTERNAL_ERROR, e.getErrorCode());
     }
   }
 
diff --git a/src/test/resources/firebase_config.json b/src/test/resources/firebase_config.json
new file mode 100644
index 0000000..53a48ba
--- /dev/null
+++ b/src/test/resources/firebase_config.json
@@ -0,0 +1,6 @@
+{
+  "databaseAuthVariableOverride": {"uid": "testuser"},
+  "databaseUrl": "https://hipster-chat.firebaseio.mock",
+  "projectId": "hipster-chat-mock",
+  "storageBucket": "hipster-chat.appspot.mock"
+}
diff --git a/src/test/resources/firebase_config_empty.json b/src/test/resources/firebase_config_empty.json
new file mode 100644
index 0000000..e69de29
diff --git a/src/test/resources/firebase_config_invalid.json b/src/test/resources/firebase_config_invalid.json
new file mode 100644
index 0000000..74c098f
--- /dev/null
+++ b/src/test/resources/firebase_config_invalid.json
@@ -0,0 +1,1 @@
+baaaaad
diff --git a/src/test/resources/firebase_config_invalid_key.json b/src/test/resources/firebase_config_invalid_key.json
new file mode 100644
index 0000000..90cde6f
--- /dev/null
+++ b/src/test/resources/firebase_config_invalid_key.json
@@ -0,0 +1,4 @@
+{
+  "databaseUareL": "https://hipster-chat.firebaseio.mock",
+  "projectId": "hipster-chat-mock"
+}
diff --git a/src/test/resources/firebase_config_partial.json b/src/test/resources/firebase_config_partial.json
new file mode 100644
index 0000000..2762904
--- /dev/null
+++ b/src/test/resources/firebase_config_partial.json
@@ -0,0 +1,4 @@
+{
+  "databaseUrl": "https://hipster-chat.firebaseio.mock",
+  "projectId": "hipster-chat-mock"
+}