sortedNameList = new ArrayList<>(allAppNames);
- Collections.sort(sortedNameList);
- return sortedNameList;
+
+ Collections.sort(allAppNames);
+ return ImmutableList.copyOf(allAppNames);
}
/** Normalizes the app name. */
@@ -359,11 +343,6 @@ public void delete() {
synchronized (appsLock) {
instances.remove(name);
}
-
- FirebaseAppStore appStore = FirebaseAppStore.getInstance();
- if (appStore != null) {
- appStore.removeApp(name);
- }
}
private void checkNotDeleted() {
@@ -582,18 +561,17 @@ enum State {
private static FirebaseOptions getOptionsFromEnvironment() throws IOException {
String defaultConfig = System.getenv(FIREBASE_CONFIG_ENV_VAR);
if (Strings.isNullOrEmpty(defaultConfig)) {
- return new FirebaseOptions.Builder()
+ return FirebaseOptions.builder()
.setCredentials(APPLICATION_DEFAULT_CREDENTIALS)
.build();
}
JsonFactory jsonFactory = Utils.getDefaultJsonFactory();
- FirebaseOptions.Builder builder = new FirebaseOptions.Builder();
+ FirebaseOptions.Builder builder = FirebaseOptions.builder();
JsonParser parser;
if (defaultConfig.startsWith("{")) {
parser = jsonFactory.createJsonParser(defaultConfig);
} else {
- FileReader reader;
- reader = new FileReader(defaultConfig);
+ FileReader reader = new FileReader(defaultConfig);
parser = jsonFactory.createJsonParser(reader);
}
parser.parseAndClose(builder);
diff --git a/src/main/java/com/google/firebase/FirebaseAppLifecycleListener.java b/src/main/java/com/google/firebase/FirebaseAppLifecycleListener.java
index 60118c249..6493edb60 100644
--- a/src/main/java/com/google/firebase/FirebaseAppLifecycleListener.java
+++ b/src/main/java/com/google/firebase/FirebaseAppLifecycleListener.java
@@ -19,7 +19,7 @@
/**
* A listener which gets notified when {@link com.google.firebase.FirebaseApp} gets deleted.
*/
-// TODO: consider making it public in a future release.
+@Deprecated
interface FirebaseAppLifecycleListener {
/**
diff --git a/src/main/java/com/google/firebase/FirebaseException.java b/src/main/java/com/google/firebase/FirebaseException.java
index f78b3fb98..a5bb80424 100644
--- a/src/main/java/com/google/firebase/FirebaseException.java
+++ b/src/main/java/com/google/firebase/FirebaseException.java
@@ -17,24 +17,55 @@
package com.google.firebase;
import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Strings;
import com.google.firebase.internal.NonNull;
+import com.google.firebase.internal.Nullable;
-/** Base class for all Firebase exceptions. */
+/**
+ * Base class for all Firebase exceptions.
+ */
public class FirebaseException extends Exception {
- // TODO(b/27677218): Exceptions should have non-empty messages.
- @Deprecated
- protected FirebaseException() {}
+ private final ErrorCode errorCode;
+ private final IncomingHttpResponse httpResponse;
+
+ public FirebaseException(
+ @NonNull ErrorCode errorCode,
+ @NonNull String message,
+ @Nullable Throwable cause,
+ @Nullable IncomingHttpResponse httpResponse) {
+ super(message, cause);
+ checkArgument(!Strings.isNullOrEmpty(message), "Message must not be null or empty");
+ this.errorCode = checkNotNull(errorCode, "ErrorCode must not be null");
+ this.httpResponse = httpResponse;
+ }
+
+ public FirebaseException(
+ @NonNull ErrorCode errorCode,
+ @NonNull String message,
+ @Nullable Throwable cause) {
+ this(errorCode, message, cause, null);
+ }
- public FirebaseException(@NonNull String detailMessage) {
- super(detailMessage);
- checkArgument(!Strings.isNullOrEmpty(detailMessage), "Detail message must not be empty");
+ /**
+ * Returns the platform-wide error code associated with this exception.
+ *
+ * @return A Firebase error code.
+ */
+ public final ErrorCode getErrorCode() {
+ return errorCode;
}
- public FirebaseException(@NonNull String detailMessage, Throwable cause) {
- super(detailMessage, cause);
- checkArgument(!Strings.isNullOrEmpty(detailMessage), "Detail message must not be empty");
+ /**
+ * Returns the HTTP response that resulted in this exception. If the exception was not caused by
+ * an HTTP error response, returns null.
+ *
+ * @return An HTTP response or null.
+ */
+ @Nullable
+ public final IncomingHttpResponse getHttpResponse() {
+ return httpResponse;
}
}
diff --git a/src/main/java/com/google/firebase/FirebaseOptions.java b/src/main/java/com/google/firebase/FirebaseOptions.java
index f0561d5e5..6ee074d6f 100644
--- a/src/main/java/com/google/firebase/FirebaseOptions.java
+++ b/src/main/java/com/google/firebase/FirebaseOptions.java
@@ -223,6 +223,16 @@ public static Builder builder() {
return new Builder();
}
+ /**
+ * Creates a new {@code Builder} from the options object.
+ *
+ * The new builder is not backed by this object's values; that is, changes made to the new
+ * builder don't change the values of the origin object.
+ */
+ public Builder toBuilder() {
+ return new Builder(this);
+ }
+
/**
* Builder for constructing {@link FirebaseOptions}.
*/
@@ -249,7 +259,12 @@ public static final class Builder {
private int connectTimeout;
private int readTimeout;
- /** Constructs an empty builder. */
+ /**
+ * Constructs an empty builder.
+ *
+ * @deprecated Use {@link FirebaseOptions#builder()} instead.
+ */
+ @Deprecated
public Builder() {}
/**
@@ -257,7 +272,10 @@ public Builder() {}
*
*
The new builder is not backed by this object's values, that is changes made to the new
* builder don't change the values of the origin object.
+ *
+ * @deprecated Use {@link FirebaseOptions#toBuilder()} instead.
*/
+ @Deprecated
public Builder(FirebaseOptions options) {
databaseUrl = options.databaseUrl;
storageBucket = options.storageBucket;
diff --git a/src/main/java/com/google/firebase/IncomingHttpResponse.java b/src/main/java/com/google/firebase/IncomingHttpResponse.java
new file mode 100644
index 000000000..cfeac5e70
--- /dev/null
+++ b/src/main/java/com/google/firebase/IncomingHttpResponse.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2020 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;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.api.client.http.HttpRequest;
+import com.google.api.client.http.HttpResponse;
+import com.google.api.client.http.HttpResponseException;
+import com.google.common.collect.ImmutableMap;
+import com.google.firebase.database.annotations.Nullable;
+import java.util.Map;
+
+/**
+ * Contains information that describes an HTTP response received by the SDK.
+ */
+public final class IncomingHttpResponse {
+
+ private final int statusCode;
+ private final String content;
+ private final Map headers;
+ private final OutgoingHttpRequest request;
+
+ /**
+ * Creates an {@code IncomingHttpResponse} from a successful response and the content read
+ * from it. The caller is expected to read the content from the response, and handle any errors
+ * that may occur while reading.
+ *
+ * @param response A successful response.
+ * @param content Content read from the response.
+ */
+ public IncomingHttpResponse(HttpResponse response, @Nullable String content) {
+ checkNotNull(response, "response must not be null");
+ this.statusCode = response.getStatusCode();
+ this.content = content;
+ this.headers = ImmutableMap.copyOf(response.getHeaders());
+ this.request = new OutgoingHttpRequest(response.getRequest());
+ }
+
+ /**
+ * Creates an {@code IncomingHttpResponse} from an HTTP error response.
+ *
+ * @param e The exception representing the HTTP error response.
+ * @param request The request that resulted in the error.
+ */
+ public IncomingHttpResponse(HttpResponseException e, HttpRequest request) {
+ this(e, new OutgoingHttpRequest(request));
+ }
+
+ /**
+ * Creates an {@code IncomingHttpResponse} from an HTTP error response.
+ *
+ * @param e The exception representing the HTTP error response.
+ * @param request The request that resulted in the error.
+ */
+ public IncomingHttpResponse(HttpResponseException e, OutgoingHttpRequest request) {
+ checkNotNull(e, "exception must not be null");
+ this.statusCode = e.getStatusCode();
+ this.content = e.getContent();
+ this.headers = ImmutableMap.copyOf(e.getHeaders());
+ this.request = checkNotNull(request, "request must not be null");
+ }
+
+ /**
+ * Returns the status code of the response.
+ *
+ * @return An HTTP status code (e.g. 500).
+ */
+ public int getStatusCode() {
+ return this.statusCode;
+ }
+
+ /**
+ * Returns the content of the response as a string.
+ *
+ * @return HTTP content or null.
+ */
+ @Nullable
+ public String getContent() {
+ return this.content;
+ }
+
+ /**
+ * Returns the headers set on the response.
+ *
+ * @return An immutable map of headers (possibly empty).
+ */
+ public Map getHeaders() {
+ return this.headers;
+ }
+
+ /**
+ * Returns the request that resulted in this response.
+ *
+ * @return An HTTP request.
+ */
+ public OutgoingHttpRequest getRequest() {
+ return request;
+ }
+}
diff --git a/src/main/java/com/google/firebase/OutgoingHttpRequest.java b/src/main/java/com/google/firebase/OutgoingHttpRequest.java
new file mode 100644
index 000000000..44af4bff0
--- /dev/null
+++ b/src/main/java/com/google/firebase/OutgoingHttpRequest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2020 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;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.api.client.http.HttpContent;
+import com.google.api.client.http.HttpRequest;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import com.google.firebase.internal.Nullable;
+import java.util.Map;
+
+/**
+ * Contains the information that describe an HTTP request made by the SDK.
+ */
+public final class OutgoingHttpRequest {
+
+ private final String method;
+ private final String url;
+ private final HttpContent content;
+ private final Map headers;
+
+ /**
+ * Creates an {@code OutgoingHttpRequest} from the HTTP method and URL.
+ *
+ * @param method HTTP method name.
+ * @param url Target HTTP URL of the request.
+ */
+ public OutgoingHttpRequest(String method, String url) {
+ checkArgument(!Strings.isNullOrEmpty(method), "method must not be null or empty");
+ checkArgument(!Strings.isNullOrEmpty(url), "url must not be empty");
+ this.method = method;
+ this.url = url;
+ this.content = null;
+ this.headers = ImmutableMap.of();
+ }
+
+ OutgoingHttpRequest(HttpRequest request) {
+ checkNotNull(request, "request must not be null");
+ this.method = request.getRequestMethod();
+ this.url = request.getUrl().toString();
+ this.content = request.getContent();
+ this.headers = ImmutableMap.copyOf(request.getHeaders());
+ }
+
+ /**
+ * Returns the HTTP method of the request.
+ *
+ * @return An HTTP method string (e.g. GET).
+ */
+ public String getMethod() {
+ return method;
+ }
+
+ /**
+ * Returns the URL of the request.
+ *
+ * @return An absolute HTTP URL.
+ */
+ public String getUrl() {
+ return url;
+ }
+
+ /**
+ * Returns any content that was sent with the request.
+ *
+ * @return HTTP content or null.
+ */
+ @Nullable
+ public HttpContent getContent() {
+ return content;
+ }
+
+ /**
+ * Returns the headers set on the request.
+ *
+ * @return An immutable map of headers (possibly empty).
+ */
+ public Map getHeaders() {
+ return headers;
+ }
+}
diff --git a/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java b/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java
index 7061d2b44..8004548a5 100644
--- a/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java
+++ b/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java
@@ -37,7 +37,6 @@
import com.google.firebase.internal.CallableOperation;
import com.google.firebase.internal.NonNull;
import com.google.firebase.internal.Nullable;
-import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
@@ -50,8 +49,6 @@
*/
public abstract class AbstractFirebaseAuth {
- private static final String ERROR_CUSTOM_TOKEN = "ERROR_CUSTOM_TOKEN";
-
private final Object lock = new Object();
private final AtomicBoolean destroyed = new AtomicBoolean(false);
@@ -173,12 +170,7 @@ private CallableOperation createCustomTokenOp(
return new CallableOperation() {
@Override
public String execute() throws FirebaseAuthException {
- try {
- return tokenFactory.createSignedCustomAuthTokenForUser(uid, developerClaims);
- } catch (IOException e) {
- throw new FirebaseAuthException(
- ERROR_CUSTOM_TOKEN, "Failed to generate a custom token", e);
- }
+ return tokenFactory.createSignedCustomAuthTokenForUser(uid, developerClaims);
}
};
}
@@ -902,7 +894,7 @@ protected UserImportResult execute() throws FirebaseAuthException {
* not guaranteed to correspond to the nth entry in the input parameters list.
*
* A maximum of 100 identifiers may be specified. If more than 100 identifiers are
- * supplied, this method throws an {@link IllegalArgumentException}.
+ * supplied, this method throws an {@code IllegalArgumentException}.
*
* @param identifiers The identifiers used to indicate which user records should be returned. Must
* have 100 or fewer entries.
@@ -924,7 +916,7 @@ public GetUsersResult getUsers(@NonNull Collection identifiers)
* not guaranteed to correspond to the nth entry in the input parameters list.
*
* A maximum of 100 identifiers may be specified. If more than 100 identifiers are
- * supplied, this method throws an {@link IllegalArgumentException}.
+ * supplied, this method throws an {@code IllegalArgumentException}.
*
* @param identifiers The identifiers used to indicate which user records should be returned.
* Must have 100 or fewer entries.
@@ -978,7 +970,7 @@ private boolean isUserFound(UserIdentifier id, Collection userRecord
* DeleteUsersResult.getSuccessCount() value.
*
* A maximum of 1000 identifiers may be supplied. If more than 1000 identifiers are
- * supplied, this method throws an {@link IllegalArgumentException}.
+ * supplied, this method throws an {@code IllegalArgumentException}.
*
*
This API has a rate limit of 1 QPS. Exceeding the limit may result in a quota exceeded
* error. If you want to delete more than 1000 users, we suggest adding a delay to ensure you
@@ -987,7 +979,7 @@ private boolean isUserFound(UserIdentifier id, Collection userRecord
* @param uids The uids of the users to be deleted. Must have <= 1000 entries.
* @return The total number of successful/failed deletions, as well as the array of errors that
* correspond to the failed deletions.
- * @throw IllegalArgumentException If any of the identifiers are invalid or if more than 1000
+ * @throws IllegalArgumentException If any of the identifiers are invalid or if more than 1000
* identifiers are specified.
* @throws FirebaseAuthException If an error occurs while deleting users.
*/
@@ -1003,7 +995,7 @@ public DeleteUsersResult deleteUsers(List uids) throws FirebaseAuthExcep
* deletions, as well as the array of errors that correspond to the failed deletions. If an
* error occurs while deleting the user account, the future throws a
* {@link FirebaseAuthException}.
- * @throw IllegalArgumentException If any of the identifiers are invalid or if more than 1000
+ * @throws IllegalArgumentException If any of the identifiers are invalid or if more than 1000
* identifiers are specified.
*/
public ApiFuture deleteUsersAsync(List uids) {
@@ -1831,11 +1823,7 @@ public FirebaseTokenVerifier get() {
new Supplier() {
@Override
public FirebaseUserManager get() {
- return FirebaseUserManager
- .builder()
- .setFirebaseApp(app)
- .setTenantId(tenantId)
- .build();
+ return FirebaseUserManager.createUserManager(app, tenantId);
}
});
}
diff --git a/src/main/java/com/google/firebase/auth/AuthErrorCode.java b/src/main/java/com/google/firebase/auth/AuthErrorCode.java
new file mode 100644
index 000000000..bea067eee
--- /dev/null
+++ b/src/main/java/com/google/firebase/auth/AuthErrorCode.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2020 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;
+
+/**
+ * Error codes that can be raised by the Firebase Auth APIs.
+ */
+public enum AuthErrorCode {
+
+ /**
+ * Failed to retrieve public key certificates required to verify JWTs.
+ */
+ CERTIFICATE_FETCH_FAILED,
+
+ /**
+ * No IdP configuration found for the given identifier.
+ */
+ CONFIGURATION_NOT_FOUND,
+
+ /**
+ * A user already exists with the provided email.
+ */
+ EMAIL_ALREADY_EXISTS,
+
+ /**
+ * The specified ID token is expired.
+ */
+ EXPIRED_ID_TOKEN,
+
+ /**
+ * The specified session cookie is expired.
+ */
+ EXPIRED_SESSION_COOKIE,
+
+ /**
+ * The provided dynamic link domain is not configured or authorized for the current project.
+ */
+ INVALID_DYNAMIC_LINK_DOMAIN,
+
+ /**
+ * The specified ID token is invalid.
+ */
+ INVALID_ID_TOKEN,
+
+ /**
+ * The specified session cookie is invalid.
+ */
+ INVALID_SESSION_COOKIE,
+
+ /**
+ * A user already exists with the provided phone number.
+ */
+ PHONE_NUMBER_ALREADY_EXISTS,
+
+ /**
+ * The specified ID token has been revoked.
+ */
+ REVOKED_ID_TOKEN,
+
+ /**
+ * The specified session cookie has been revoked.
+ */
+ REVOKED_SESSION_COOKIE,
+
+ /**
+ * Tenant ID in the JWT does not match.
+ */
+ TENANT_ID_MISMATCH,
+
+ /**
+ * No tenant found for the given identifier.
+ */
+ TENANT_NOT_FOUND,
+
+ /**
+ * A user already exists with the provided UID.
+ */
+ UID_ALREADY_EXISTS,
+
+ /**
+ * The domain of the continue URL is not whitelisted. Whitelist the domain in the Firebase
+ * console.
+ */
+ UNAUTHORIZED_CONTINUE_URL,
+
+ /**
+ * No user record found for the given identifier.
+ */
+ USER_NOT_FOUND,
+}
diff --git a/src/main/java/com/google/firebase/auth/FirebaseAuthException.java b/src/main/java/com/google/firebase/auth/FirebaseAuthException.java
index 2314a69d2..53c980668 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseAuthException.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseAuthException.java
@@ -16,17 +16,11 @@
package com.google.firebase.auth;
-// TODO: Move it out from firebase-common. Temporary host it their for
-// database's integration.http://b/27624510.
-
-// TODO: Decide if changing this not enforcing an error code. Need to align
-// with the decision in http://b/27677218. Also, need to turn this into abstract later.
-
-import static com.google.common.base.Preconditions.checkArgument;
-
-import com.google.common.base.Strings;
+import com.google.firebase.ErrorCode;
import com.google.firebase.FirebaseException;
+import com.google.firebase.IncomingHttpResponse;
import com.google.firebase.internal.NonNull;
+import com.google.firebase.internal.Nullable;
/**
* Generic exception related to Firebase Authentication. Check the error code and message for more
@@ -34,22 +28,24 @@
*/
public class FirebaseAuthException extends FirebaseException {
- private final String errorCode;
+ private final AuthErrorCode errorCode;
- public FirebaseAuthException(@NonNull String errorCode, @NonNull String detailMessage) {
- this(errorCode, detailMessage, null);
+ public FirebaseAuthException(
+ @NonNull ErrorCode errorCode,
+ @NonNull String message,
+ Throwable cause,
+ IncomingHttpResponse response,
+ AuthErrorCode authErrorCode) {
+ super(errorCode, message, cause, response);
+ this.errorCode = authErrorCode;
}
- public FirebaseAuthException(@NonNull String errorCode, @NonNull String detailMessage,
- Throwable throwable) {
- super(detailMessage, throwable);
- checkArgument(!Strings.isNullOrEmpty(errorCode));
- this.errorCode = errorCode;
+ public FirebaseAuthException(FirebaseException base) {
+ this(base.getErrorCode(), base.getMessage(), base.getCause(), base.getHttpResponse(), null);
}
- /** Returns an error code that may provide more information about the error. */
- @NonNull
- public String getErrorCode() {
+ @Nullable
+ public AuthErrorCode getAuthErrorCode() {
return errorCode;
}
}
diff --git a/src/main/java/com/google/firebase/auth/FirebaseTokenUtils.java b/src/main/java/com/google/firebase/auth/FirebaseTokenUtils.java
index 7c8f9f0a5..873dbe7ac 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseTokenUtils.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseTokenUtils.java
@@ -94,6 +94,8 @@ static FirebaseTokenVerifierImpl createIdTokenVerifier(
.setJsonFactory(app.getOptions().getJsonFactory())
.setPublicKeysManager(publicKeysManager)
.setIdTokenVerifier(idTokenVerifier)
+ .setInvalidTokenErrorCode(AuthErrorCode.INVALID_ID_TOKEN)
+ .setExpiredTokenErrorCode(AuthErrorCode.EXPIRED_ID_TOKEN)
.setTenantId(tenantId)
.build();
}
@@ -115,6 +117,8 @@ static FirebaseTokenVerifierImpl createSessionCookieVerifier(
.setShortName("session cookie")
.setMethod("verifySessionCookie()")
.setDocUrl("https://firebase.google.com/docs/auth/admin/manage-cookies")
+ .setInvalidTokenErrorCode(AuthErrorCode.INVALID_SESSION_COOKIE)
+ .setExpiredTokenErrorCode(AuthErrorCode.EXPIRED_SESSION_COOKIE)
.setJsonFactory(app.getOptions().getJsonFactory())
.setPublicKeysManager(publicKeysManager)
.setIdTokenVerifier(idTokenVerifier)
diff --git a/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java b/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java
index e1a5a9a19..273ec1532 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java
@@ -28,11 +28,13 @@
import com.google.api.client.util.ArrayMap;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
+import com.google.firebase.ErrorCode;
import com.google.firebase.internal.Nullable;
import java.io.IOException;
import java.math.BigDecimal;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
+import java.util.List;
/**
* The default implementation of the {@link FirebaseTokenVerifier} interface. Uses the Google API
@@ -44,9 +46,6 @@ final class FirebaseTokenVerifierImpl implements FirebaseTokenVerifier {
private static final String RS256 = "RS256";
private static final String FIREBASE_AUDIENCE =
"https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit";
- private static final String ERROR_INVALID_CREDENTIAL = "ERROR_INVALID_CREDENTIAL";
- private static final String ERROR_RUNTIME_EXCEPTION = "ERROR_RUNTIME_EXCEPTION";
- static final String TENANT_ID_MISMATCH_ERROR = "tenant-id-mismatch";
private final JsonFactory jsonFactory;
private final GooglePublicKeysManager publicKeysManager;
@@ -55,6 +54,8 @@ final class FirebaseTokenVerifierImpl implements FirebaseTokenVerifier {
private final String shortName;
private final String articledShortName;
private final String docUrl;
+ private final AuthErrorCode invalidTokenErrorCode;
+ private final AuthErrorCode expiredTokenErrorCode;
private final String tenantId;
private FirebaseTokenVerifierImpl(Builder builder) {
@@ -68,6 +69,8 @@ private FirebaseTokenVerifierImpl(Builder builder) {
this.shortName = builder.shortName;
this.articledShortName = prefixWithIndefiniteArticle(this.shortName);
this.docUrl = builder.docUrl;
+ this.invalidTokenErrorCode = checkNotNull(builder.invalidTokenErrorCode);
+ this.expiredTokenErrorCode = checkNotNull(builder.expiredTokenErrorCode);
this.tenantId = Strings.nullToEmpty(builder.tenantId);
}
@@ -143,38 +146,28 @@ private IdToken parse(String token) throws FirebaseAuthException {
shortName,
docUrl,
articledShortName);
- throw new FirebaseAuthException(ERROR_INVALID_CREDENTIAL, detailedError, e);
- }
- }
-
- private void checkContents(final IdToken token) throws FirebaseAuthException {
- String errorMessage = getErrorIfContentInvalid(token);
- if (errorMessage != null) {
- String detailedError = String.format("%s %s", errorMessage, getVerifyTokenMessage());
- throw new FirebaseAuthException(ERROR_INVALID_CREDENTIAL, detailedError);
+ throw newException(detailedError, invalidTokenErrorCode, e);
}
}
private void checkSignature(IdToken token) throws FirebaseAuthException {
- try {
- if (!isSignatureValid(token)) {
- throw new FirebaseAuthException(ERROR_INVALID_CREDENTIAL,
- String.format(
- "Failed to verify the signature of Firebase %s. %s",
- shortName,
- getVerifyTokenMessage()));
- }
- } catch (GeneralSecurityException | IOException e) {
- throw new FirebaseAuthException(
- ERROR_RUNTIME_EXCEPTION, "Error while verifying signature.", e);
+ if (!isSignatureValid(token)) {
+ String message = String.format(
+ "Failed to verify the signature of Firebase %s. %s",
+ shortName,
+ getVerifyTokenMessage());
+ throw newException(message, invalidTokenErrorCode);
}
}
- private String getErrorIfContentInvalid(final IdToken idToken) {
+ private void checkContents(final IdToken idToken) throws FirebaseAuthException {
final Header header = idToken.getHeader();
final Payload payload = idToken.getPayload();
+ final long currentTimeMillis = idTokenVerifier.getClock().currentTimeMillis();
String errorMessage = null;
+ AuthErrorCode errorCode = invalidTokenErrorCode;
+
if (header.getKeyId() == null) {
errorMessage = getErrorForTokenWithoutKid(header, payload);
} else if (!RS256.equals(header.getAlgorithm())) {
@@ -209,14 +202,35 @@ private String getErrorIfContentInvalid(final IdToken idToken) {
errorMessage = String.format(
"Firebase %s has \"sub\" (subject) claim longer than 128 characters.",
shortName);
- } else if (!verifyTimestamps(idToken)) {
+ } else if (!idToken.verifyExpirationTime(
+ currentTimeMillis, idTokenVerifier.getAcceptableTimeSkewSeconds())) {
errorMessage = String.format(
- "Firebase %s has expired or is not yet valid. Get a fresh %s and try again.",
+ "Firebase %s has expired. Get a fresh %s and try again.",
shortName,
shortName);
+ // Also set the expired error code.
+ errorCode = expiredTokenErrorCode;
+ } else if (!idToken.verifyIssuedAtTime(
+ currentTimeMillis, idTokenVerifier.getAcceptableTimeSkewSeconds())) {
+ errorMessage = String.format(
+ "Firebase %s is not yet valid.",
+ shortName);
+ }
+
+ if (errorMessage != null) {
+ String detailedError = String.format("%s %s", errorMessage, getVerifyTokenMessage());
+ throw newException(detailedError, errorCode);
}
+ }
- return errorMessage;
+ private FirebaseAuthException newException(String message, AuthErrorCode errorCode) {
+ return newException(message, errorCode, null);
+ }
+
+ private FirebaseAuthException newException(
+ String message, AuthErrorCode errorCode, Throwable cause) {
+ return new FirebaseAuthException(
+ ErrorCode.INVALID_ARGUMENT, message, cause, null, errorCode);
}
private String getVerifyTokenMessage() {
@@ -230,15 +244,44 @@ private String getVerifyTokenMessage() {
* Verifies the cryptographic signature on the FirebaseToken. Can block on a web request to fetch
* the keys if they have expired.
*/
- private boolean isSignatureValid(IdToken token) throws GeneralSecurityException, IOException {
- for (PublicKey key : publicKeysManager.getPublicKeys()) {
- if (token.verifySignature(key)) {
+ private boolean isSignatureValid(IdToken token) throws FirebaseAuthException {
+ for (PublicKey key : fetchPublicKeys()) {
+ if (isSignatureValid(token, key)) {
return true;
}
}
+
return false;
}
+ private boolean isSignatureValid(IdToken token, PublicKey key) throws FirebaseAuthException {
+ try {
+ return token.verifySignature(key);
+ } catch (GeneralSecurityException e) {
+ // This doesn't happen under usual circumstances. Seems to only happen if the crypto
+ // setup of the runtime is incorrect in some way.
+ throw new FirebaseAuthException(
+ ErrorCode.UNKNOWN,
+ String.format("Unexpected error while verifying %s: %s", shortName, e.getMessage()),
+ e,
+ null,
+ invalidTokenErrorCode);
+ }
+ }
+
+ private List fetchPublicKeys() throws FirebaseAuthException {
+ try {
+ return publicKeysManager.getPublicKeys();
+ } catch (GeneralSecurityException | IOException e) {
+ throw new FirebaseAuthException(
+ ErrorCode.UNKNOWN,
+ "Error while fetching public key certificates: " + e.getMessage(),
+ e,
+ null,
+ AuthErrorCode.CERTIFICATE_FETCH_FAILED);
+ }
+ }
+
private String getErrorForTokenWithoutKid(IdToken.Header header, IdToken.Payload payload) {
if (isCustomToken(payload)) {
return String.format("%s expects %s, but was given a custom token.",
@@ -261,11 +304,6 @@ private String getProjectIdMatchMessage() {
shortName);
}
- private boolean verifyTimestamps(IdToken token) {
- long currentTimeMillis = idTokenVerifier.getClock().currentTimeMillis();
- return token.verifyTime(currentTimeMillis, idTokenVerifier.getAcceptableTimeSkewSeconds());
- }
-
private boolean isCustomToken(IdToken.Payload payload) {
return FIREBASE_AUDIENCE.equals(payload.getAudience());
}
@@ -287,12 +325,11 @@ private boolean containsLegacyUidField(IdToken.Payload payload) {
private void checkTenantId(final FirebaseToken firebaseToken) throws FirebaseAuthException {
String tokenTenantId = Strings.nullToEmpty(firebaseToken.getTenantId());
if (!this.tenantId.equals(tokenTenantId)) {
- throw new FirebaseAuthException(
- TENANT_ID_MISMATCH_ERROR,
- String.format(
- "The tenant ID ('%s') of the token did not match the expected value ('%s')",
- tokenTenantId,
- tenantId));
+ String message = String.format(
+ "The tenant ID ('%s') of the token did not match the expected value ('%s')",
+ tokenTenantId,
+ tenantId);
+ throw newException(message, AuthErrorCode.TENANT_ID_MISMATCH);
}
}
@@ -308,6 +345,8 @@ static final class Builder {
private String shortName;
private IdTokenVerifier idTokenVerifier;
private String docUrl;
+ private AuthErrorCode invalidTokenErrorCode;
+ private AuthErrorCode expiredTokenErrorCode;
private String tenantId;
private Builder() { }
@@ -342,6 +381,16 @@ Builder setDocUrl(String docUrl) {
return this;
}
+ Builder setInvalidTokenErrorCode(AuthErrorCode invalidTokenErrorCode) {
+ this.invalidTokenErrorCode = invalidTokenErrorCode;
+ return this;
+ }
+
+ Builder setExpiredTokenErrorCode(AuthErrorCode expiredTokenErrorCode) {
+ this.expiredTokenErrorCode = expiredTokenErrorCode;
+ return this;
+ }
+
Builder setTenantId(@Nullable String tenantId) {
this.tenantId = tenantId;
return this;
diff --git a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
index b73882277..554d0179a 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
@@ -19,7 +19,6 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
-import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponseInterceptor;
import com.google.api.client.json.GenericJson;
@@ -30,8 +29,10 @@
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.firebase.ErrorCode;
import com.google.firebase.FirebaseApp;
import com.google.firebase.ImplFirebaseTrampolines;
+import com.google.firebase.IncomingHttpResponse;
import com.google.firebase.auth.internal.AuthHttpClient;
import com.google.firebase.auth.internal.BatchDeleteResponse;
import com.google.firebase.auth.internal.DownloadAccountResponse;
@@ -41,9 +42,9 @@
import com.google.firebase.auth.internal.ListSamlProviderConfigsResponse;
import com.google.firebase.auth.internal.UploadAccountResponse;
import com.google.firebase.internal.ApiClientUtils;
+import com.google.firebase.internal.HttpRequestInfo;
import com.google.firebase.internal.NonNull;
import com.google.firebase.internal.Nullable;
-
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
@@ -57,7 +58,7 @@
* @see
* Google Identity Toolkit
*/
-class FirebaseUserManager {
+final class FirebaseUserManager {
static final int MAX_LIST_PROVIDER_CONFIGS_RESULTS = 100;
static final int MAX_GET_ACCOUNTS_BATCH_SIZE = 100;
@@ -78,12 +79,12 @@ class FirebaseUserManager {
private final AuthHttpClient httpClient;
private FirebaseUserManager(Builder builder) {
- FirebaseApp app = checkNotNull(builder.app, "FirebaseApp must not be null");
- String projectId = ImplFirebaseTrampolines.getProjectId(app);
+ String projectId = builder.projectId;
checkArgument(!Strings.isNullOrEmpty(projectId),
"Project ID is required to access the auth service. Use a service account credential or "
+ "set the project ID explicitly via FirebaseOptions. Alternatively you can also "
+ "set the project ID via the GOOGLE_CLOUD_PROJECT environment variable.");
+ this.jsonFactory = checkNotNull(builder.jsonFactory, "JsonFactory must not be null");
final String idToolkitUrlV1 = String.format(ID_TOOLKIT_URL, "v1", projectId);
final String idToolkitUrlV2 = String.format(ID_TOOLKIT_URL, "v2", projectId);
final String tenantId = builder.tenantId;
@@ -96,10 +97,7 @@ private FirebaseUserManager(Builder builder) {
this.idpConfigMgtBaseUrl = idToolkitUrlV2 + "/tenants/" + tenantId;
}
- this.jsonFactory = app.getOptions().getJsonFactory();
- HttpRequestFactory requestFactory = builder.requestFactory == null
- ? ApiClientUtils.newAuthorizedRequestFactory(app) : builder.requestFactory;
- this.httpClient = new AuthHttpClient(jsonFactory, requestFactory);
+ this.httpClient = new AuthHttpClient(jsonFactory, builder.requestFactory);
}
@VisibleForTesting
@@ -110,40 +108,19 @@ void setInterceptor(HttpResponseInterceptor interceptor) {
UserRecord getUserById(String uid) throws FirebaseAuthException {
final Map payload = ImmutableMap.of(
"localId", ImmutableList.of(uid));
- GetAccountInfoResponse response = post(
- "/accounts:lookup", payload, GetAccountInfoResponse.class);
- if (response == null || response.getUsers() == null || response.getUsers().isEmpty()) {
- throw new FirebaseAuthException(
- AuthHttpClient.USER_NOT_FOUND_ERROR,
- "No user record found for the provided user ID: " + uid);
- }
- return new UserRecord(response.getUsers().get(0), jsonFactory);
+ return lookupUserAccount(payload, "user ID: " + uid);
}
UserRecord getUserByEmail(String email) throws FirebaseAuthException {
final Map payload = ImmutableMap.of(
"email", ImmutableList.of(email));
- GetAccountInfoResponse response = post(
- "/accounts:lookup", payload, GetAccountInfoResponse.class);
- if (response == null || response.getUsers() == null || response.getUsers().isEmpty()) {
- throw new FirebaseAuthException(
- AuthHttpClient.USER_NOT_FOUND_ERROR,
- "No user record found for the provided email: " + email);
- }
- return new UserRecord(response.getUsers().get(0), jsonFactory);
+ return lookupUserAccount(payload, "email: " + email);
}
UserRecord getUserByPhoneNumber(String phoneNumber) throws FirebaseAuthException {
final Map payload = ImmutableMap.of(
"phoneNumber", ImmutableList.of(phoneNumber));
- GetAccountInfoResponse response = post(
- "/accounts:lookup", payload, GetAccountInfoResponse.class);
- if (response == null || response.getUsers() == null || response.getUsers().isEmpty()) {
- throw new FirebaseAuthException(
- AuthHttpClient.USER_NOT_FOUND_ERROR,
- "No user record found for the provided phone number: " + phoneNumber);
- }
- return new UserRecord(response.getUsers().get(0), jsonFactory);
+ return lookupUserAccount(payload, "phone number: " + phoneNumber);
}
Set getAccountInfo(@NonNull Collection identifiers)
@@ -159,12 +136,6 @@ Set getAccountInfo(@NonNull Collection identifiers)
GetAccountInfoResponse response = post(
"/accounts:lookup", payload, GetAccountInfoResponse.class);
-
- if (response == null) {
- throw new FirebaseAuthException(
- AuthHttpClient.INTERNAL_ERROR, "Failed to parse server response");
- }
-
Set results = new HashSet<>();
if (response.getUsers() != null) {
for (GetAccountInfoResponse.User user : response.getUsers()) {
@@ -175,51 +146,26 @@ Set getAccountInfo(@NonNull Collection identifiers)
}
String createUser(UserRecord.CreateRequest request) throws FirebaseAuthException {
- GenericJson response = post(
- "/accounts", request.getProperties(), GenericJson.class);
- if (response != null) {
- String uid = (String) response.get("localId");
- if (!Strings.isNullOrEmpty(uid)) {
- return uid;
- }
- }
- throw new FirebaseAuthException(AuthHttpClient.INTERNAL_ERROR, "Failed to create new user");
+ GenericJson response = post("/accounts", request.getProperties(), GenericJson.class);
+ return (String) response.get("localId");
}
void updateUser(UserRecord.UpdateRequest request, JsonFactory jsonFactory)
throws FirebaseAuthException {
- GenericJson response = post(
- "/accounts:update", request.getProperties(jsonFactory), GenericJson.class);
- if (response == null || !request.getUid().equals(response.get("localId"))) {
- throw new FirebaseAuthException(
- AuthHttpClient.INTERNAL_ERROR, "Failed to update user: " + request.getUid());
- }
+ post("/accounts:update", request.getProperties(jsonFactory), GenericJson.class);
}
void deleteUser(String uid) throws FirebaseAuthException {
final Map payload = ImmutableMap.of("localId", uid);
- GenericJson response = post(
- "/accounts:delete", payload, GenericJson.class);
- if (response == null || !response.containsKey("kind")) {
- throw new FirebaseAuthException(
- AuthHttpClient.INTERNAL_ERROR, "Failed to delete user: " + uid);
- }
+ post("/accounts:delete", payload, GenericJson.class);
}
- /**
- * @pre uids != null
- * @pre uids.size() <= MAX_DELETE_ACCOUNTS_BATCH_SIZE
- */
DeleteUsersResult deleteUsers(@NonNull List uids) throws FirebaseAuthException {
final Map payload = ImmutableMap.of(
"localIds", uids,
"force", true);
BatchDeleteResponse response = post(
"/accounts:batchDelete", payload, BatchDeleteResponse.class);
- if (response == null) {
- throw new FirebaseAuthException(AuthHttpClient.INTERNAL_ERROR, "Failed to delete users");
- }
-
return new DeleteUsersResult(uids.size(), response);
}
@@ -231,23 +177,16 @@ DownloadAccountResponse listUsers(int maxResults, String pageToken) throws Fireb
builder.put("nextPageToken", pageToken);
}
- GenericUrl url = new GenericUrl(userMgtBaseUrl + "/accounts:batchGet");
- url.putAll(builder.build());
- DownloadAccountResponse response = httpClient.sendRequest(
- "GET", url, null, DownloadAccountResponse.class);
- if (response == null) {
- throw new FirebaseAuthException(AuthHttpClient.INTERNAL_ERROR, "Failed to retrieve users.");
- }
- return response;
+ String url = userMgtBaseUrl + "/accounts:batchGet";
+ HttpRequestInfo requestInfo = HttpRequestInfo.buildGetRequest(url)
+ .addAllParameters(builder.build());
+ return httpClient.sendRequest(requestInfo, DownloadAccountResponse.class);
}
UserImportResult importUsers(UserImportRequest request) throws FirebaseAuthException {
checkNotNull(request);
UploadAccountResponse response = post(
"/accounts:batchCreate", request, UploadAccountResponse.class);
- if (response == null) {
- throw new FirebaseAuthException(AuthHttpClient.INTERNAL_ERROR, "Failed to import users.");
- }
return new UserImportResult(request.getUsersCount(), response);
}
@@ -256,14 +195,7 @@ String createSessionCookie(String idToken,
final Map payload = ImmutableMap.of(
"idToken", idToken, "validDuration", options.getExpiresInSeconds());
GenericJson response = post(":createSessionCookie", payload, GenericJson.class);
- if (response != null) {
- String cookie = (String) response.get("sessionCookie");
- if (!Strings.isNullOrEmpty(cookie)) {
- return cookie;
- }
- }
- throw new FirebaseAuthException(
- AuthHttpClient.INTERNAL_ERROR, "Failed to create session cookie");
+ return (String) response.get("sessionCookie");
}
String getEmailActionLink(EmailLinkType type, String email,
@@ -275,57 +207,70 @@ String getEmailActionLink(EmailLinkType type, String email,
if (settings != null) {
payload.putAll(settings.getProperties());
}
+
GenericJson response = post("/accounts:sendOobCode", payload.build(), GenericJson.class);
- if (response != null) {
- String link = (String) response.get("oobLink");
- if (!Strings.isNullOrEmpty(link)) {
- return link;
- }
+ return (String) response.get("oobLink");
+ }
+
+ private UserRecord lookupUserAccount(
+ Map payload, String identifier) throws FirebaseAuthException {
+ HttpRequestInfo requestInfo = HttpRequestInfo.buildJsonPostRequest(
+ userMgtBaseUrl + "/accounts:lookup", payload);
+ IncomingHttpResponse response = httpClient.sendRequest(requestInfo);
+ GetAccountInfoResponse parsed = httpClient.parse(response, GetAccountInfoResponse.class);
+ if (parsed.getUsers() == null || parsed.getUsers().isEmpty()) {
+ throw new FirebaseAuthException(ErrorCode.NOT_FOUND,
+ "No user record found for the provided " + identifier,
+ null,
+ response,
+ AuthErrorCode.USER_NOT_FOUND);
}
- throw new FirebaseAuthException(
- AuthHttpClient.INTERNAL_ERROR, "Failed to create email action link");
+
+ return new UserRecord(parsed.getUsers().get(0), jsonFactory);
}
OidcProviderConfig createOidcProviderConfig(
OidcProviderConfig.CreateRequest request) throws FirebaseAuthException {
- GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + "/oauthIdpConfigs");
- url.set("oauthIdpConfigId", request.getProviderId());
- return httpClient.sendRequest("POST", url, request.getProperties(), OidcProviderConfig.class);
+ String url = idpConfigMgtBaseUrl + "/oauthIdpConfigs";
+ HttpRequestInfo requestInfo = HttpRequestInfo.buildJsonPostRequest(url, request.getProperties())
+ .addParameter("oauthIdpConfigId", request.getProviderId());
+ return httpClient.sendRequest(requestInfo, OidcProviderConfig.class);
}
SamlProviderConfig createSamlProviderConfig(
SamlProviderConfig.CreateRequest request) throws FirebaseAuthException {
- GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + "/inboundSamlConfigs");
- url.set("inboundSamlConfigId", request.getProviderId());
- return httpClient.sendRequest("POST", url, request.getProperties(), SamlProviderConfig.class);
+ String url = idpConfigMgtBaseUrl + "/inboundSamlConfigs";
+ HttpRequestInfo requestInfo = HttpRequestInfo.buildJsonPostRequest(url, request.getProperties())
+ .addParameter("inboundSamlConfigId", request.getProviderId());
+ return httpClient.sendRequest(requestInfo, SamlProviderConfig.class);
}
OidcProviderConfig updateOidcProviderConfig(OidcProviderConfig.UpdateRequest request)
throws FirebaseAuthException {
Map properties = request.getProperties();
- GenericUrl url =
- new GenericUrl(idpConfigMgtBaseUrl + getOidcUrlSuffix(request.getProviderId()));
- url.put("updateMask", Joiner.on(",").join(AuthHttpClient.generateMask(properties)));
- return httpClient.sendRequest("PATCH", url, properties, OidcProviderConfig.class);
+ String url = idpConfigMgtBaseUrl + getOidcUrlSuffix(request.getProviderId());
+ HttpRequestInfo requestInfo = HttpRequestInfo.buildJsonPatchRequest(url, properties)
+ .addParameter("updateMask", Joiner.on(",").join(AuthHttpClient.generateMask(properties)));
+ return httpClient.sendRequest(requestInfo, OidcProviderConfig.class);
}
SamlProviderConfig updateSamlProviderConfig(SamlProviderConfig.UpdateRequest request)
throws FirebaseAuthException {
Map properties = request.getProperties();
- GenericUrl url =
- new GenericUrl(idpConfigMgtBaseUrl + getSamlUrlSuffix(request.getProviderId()));
- url.put("updateMask", Joiner.on(",").join(AuthHttpClient.generateMask(properties)));
- return httpClient.sendRequest("PATCH", url, properties, SamlProviderConfig.class);
+ String url = idpConfigMgtBaseUrl + getSamlUrlSuffix(request.getProviderId());
+ HttpRequestInfo requestInfo = HttpRequestInfo.buildJsonPatchRequest(url, properties)
+ .addParameter("updateMask", Joiner.on(",").join(AuthHttpClient.generateMask(properties)));
+ return httpClient.sendRequest(requestInfo, SamlProviderConfig.class);
}
OidcProviderConfig getOidcProviderConfig(String providerId) throws FirebaseAuthException {
- GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + getOidcUrlSuffix(providerId));
- return httpClient.sendRequest("GET", url, null, OidcProviderConfig.class);
+ String url = idpConfigMgtBaseUrl + getOidcUrlSuffix(providerId);
+ return httpClient.sendRequest(HttpRequestInfo.buildGetRequest(url), OidcProviderConfig.class);
}
SamlProviderConfig getSamlProviderConfig(String providerId) throws FirebaseAuthException {
- GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + getSamlUrlSuffix(providerId));
- return httpClient.sendRequest("GET", url, null, SamlProviderConfig.class);
+ String url = idpConfigMgtBaseUrl + getSamlUrlSuffix(providerId);
+ return httpClient.sendRequest(HttpRequestInfo.buildGetRequest(url), SamlProviderConfig.class);
}
ListOidcProviderConfigsResponse listOidcProviderConfigs(int maxResults, String pageToken)
@@ -338,15 +283,10 @@ ListOidcProviderConfigsResponse listOidcProviderConfigs(int maxResults, String p
builder.put("nextPageToken", pageToken);
}
- GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + "/oauthIdpConfigs");
- url.putAll(builder.build());
- ListOidcProviderConfigsResponse response =
- httpClient.sendRequest("GET", url, null, ListOidcProviderConfigsResponse.class);
- if (response == null) {
- throw new FirebaseAuthException(
- AuthHttpClient.INTERNAL_ERROR, "Failed to retrieve provider configs.");
- }
- return response;
+ String url = idpConfigMgtBaseUrl + "/oauthIdpConfigs";
+ HttpRequestInfo requestInfo = HttpRequestInfo.buildGetRequest(url)
+ .addAllParameters(builder.build());
+ return httpClient.sendRequest(requestInfo, ListOidcProviderConfigsResponse.class);
}
ListSamlProviderConfigsResponse listSamlProviderConfigs(int maxResults, String pageToken)
@@ -359,25 +299,20 @@ ListSamlProviderConfigsResponse listSamlProviderConfigs(int maxResults, String p
builder.put("nextPageToken", pageToken);
}
- GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + "/inboundSamlConfigs");
- url.putAll(builder.build());
- ListSamlProviderConfigsResponse response =
- httpClient.sendRequest("GET", url, null, ListSamlProviderConfigsResponse.class);
- if (response == null) {
- throw new FirebaseAuthException(
- AuthHttpClient.INTERNAL_ERROR, "Failed to retrieve provider configs.");
- }
- return response;
+ String url = idpConfigMgtBaseUrl + "/inboundSamlConfigs";
+ HttpRequestInfo requestInfo = HttpRequestInfo.buildGetRequest(url)
+ .addAllParameters(builder.build());
+ return httpClient.sendRequest(requestInfo, ListSamlProviderConfigsResponse.class);
}
void deleteOidcProviderConfig(String providerId) throws FirebaseAuthException {
- GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + getOidcUrlSuffix(providerId));
- httpClient.sendRequest("DELETE", url, null, GenericJson.class);
+ String url = idpConfigMgtBaseUrl + getOidcUrlSuffix(providerId);
+ httpClient.sendRequest(HttpRequestInfo.buildDeleteRequest(url));
}
void deleteSamlProviderConfig(String providerId) throws FirebaseAuthException {
- GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + getSamlUrlSuffix(providerId));
- httpClient.sendRequest("DELETE", url, null, GenericJson.class);
+ String url = idpConfigMgtBaseUrl + getSamlUrlSuffix(providerId);
+ httpClient.sendRequest(HttpRequestInfo.buildDeleteRequest(url));
}
private static String getOidcUrlSuffix(String providerId) {
@@ -393,8 +328,8 @@ private static String getSamlUrlSuffix(String providerId) {
private T post(String path, Object content, Class clazz) throws FirebaseAuthException {
checkArgument(!Strings.isNullOrEmpty(path), "path must not be null or empty");
checkNotNull(content, "content must not be null for POST requests");
- GenericUrl url = new GenericUrl(userMgtBaseUrl + path);
- return httpClient.sendRequest("POST", url, content, clazz);
+ String url = userMgtBaseUrl + path;
+ return httpClient.sendRequest(HttpRequestInfo.buildJsonPostRequest(url, content), clazz);
}
static class UserImportRequest extends GenericJson {
@@ -437,18 +372,30 @@ enum EmailLinkType {
PASSWORD_RESET,
}
+ static FirebaseUserManager createUserManager(FirebaseApp app, String tenantId) {
+ return FirebaseUserManager.builder()
+ .setProjectId(ImplFirebaseTrampolines.getProjectId(app))
+ .setTenantId(tenantId)
+ .setHttpRequestFactory(ApiClientUtils.newAuthorizedRequestFactory(app))
+ .setJsonFactory(app.getOptions().getJsonFactory())
+ .build();
+ }
+
static Builder builder() {
return new Builder();
}
static class Builder {
- private FirebaseApp app;
+ private String projectId;
private String tenantId;
private HttpRequestFactory requestFactory;
+ private JsonFactory jsonFactory;
- Builder setFirebaseApp(FirebaseApp app) {
- this.app = app;
+ private Builder() { }
+
+ public Builder setProjectId(String projectId) {
+ this.projectId = projectId;
return this;
}
@@ -462,6 +409,11 @@ Builder setHttpRequestFactory(HttpRequestFactory requestFactory) {
return this;
}
+ public Builder setJsonFactory(JsonFactory jsonFactory) {
+ this.jsonFactory = jsonFactory;
+ return this;
+ }
+
FirebaseUserManager build() {
return new FirebaseUserManager(this);
}
diff --git a/src/main/java/com/google/firebase/auth/ListProviderConfigsPage.java b/src/main/java/com/google/firebase/auth/ListProviderConfigsPage.java
index 361f932bd..0e35337a9 100644
--- a/src/main/java/com/google/firebase/auth/ListProviderConfigsPage.java
+++ b/src/main/java/com/google/firebase/auth/ListProviderConfigsPage.java
@@ -88,7 +88,7 @@ public String getNextPageToken() {
@Override
public ListProviderConfigsPage getNextPage() {
if (hasNextPage()) {
- Factory factory = new Factory(source, maxResults, currentBatch.getPageToken());
+ Factory factory = new Factory<>(source, maxResults, currentBatch.getPageToken());
try {
return factory.create();
} catch (FirebaseAuthException e) {
@@ -99,25 +99,25 @@ public ListProviderConfigsPage getNextPage() {
}
/**
- * Returns an {@link Iterable} that facilitates transparently iterating over all the provider
+ * Returns an {@code Iterable} that facilitates transparently iterating over all the provider
* configs in the current Firebase project, starting from this page.
*
- * The {@link Iterator} instances produced by the returned {@link Iterable} never buffers more
+ *
The {@code Iterator} instances produced by the returned {@code Iterable} never buffers more
* than one page of provider configs at a time. It is safe to abandon the iterators (i.e. break
* the loops) at any time.
*
- * @return a new {@link Iterable} instance.
+ * @return a new {@code Iterable} instance.
*/
@NonNull
@Override
public Iterable iterateAll() {
- return new ProviderConfigIterable(this);
+ return new ProviderConfigIterable<>(this);
}
/**
- * Returns an {@link Iterable} over the provider configs in this page.
+ * Returns an {@code Iterable} over the provider configs in this page.
*
- * @return a {@link Iterable} instance.
+ * @return a {@code Iterable} instance.
*/
@NonNull
@Override
@@ -136,7 +136,7 @@ private static class ProviderConfigIterable implements
@Override
@NonNull
public Iterator iterator() {
- return new ProviderConfigIterator(startingPage);
+ return new ProviderConfigIterator<>(startingPage);
}
/**
@@ -230,7 +230,7 @@ public ListSamlProviderConfigsResponse fetch(int maxResults, String pageToken)
}
/**
- * A simple factory class for {@link ProviderConfigsPage} instances.
+ * A simple factory class for {@link ListProviderConfigsPage} instances.
*
* Performs argument validation before attempting to load any provider config data (which is
* expensive, and hence may be performed asynchronously on a separate thread).
@@ -261,7 +261,7 @@ static class Factory {
ListProviderConfigsPage create() throws FirebaseAuthException {
ListProviderConfigsResponse batch = source.fetch(maxResults, pageToken);
- return new ListProviderConfigsPage(batch, source, maxResults);
+ return new ListProviderConfigsPage<>(batch, source, maxResults);
}
}
}
diff --git a/src/main/java/com/google/firebase/auth/OidcProviderConfig.java b/src/main/java/com/google/firebase/auth/OidcProviderConfig.java
index 879b7e79f..26931788e 100644
--- a/src/main/java/com/google/firebase/auth/OidcProviderConfig.java
+++ b/src/main/java/com/google/firebase/auth/OidcProviderConfig.java
@@ -132,7 +132,7 @@ public static final class UpdateRequest extends AbstractUpdateRequestThe returned object should be passed to
- * {@link AbstractFirebaseAuth#updateOidcProviderConfig(CreateRequest)} to save the updated
+ * {@link AbstractFirebaseAuth#updateOidcProviderConfig(UpdateRequest)} to save the updated
* config.
*
* @param providerId A non-null, non-empty provider ID string.
diff --git a/src/main/java/com/google/firebase/auth/RevocationCheckDecorator.java b/src/main/java/com/google/firebase/auth/RevocationCheckDecorator.java
index e53ad25c4..74cda69c9 100644
--- a/src/main/java/com/google/firebase/auth/RevocationCheckDecorator.java
+++ b/src/main/java/com/google/firebase/auth/RevocationCheckDecorator.java
@@ -20,30 +20,27 @@
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Strings;
+import com.google.firebase.ErrorCode;
/**
* A decorator for adding token revocation checks to an existing {@link FirebaseTokenVerifier}.
*/
class RevocationCheckDecorator implements FirebaseTokenVerifier {
- static final String ID_TOKEN_REVOKED_ERROR = "id-token-revoked";
- static final String SESSION_COOKIE_REVOKED_ERROR = "session-cookie-revoked";
-
private final FirebaseTokenVerifier tokenVerifier;
private final FirebaseUserManager userManager;
- private final String errorCode;
+ private final AuthErrorCode errorCode;
private final String shortName;
private RevocationCheckDecorator(
FirebaseTokenVerifier tokenVerifier,
FirebaseUserManager userManager,
- String errorCode,
+ AuthErrorCode errorCode,
String shortName) {
this.tokenVerifier = checkNotNull(tokenVerifier);
this.userManager = checkNotNull(userManager);
- checkArgument(!Strings.isNullOrEmpty(errorCode));
+ this.errorCode = checkNotNull(errorCode);
checkArgument(!Strings.isNullOrEmpty(shortName));
- this.errorCode = errorCode;
this.shortName = shortName;
}
@@ -55,8 +52,14 @@ private RevocationCheckDecorator(
public FirebaseToken verifyToken(String token) throws FirebaseAuthException {
FirebaseToken firebaseToken = tokenVerifier.verifyToken(token);
if (isRevoked(firebaseToken)) {
- throw new FirebaseAuthException(errorCode, "Firebase " + shortName + " revoked");
+ throw new FirebaseAuthException(
+ ErrorCode.INVALID_ARGUMENT,
+ "Firebase " + shortName + " is revoked.",
+ null,
+ null,
+ errorCode);
}
+
return firebaseToken;
}
@@ -69,12 +72,12 @@ private boolean isRevoked(FirebaseToken firebaseToken) throws FirebaseAuthExcept
static RevocationCheckDecorator decorateIdTokenVerifier(
FirebaseTokenVerifier tokenVerifier, FirebaseUserManager userManager) {
return new RevocationCheckDecorator(
- tokenVerifier, userManager, ID_TOKEN_REVOKED_ERROR, "id token");
+ tokenVerifier, userManager, AuthErrorCode.REVOKED_ID_TOKEN, "id token");
}
static RevocationCheckDecorator decorateSessionCookieVerifier(
FirebaseTokenVerifier tokenVerifier, FirebaseUserManager userManager) {
return new RevocationCheckDecorator(
- tokenVerifier, userManager, SESSION_COOKIE_REVOKED_ERROR, "session cookie");
+ tokenVerifier, userManager, AuthErrorCode.REVOKED_SESSION_COOKIE, "session cookie");
}
}
diff --git a/src/main/java/com/google/firebase/auth/hash/Bcrypt.java b/src/main/java/com/google/firebase/auth/hash/Bcrypt.java
index 2b5f89029..9c55d8d56 100644
--- a/src/main/java/com/google/firebase/auth/hash/Bcrypt.java
+++ b/src/main/java/com/google/firebase/auth/hash/Bcrypt.java
@@ -24,7 +24,7 @@
* Represents the Bcrypt password hashing algorithm. Can be used as an instance of
* {@link com.google.firebase.auth.UserImportHash} when importing users.
*/
-public class Bcrypt extends UserImportHash {
+public final class Bcrypt extends UserImportHash {
private Bcrypt() {
super("BCRYPT");
diff --git a/src/main/java/com/google/firebase/auth/hash/HmacMd5.java b/src/main/java/com/google/firebase/auth/hash/HmacMd5.java
index b67574358..b2ffdb852 100644
--- a/src/main/java/com/google/firebase/auth/hash/HmacMd5.java
+++ b/src/main/java/com/google/firebase/auth/hash/HmacMd5.java
@@ -20,7 +20,7 @@
* Represents the HMAC MD5 password hashing algorithm. Can be used as an instance of
* {@link com.google.firebase.auth.UserImportHash} when importing users.
*/
-public class HmacMd5 extends Hmac {
+public final class HmacMd5 extends Hmac {
private HmacMd5(Builder builder) {
super("HMAC_MD5", builder);
diff --git a/src/main/java/com/google/firebase/auth/hash/HmacSha1.java b/src/main/java/com/google/firebase/auth/hash/HmacSha1.java
index a9ecefd6f..964e5e60d 100644
--- a/src/main/java/com/google/firebase/auth/hash/HmacSha1.java
+++ b/src/main/java/com/google/firebase/auth/hash/HmacSha1.java
@@ -20,7 +20,7 @@
* Represents the HMAC SHA1 password hashing algorithm. Can be used as an instance of
* {@link com.google.firebase.auth.UserImportHash} when importing users.
*/
-public class HmacSha1 extends Hmac {
+public final class HmacSha1 extends Hmac {
private HmacSha1(Builder builder) {
super("HMAC_SHA1", builder);
diff --git a/src/main/java/com/google/firebase/auth/hash/HmacSha256.java b/src/main/java/com/google/firebase/auth/hash/HmacSha256.java
index 78f131cff..92917e6f3 100644
--- a/src/main/java/com/google/firebase/auth/hash/HmacSha256.java
+++ b/src/main/java/com/google/firebase/auth/hash/HmacSha256.java
@@ -20,7 +20,7 @@
* Represents the HMAC SHA256 password hashing algorithm. Can be used as an instance of
* {@link com.google.firebase.auth.UserImportHash} when importing users.
*/
-public class HmacSha256 extends Hmac {
+public final class HmacSha256 extends Hmac {
private HmacSha256(Builder builder) {
super("HMAC_SHA256", builder);
diff --git a/src/main/java/com/google/firebase/auth/hash/HmacSha512.java b/src/main/java/com/google/firebase/auth/hash/HmacSha512.java
index 21e6a2b25..b5a0e09ec 100644
--- a/src/main/java/com/google/firebase/auth/hash/HmacSha512.java
+++ b/src/main/java/com/google/firebase/auth/hash/HmacSha512.java
@@ -20,7 +20,7 @@
* Represents the HMAC SHA512 password hashing algorithm. Can be used as an instance of
* {@link com.google.firebase.auth.UserImportHash} when importing users.
*/
-public class HmacSha512 extends Hmac {
+public final class HmacSha512 extends Hmac {
private HmacSha512(Builder builder) {
super("HMAC_SHA512", builder);
diff --git a/src/main/java/com/google/firebase/auth/hash/Md5.java b/src/main/java/com/google/firebase/auth/hash/Md5.java
index 2abbe55ba..353b07f01 100644
--- a/src/main/java/com/google/firebase/auth/hash/Md5.java
+++ b/src/main/java/com/google/firebase/auth/hash/Md5.java
@@ -20,7 +20,7 @@
* Represents the MD5 password hashing algorithm. Can be used as an instance of
* {@link com.google.firebase.auth.UserImportHash} when importing users.
*/
-public class Md5 extends RepeatableHash {
+public final class Md5 extends RepeatableHash {
private Md5(Builder builder) {
super("MD5", 0, 8192, builder);
diff --git a/src/main/java/com/google/firebase/auth/hash/Pbkdf2Sha256.java b/src/main/java/com/google/firebase/auth/hash/Pbkdf2Sha256.java
index 4c5108e35..6c3ffeff2 100644
--- a/src/main/java/com/google/firebase/auth/hash/Pbkdf2Sha256.java
+++ b/src/main/java/com/google/firebase/auth/hash/Pbkdf2Sha256.java
@@ -20,7 +20,7 @@
* Represents the PBKDF2 SHA256 password hashing algorithm. Can be used as an instance of
* {@link com.google.firebase.auth.UserImportHash} when importing users.
*/
-public class Pbkdf2Sha256 extends RepeatableHash {
+public final class Pbkdf2Sha256 extends RepeatableHash {
private Pbkdf2Sha256(Builder builder) {
super("PBKDF2_SHA256", 0, 120000, builder);
diff --git a/src/main/java/com/google/firebase/auth/hash/PbkdfSha1.java b/src/main/java/com/google/firebase/auth/hash/PbkdfSha1.java
index 8afe3f4ab..647a365b3 100644
--- a/src/main/java/com/google/firebase/auth/hash/PbkdfSha1.java
+++ b/src/main/java/com/google/firebase/auth/hash/PbkdfSha1.java
@@ -20,7 +20,7 @@
* Represents the PBKDF SHA1 password hashing algorithm. Can be used as an instance of
* {@link com.google.firebase.auth.UserImportHash} when importing users.
*/
-public class PbkdfSha1 extends RepeatableHash {
+public final class PbkdfSha1 extends RepeatableHash {
private PbkdfSha1(Builder builder) {
super("PBKDF_SHA1", 0, 120000, builder);
diff --git a/src/main/java/com/google/firebase/auth/hash/Sha1.java b/src/main/java/com/google/firebase/auth/hash/Sha1.java
index 385f4310c..9de01b0b8 100644
--- a/src/main/java/com/google/firebase/auth/hash/Sha1.java
+++ b/src/main/java/com/google/firebase/auth/hash/Sha1.java
@@ -20,7 +20,7 @@
* Represents the SHA1 password hashing algorithm. Can be used as an instance of
* {@link com.google.firebase.auth.UserImportHash} when importing users.
*/
-public class Sha1 extends RepeatableHash {
+public final class Sha1 extends RepeatableHash {
private Sha1(Builder builder) {
super("SHA1", 1, 8192, builder);
diff --git a/src/main/java/com/google/firebase/auth/hash/Sha256.java b/src/main/java/com/google/firebase/auth/hash/Sha256.java
index f65aee19a..d0185195e 100644
--- a/src/main/java/com/google/firebase/auth/hash/Sha256.java
+++ b/src/main/java/com/google/firebase/auth/hash/Sha256.java
@@ -20,7 +20,7 @@
* Represents the SHA256 password hashing algorithm. Can be used as an instance of
* {@link com.google.firebase.auth.UserImportHash} when importing users.
*/
-public class Sha256 extends RepeatableHash {
+public final class Sha256 extends RepeatableHash {
private Sha256(Builder builder) {
super("SHA256", 1, 8192, builder);
diff --git a/src/main/java/com/google/firebase/auth/hash/Sha512.java b/src/main/java/com/google/firebase/auth/hash/Sha512.java
index e582520a9..f468abe1c 100644
--- a/src/main/java/com/google/firebase/auth/hash/Sha512.java
+++ b/src/main/java/com/google/firebase/auth/hash/Sha512.java
@@ -20,7 +20,7 @@
* Represents the SHA512 password hashing algorithm. Can be used as an instance of
* {@link com.google.firebase.auth.UserImportHash} when importing users.
*/
-public class Sha512 extends RepeatableHash {
+public final class Sha512 extends RepeatableHash {
private Sha512(Builder builder) {
super("SHA512", 1, 8192, builder);
diff --git a/src/main/java/com/google/firebase/auth/hash/StandardScrypt.java b/src/main/java/com/google/firebase/auth/hash/StandardScrypt.java
index 49f7d72f5..139fd114f 100644
--- a/src/main/java/com/google/firebase/auth/hash/StandardScrypt.java
+++ b/src/main/java/com/google/firebase/auth/hash/StandardScrypt.java
@@ -24,7 +24,7 @@
* Represents the Standard Scrypt password hashing algorithm. Can be used as an instance of
* {@link com.google.firebase.auth.UserImportHash} when importing users.
*/
-public class StandardScrypt extends UserImportHash {
+public final class StandardScrypt extends UserImportHash {
private final int derivedKeyLength;
private final int blockSize;
diff --git a/src/main/java/com/google/firebase/auth/internal/AuthErrorHandler.java b/src/main/java/com/google/firebase/auth/internal/AuthErrorHandler.java
new file mode 100644
index 000000000..e98911407
--- /dev/null
+++ b/src/main/java/com/google/firebase/auth/internal/AuthErrorHandler.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2020 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 static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.api.client.json.GenericJson;
+import com.google.api.client.json.JsonFactory;
+import com.google.api.client.util.Key;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import com.google.firebase.ErrorCode;
+import com.google.firebase.FirebaseException;
+import com.google.firebase.auth.AuthErrorCode;
+import com.google.firebase.auth.FirebaseAuthException;
+import com.google.firebase.internal.AbstractHttpErrorHandler;
+import com.google.firebase.internal.Nullable;
+import java.io.IOException;
+import java.util.Map;
+
+final class AuthErrorHandler extends AbstractHttpErrorHandler {
+
+ private static final Map ERROR_CODES =
+ ImmutableMap.builder()
+ .put(
+ "CONFIGURATION_NOT_FOUND",
+ new AuthError(
+ ErrorCode.NOT_FOUND,
+ "No IdP configuration found corresponding to the provided identifier",
+ AuthErrorCode.CONFIGURATION_NOT_FOUND))
+ .put(
+ "DUPLICATE_EMAIL",
+ new AuthError(
+ ErrorCode.ALREADY_EXISTS,
+ "The user with the provided email already exists",
+ AuthErrorCode.EMAIL_ALREADY_EXISTS))
+ .put(
+ "DUPLICATE_LOCAL_ID",
+ new AuthError(
+ ErrorCode.ALREADY_EXISTS,
+ "The user with the provided uid already exists",
+ AuthErrorCode.UID_ALREADY_EXISTS))
+ .put(
+ "EMAIL_EXISTS",
+ new AuthError(
+ ErrorCode.ALREADY_EXISTS,
+ "The user with the provided email already exists",
+ AuthErrorCode.EMAIL_ALREADY_EXISTS))
+ .put(
+ "INVALID_DYNAMIC_LINK_DOMAIN",
+ new AuthError(
+ ErrorCode.INVALID_ARGUMENT,
+ "The provided dynamic link domain is not "
+ + "configured or authorized for the current project",
+ AuthErrorCode.INVALID_DYNAMIC_LINK_DOMAIN))
+ .put(
+ "PHONE_NUMBER_EXISTS",
+ new AuthError(
+ ErrorCode.ALREADY_EXISTS,
+ "The user with the provided phone number already exists",
+ AuthErrorCode.PHONE_NUMBER_ALREADY_EXISTS))
+ .put(
+ "TENANT_NOT_FOUND",
+ new AuthError(
+ ErrorCode.NOT_FOUND,
+ "No tenant found for the given identifier",
+ AuthErrorCode.TENANT_NOT_FOUND))
+ .put(
+ "UNAUTHORIZED_DOMAIN",
+ new AuthError(
+ ErrorCode.INVALID_ARGUMENT,
+ "The domain of the continue URL is not whitelisted",
+ AuthErrorCode.UNAUTHORIZED_CONTINUE_URL))
+ .put(
+ "USER_NOT_FOUND",
+ new AuthError(
+ ErrorCode.NOT_FOUND,
+ "No user record found for the given identifier",
+ AuthErrorCode.USER_NOT_FOUND))
+ .build();
+
+ private final JsonFactory jsonFactory;
+
+ AuthErrorHandler(JsonFactory jsonFactory) {
+ this.jsonFactory = checkNotNull(jsonFactory);
+ }
+
+ @Override
+ protected FirebaseAuthException createException(FirebaseException base) {
+ String response = getResponse(base);
+ AuthServiceErrorResponse parsed = safeParse(response);
+ AuthError errorInfo = ERROR_CODES.get(parsed.getCode());
+ if (errorInfo != null) {
+ return new FirebaseAuthException(
+ errorInfo.getErrorCode(),
+ errorInfo.buildMessage(parsed),
+ base.getCause(),
+ base.getHttpResponse(),
+ errorInfo.getAuthErrorCode());
+ }
+
+ return new FirebaseAuthException(base);
+ }
+
+ private String getResponse(FirebaseException base) {
+ if (base.getHttpResponse() == null) {
+ return null;
+ }
+
+ return base.getHttpResponse().getContent();
+ }
+
+ private AuthServiceErrorResponse safeParse(String response) {
+ AuthServiceErrorResponse parsed = new AuthServiceErrorResponse();
+ if (!Strings.isNullOrEmpty(response)) {
+ try {
+ jsonFactory.createJsonParser(response).parse(parsed);
+ } catch (IOException ignore) {
+ // Ignore any error that may occur while parsing the error response. The server
+ // may have responded with a non-json payload.
+ }
+ }
+
+ return parsed;
+ }
+
+ private static class AuthError {
+
+ private final ErrorCode errorCode;
+ private final String message;
+ private final AuthErrorCode authErrorCode;
+
+ AuthError(ErrorCode errorCode, String message, AuthErrorCode authErrorCode) {
+ this.errorCode = errorCode;
+ this.message = message;
+ this.authErrorCode = authErrorCode;
+ }
+
+ ErrorCode getErrorCode() {
+ return errorCode;
+ }
+
+ AuthErrorCode getAuthErrorCode() {
+ return authErrorCode;
+ }
+
+ String buildMessage(AuthServiceErrorResponse response) {
+ StringBuilder builder = new StringBuilder(this.message)
+ .append(" (").append(response.getCode()).append(")");
+ String detail = response.getDetail();
+ if (!Strings.isNullOrEmpty(detail)) {
+ builder.append(": ").append(detail);
+ } else {
+ builder.append(".");
+ }
+
+ return builder.toString();
+ }
+ }
+
+ /**
+ * JSON data binding for JSON error messages sent by Google identity toolkit service. These
+ * error messages take the form `{"error": {"message": "CODE: OPTIONAL DETAILS"}}`.
+ */
+ private static class AuthServiceErrorResponse {
+
+ @Key("error")
+ private GenericJson error;
+
+ @Nullable
+ public String getCode() {
+ String message = getMessage();
+ if (Strings.isNullOrEmpty(message)) {
+ return null;
+ }
+
+ int separator = message.indexOf(':');
+ if (separator != -1) {
+ return message.substring(0, separator);
+ }
+
+ return message;
+ }
+
+ @Nullable
+ public String getDetail() {
+ String message = getMessage();
+ if (Strings.isNullOrEmpty(message)) {
+ return null;
+ }
+
+ int separator = message.indexOf(':');
+ if (separator != -1) {
+ return message.substring(separator + 1).trim();
+ }
+
+ return null;
+ }
+
+ private String getMessage() {
+ if (error == null) {
+ return null;
+ }
+
+ return (String) error.get("message");
+ }
+ }
+}
diff --git a/src/main/java/com/google/firebase/auth/internal/AuthHttpClient.java b/src/main/java/com/google/firebase/auth/internal/AuthHttpClient.java
index ad77236d1..e8413f13f 100644
--- a/src/main/java/com/google/firebase/auth/internal/AuthHttpClient.java
+++ b/src/main/java/com/google/firebase/auth/internal/AuthHttpClient.java
@@ -16,26 +16,15 @@
package com.google.firebase.auth.internal;
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import com.google.api.client.http.GenericUrl;
-import com.google.api.client.http.HttpContent;
-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.json.JsonHttpContent;
import com.google.api.client.json.JsonFactory;
-import com.google.api.client.json.JsonObjectParser;
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;
+import com.google.firebase.IncomingHttpResponse;
import com.google.firebase.auth.FirebaseAuthException;
-import com.google.firebase.internal.Nullable;
+import com.google.firebase.internal.ErrorHandlingHttpClient;
+import com.google.firebase.internal.HttpRequestInfo;
import com.google.firebase.internal.SdkUtils;
-import java.io.IOException;
import java.util.Map;
import java.util.Set;
@@ -44,45 +33,17 @@
*/
public final class AuthHttpClient {
- public static final String CONFIGURATION_NOT_FOUND_ERROR = "configuration-not-found";
- public static final String INTERNAL_ERROR = "internal-error";
- public static final String TENANT_NOT_FOUND_ERROR = "tenant-not-found";
- public static final String USER_NOT_FOUND_ERROR = "user-not-found";
-
private static final String CLIENT_VERSION_HEADER = "X-Client-Version";
private static final String CLIENT_VERSION = "Java/Admin/" + SdkUtils.getVersion();
- // 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 ERROR_CODES = ImmutableMap.builder()
- .put("CLAIMS_TOO_LARGE", "claims-too-large")
- .put("CONFIGURATION_NOT_FOUND", CONFIGURATION_NOT_FOUND_ERROR)
- .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")
- .put("UNAUTHORIZED_DOMAIN", "unauthorized-continue-uri")
- .put("INVALID_DYNAMIC_LINK_DOMAIN", "invalid-dynamic-link-domain")
- .put("TENANT_NOT_FOUND", TENANT_NOT_FOUND_ERROR)
- .build();
-
+ private final ErrorHandlingHttpClient httpClient;
private final JsonFactory jsonFactory;
- private final HttpRequestFactory requestFactory;
-
- private HttpResponseInterceptor interceptor;
public AuthHttpClient(JsonFactory jsonFactory, HttpRequestFactory requestFactory) {
+ AuthErrorHandler authErrorHandler = new AuthErrorHandler(jsonFactory);
+ this.httpClient = new ErrorHandlingHttpClient<>(requestFactory, jsonFactory, authErrorHandler);
this.jsonFactory = jsonFactory;
- this.requestFactory = requestFactory;
}
public static Set generateMask(Map properties) {
@@ -101,60 +62,20 @@ public static Set generateMask(Map properties) {
}
public void setInterceptor(HttpResponseInterceptor interceptor) {
- this.interceptor = interceptor;
+ this.httpClient.setInterceptor(interceptor);
}
- public T sendRequest(
- String method, GenericUrl url,
- @Nullable Object content, Class clazz) throws FirebaseAuthException {
+ public T sendRequest(HttpRequestInfo request, Class clazz) throws FirebaseAuthException {
+ IncomingHttpResponse response = this.sendRequest(request);
+ return this.parse(response, clazz);
+ }
- checkArgument(!Strings.isNullOrEmpty(method), "method must not be null or empty");
- checkNotNull(url, "url must not be null");
- checkNotNull(clazz, "response class must not be null");
- HttpResponse response = null;
- try {
- HttpContent httpContent = content != null ? new JsonHttpContent(jsonFactory, content) : null;
- HttpRequest request =
- requestFactory.buildRequest(method.equals("PATCH") ? "POST" : method, url, httpContent);
- request.setParser(new JsonObjectParser(jsonFactory));
- request.getHeaders().set(CLIENT_VERSION_HEADER, CLIENT_VERSION);
- if (method.equals("PATCH")) {
- request.getHeaders().set("X-HTTP-Method-Override", "PATCH");
- }
- 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 the Firebase Auth backend service", e);
- } finally {
- if (response != null) {
- try {
- response.disconnect();
- } catch (IOException ignored) {
- // Ignored
- }
- }
- }
+ public IncomingHttpResponse sendRequest(HttpRequestInfo request) throws FirebaseAuthException {
+ request.addHeader(CLIENT_VERSION_HEADER, CLIENT_VERSION);
+ return httpClient.send(request);
}
- 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, "Firebase Auth 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);
+ public T parse(IncomingHttpResponse response, Class clazz) throws FirebaseAuthException {
+ return httpClient.parse(response, clazz);
}
}
diff --git a/src/main/java/com/google/firebase/auth/internal/CryptoSigner.java b/src/main/java/com/google/firebase/auth/internal/CryptoSigner.java
index 3036f9f28..2ff30a20c 100644
--- a/src/main/java/com/google/firebase/auth/internal/CryptoSigner.java
+++ b/src/main/java/com/google/firebase/auth/internal/CryptoSigner.java
@@ -16,6 +16,7 @@
package com.google.firebase.auth.internal;
+import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.internal.NonNull;
import java.io.IOException;
@@ -32,10 +33,10 @@ interface CryptoSigner {
*
* @param payload Data to be signed
* @return Signature as a byte array
- * @throws IOException If an error occurs during signing
+ * @throws FirebaseAuthException If an error occurs during signing
*/
@NonNull
- byte[] sign(@NonNull byte[] payload) throws IOException;
+ byte[] sign(@NonNull byte[] payload) throws FirebaseAuthException;
/**
* Returns the client email of the service account used to sign payloads.
diff --git a/src/main/java/com/google/firebase/auth/internal/CryptoSigners.java b/src/main/java/com/google/firebase/auth/internal/CryptoSigners.java
index 6ea70b880..7bc54afdf 100644
--- a/src/main/java/com/google/firebase/auth/internal/CryptoSigners.java
+++ b/src/main/java/com/google/firebase/auth/internal/CryptoSigners.java
@@ -8,10 +8,8 @@
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpResponseInterceptor;
-import com.google.api.client.http.json.JsonHttpContent;
+import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonFactory;
-import com.google.api.client.json.JsonObjectParser;
-import com.google.api.client.util.Key;
import com.google.api.client.util.StringUtils;
import com.google.auth.ServiceAccountSigner;
import com.google.auth.oauth2.GoogleCredentials;
@@ -21,9 +19,13 @@
import com.google.common.io.BaseEncoding;
import com.google.common.io.ByteStreams;
import com.google.firebase.FirebaseApp;
-import com.google.firebase.FirebaseOptions;
+import com.google.firebase.FirebaseException;
import com.google.firebase.ImplFirebaseTrampolines;
-import com.google.firebase.internal.FirebaseRequestInitializer;
+import com.google.firebase.auth.FirebaseAuthException;
+import com.google.firebase.internal.AbstractPlatformErrorHandler;
+import com.google.firebase.internal.ApiClientUtils;
+import com.google.firebase.internal.ErrorHandlingHttpClient;
+import com.google.firebase.internal.HttpRequestInfo;
import com.google.firebase.internal.NonNull;
import java.io.IOException;
import java.util.Map;
@@ -34,7 +36,9 @@
public class CryptoSigners {
private static final String METADATA_SERVICE_URL =
- "http://metadata/computeMetadata/v1/instance/service-accounts/default/email";
+ "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email";
+
+ private CryptoSigners() { }
/**
* A {@link CryptoSigner} implementation that uses service account credentials or equivalent
@@ -69,48 +73,33 @@ static class IAMCryptoSigner implements CryptoSigner {
private static final String IAM_SIGN_BLOB_URL =
"https://iam.googleapis.com/v1/projects/-/serviceAccounts/%s:signBlob";
- private final HttpRequestFactory requestFactory;
- private final JsonFactory jsonFactory;
private final String serviceAccount;
- private HttpResponseInterceptor interceptor;
+ private final ErrorHandlingHttpClient httpClient;
IAMCryptoSigner(
@NonNull HttpRequestFactory requestFactory,
@NonNull JsonFactory jsonFactory,
@NonNull String serviceAccount) {
- this.requestFactory = checkNotNull(requestFactory);
- this.jsonFactory = checkNotNull(jsonFactory);
checkArgument(!Strings.isNullOrEmpty(serviceAccount));
this.serviceAccount = serviceAccount;
+ this.httpClient = new ErrorHandlingHttpClient<>(
+ requestFactory,
+ jsonFactory,
+ new IAMErrorHandler(jsonFactory));
}
void setInterceptor(HttpResponseInterceptor interceptor) {
- this.interceptor = interceptor;
+ httpClient.setInterceptor(interceptor);
}
@Override
- public byte[] sign(byte[] payload) throws IOException {
- String encodedUrl = String.format(IAM_SIGN_BLOB_URL, serviceAccount);
- HttpResponse response = null;
+ public byte[] sign(byte[] payload) throws FirebaseAuthException {
String encodedPayload = BaseEncoding.base64().encode(payload);
Map content = ImmutableMap.of("bytesToSign", encodedPayload);
- try {
- HttpRequest request = requestFactory.buildPostRequest(new GenericUrl(encodedUrl),
- new JsonHttpContent(jsonFactory, content));
- request.setParser(new JsonObjectParser(jsonFactory));
- request.setResponseInterceptor(interceptor);
- response = request.execute();
- SignBlobResponse parsed = response.parseAs(SignBlobResponse.class);
- return BaseEncoding.base64().decode(parsed.signature);
- } finally {
- if (response != null) {
- try {
- response.disconnect();
- } catch (IOException ignored) {
- // Ignored
- }
- }
- }
+ String encodedUrl = String.format(IAM_SIGN_BLOB_URL, serviceAccount);
+ HttpRequestInfo requestInfo = HttpRequestInfo.buildJsonPostRequest(encodedUrl, content);
+ GenericJson parsed = httpClient.sendAndParse(requestInfo, GenericJson.class);
+ return BaseEncoding.base64().decode((String) parsed.get("signature"));
}
@Override
@@ -119,9 +108,17 @@ public String getAccount() {
}
}
- public static class SignBlobResponse {
- @Key("signature")
- private String signature;
+ private static class IAMErrorHandler
+ extends AbstractPlatformErrorHandler {
+
+ IAMErrorHandler(JsonFactory jsonFactory) {
+ super(jsonFactory);
+ }
+
+ @Override
+ protected FirebaseAuthException createException(FirebaseException base) {
+ return new FirebaseAuthException(base);
+ }
}
/**
@@ -136,14 +133,12 @@ public static CryptoSigner getCryptoSigner(FirebaseApp firebaseApp) throws IOExc
return new ServiceAccountCryptoSigner((ServiceAccountCredentials) credentials);
}
- FirebaseOptions options = firebaseApp.getOptions();
- HttpRequestFactory requestFactory = options.getHttpTransport().createRequestFactory(
- new FirebaseRequestInitializer(firebaseApp));
- JsonFactory jsonFactory = options.getJsonFactory();
+ HttpRequestFactory requestFactory = ApiClientUtils.newAuthorizedRequestFactory(firebaseApp);
+ JsonFactory jsonFactory = firebaseApp.getOptions().getJsonFactory();
// If the SDK was initialized with a service account email, use it with the IAM service
// to sign bytes.
- String serviceAccountId = options.getServiceAccountId();
+ String serviceAccountId = firebaseApp.getOptions().getServiceAccountId();
if (!Strings.isNullOrEmpty(serviceAccountId)) {
return new IAMCryptoSigner(requestFactory, jsonFactory, serviceAccountId);
}
@@ -156,15 +151,22 @@ public static CryptoSigner getCryptoSigner(FirebaseApp firebaseApp) throws IOExc
// Attempt to discover a service account email from the local Metadata service. Use it
// with the IAM service to sign bytes.
- HttpRequest request = requestFactory.buildGetRequest(new GenericUrl(METADATA_SERVICE_URL));
+ serviceAccountId = discoverServiceAccountId(firebaseApp);
+ return new IAMCryptoSigner(requestFactory, jsonFactory, serviceAccountId);
+ }
+
+ private static String discoverServiceAccountId(FirebaseApp firebaseApp) throws IOException {
+ HttpRequestFactory metadataRequestFactory =
+ ApiClientUtils.newUnauthorizedRequestFactory(firebaseApp);
+ HttpRequest request = metadataRequestFactory.buildGetRequest(
+ new GenericUrl(METADATA_SERVICE_URL));
request.getHeaders().set("Metadata-Flavor", "Google");
HttpResponse response = request.execute();
try {
byte[] output = ByteStreams.toByteArray(response.getContent());
- serviceAccountId = StringUtils.newStringUtf8(output).trim();
- return new IAMCryptoSigner(requestFactory, jsonFactory, serviceAccountId);
+ return StringUtils.newStringUtf8(output).trim();
} finally {
- response.disconnect();
+ ApiClientUtils.disconnectQuietly(response);
}
}
}
diff --git a/src/main/java/com/google/firebase/auth/internal/FirebaseTokenFactory.java b/src/main/java/com/google/firebase/auth/internal/FirebaseTokenFactory.java
index 778911d46..b5aa1e31a 100644
--- a/src/main/java/com/google/firebase/auth/internal/FirebaseTokenFactory.java
+++ b/src/main/java/com/google/firebase/auth/internal/FirebaseTokenFactory.java
@@ -25,7 +25,9 @@
import com.google.api.client.util.Base64;
import com.google.api.client.util.Clock;
import com.google.api.client.util.StringUtils;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
+import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.internal.Nullable;
import java.io.IOException;
@@ -44,10 +46,6 @@ public class FirebaseTokenFactory {
private final CryptoSigner signer;
private final String tenantId;
- public FirebaseTokenFactory(JsonFactory jsonFactory, Clock clock, CryptoSigner signer) {
- this(jsonFactory, clock, signer, null);
- }
-
public FirebaseTokenFactory(
JsonFactory jsonFactory, Clock clock, CryptoSigner signer, @Nullable String tenantId) {
this.jsonFactory = checkNotNull(jsonFactory);
@@ -56,12 +54,17 @@ public FirebaseTokenFactory(
this.tenantId = tenantId;
}
- String createSignedCustomAuthTokenForUser(String uid) throws IOException {
+ @VisibleForTesting
+ FirebaseTokenFactory(JsonFactory jsonFactory, Clock clock, CryptoSigner signer) {
+ this(jsonFactory, clock, signer, null);
+ }
+
+ String createSignedCustomAuthTokenForUser(String uid) throws FirebaseAuthException {
return createSignedCustomAuthTokenForUser(uid, null);
}
public String createSignedCustomAuthTokenForUser(
- String uid, Map developerClaims) throws IOException {
+ String uid, Map developerClaims) throws FirebaseAuthException {
checkArgument(!Strings.isNullOrEmpty(uid), "Uid must be provided.");
checkArgument(uid.length() <= 128, "Uid must be shorter than 128 characters.");
@@ -88,20 +91,33 @@ public String createSignedCustomAuthTokenForUser(
String.format("developerClaims must not contain a reserved key: %s", key));
}
}
+
GenericJson jsonObject = new GenericJson();
jsonObject.putAll(developerClaims);
payload.setDeveloperClaims(jsonObject);
}
+
return signPayload(header, payload);
}
- private String signPayload(JsonWebSignature.Header header,
- FirebaseCustomAuthToken.Payload payload) throws IOException {
- String headerString = Base64.encodeBase64URLSafeString(jsonFactory.toByteArray(header));
- String payloadString = Base64.encodeBase64URLSafeString(jsonFactory.toByteArray(payload));
- String content = headerString + "." + payloadString;
+ private String signPayload(
+ JsonWebSignature.Header header,
+ FirebaseCustomAuthToken.Payload payload) throws FirebaseAuthException {
+ String content = encodePayload(header, payload);
byte[] contentBytes = StringUtils.getBytesUtf8(content);
String signature = Base64.encodeBase64URLSafeString(signer.sign(contentBytes));
return content + "." + signature;
}
+
+ private String encodePayload(
+ JsonWebSignature.Header header, FirebaseCustomAuthToken.Payload payload) {
+ try {
+ String headerString = Base64.encodeBase64URLSafeString(jsonFactory.toByteArray(header));
+ String payloadString = Base64.encodeBase64URLSafeString(jsonFactory.toByteArray(payload));
+ return headerString + "." + payloadString;
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Failed to encode JWT with the given claims: " + e.getMessage(), 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
deleted file mode 100644
index d4be4b4a6..000000000
--- a/src/main/java/com/google/firebase/auth/internal/HttpErrorResponse.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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;
- }
- }
-
-}
diff --git a/src/main/java/com/google/firebase/auth/multitenancy/FirebaseTenantClient.java b/src/main/java/com/google/firebase/auth/multitenancy/FirebaseTenantClient.java
index 1278e63d5..5b776a49a 100644
--- a/src/main/java/com/google/firebase/auth/multitenancy/FirebaseTenantClient.java
+++ b/src/main/java/com/google/firebase/auth/multitenancy/FirebaseTenantClient.java
@@ -19,7 +19,6 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
-import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponseInterceptor;
import com.google.api.client.json.GenericJson;
@@ -33,6 +32,7 @@
import com.google.firebase.auth.internal.AuthHttpClient;
import com.google.firebase.auth.internal.ListTenantsResponse;
import com.google.firebase.internal.ApiClientUtils;
+import com.google.firebase.internal.HttpRequestInfo;
import java.util.Map;
final class FirebaseTenantClient {
@@ -46,15 +46,19 @@ final class FirebaseTenantClient {
private final AuthHttpClient httpClient;
FirebaseTenantClient(FirebaseApp app) {
- checkNotNull(app, "FirebaseApp must not be null");
- String projectId = ImplFirebaseTrampolines.getProjectId(app);
+ this(
+ ImplFirebaseTrampolines.getProjectId(checkNotNull(app)),
+ app.getOptions().getJsonFactory(),
+ ApiClientUtils.newAuthorizedRequestFactory(app));
+ }
+
+ FirebaseTenantClient(
+ String projectId, JsonFactory jsonFactory, HttpRequestFactory requestFactory) {
checkArgument(!Strings.isNullOrEmpty(projectId),
"Project ID is required to access the auth service. Use a service account credential or "
+ "set the project ID explicitly via FirebaseOptions. Alternatively you can also "
+ "set the project ID via the GOOGLE_CLOUD_PROJECT environment variable.");
this.tenantMgtBaseUrl = String.format(ID_TOOLKIT_URL, "v2", projectId);
- JsonFactory jsonFactory = app.getOptions().getJsonFactory();
- HttpRequestFactory requestFactory = ApiClientUtils.newAuthorizedRequestFactory(app);
this.httpClient = new AuthHttpClient(jsonFactory, requestFactory);
}
@@ -63,25 +67,28 @@ void setInterceptor(HttpResponseInterceptor interceptor) {
}
Tenant getTenant(String tenantId) throws FirebaseAuthException {
- GenericUrl url = new GenericUrl(tenantMgtBaseUrl + getTenantUrlSuffix(tenantId));
- return httpClient.sendRequest("GET", url, null, Tenant.class);
+ String url = tenantMgtBaseUrl + getTenantUrlSuffix(tenantId);
+ return httpClient.sendRequest(HttpRequestInfo.buildGetRequest(url), Tenant.class);
}
Tenant createTenant(Tenant.CreateRequest request) throws FirebaseAuthException {
- GenericUrl url = new GenericUrl(tenantMgtBaseUrl + "/tenants");
- return httpClient.sendRequest("POST", url, request.getProperties(), Tenant.class);
+ String url = tenantMgtBaseUrl + "/tenants";
+ return httpClient.sendRequest(
+ HttpRequestInfo.buildJsonPostRequest(url, request.getProperties()),
+ Tenant.class);
}
Tenant updateTenant(Tenant.UpdateRequest request) throws FirebaseAuthException {
Map properties = request.getProperties();
- GenericUrl url = new GenericUrl(tenantMgtBaseUrl + getTenantUrlSuffix(request.getTenantId()));
- url.put("updateMask", Joiner.on(",").join(AuthHttpClient.generateMask(properties)));
- return httpClient.sendRequest("PATCH", url, properties, Tenant.class);
+ String url = tenantMgtBaseUrl + getTenantUrlSuffix(request.getTenantId());
+ HttpRequestInfo requestInfo = HttpRequestInfo.buildJsonPatchRequest(url, properties)
+ .addParameter("updateMask", Joiner.on(",").join(AuthHttpClient.generateMask(properties)));
+ return httpClient.sendRequest(requestInfo, Tenant.class);
}
void deleteTenant(String tenantId) throws FirebaseAuthException {
- GenericUrl url = new GenericUrl(tenantMgtBaseUrl + getTenantUrlSuffix(tenantId));
- httpClient.sendRequest("DELETE", url, null, GenericJson.class);
+ String url = tenantMgtBaseUrl + getTenantUrlSuffix(tenantId);
+ httpClient.sendRequest(HttpRequestInfo.buildDeleteRequest(url), GenericJson.class);
}
ListTenantsResponse listTenants(int maxResults, String pageToken)
@@ -94,14 +101,9 @@ ListTenantsResponse listTenants(int maxResults, String pageToken)
builder.put("pageToken", pageToken);
}
- GenericUrl url = new GenericUrl(tenantMgtBaseUrl + "/tenants");
- url.putAll(builder.build());
- ListTenantsResponse response = httpClient.sendRequest(
- "GET", url, null, ListTenantsResponse.class);
- if (response == null) {
- throw new FirebaseAuthException(AuthHttpClient.INTERNAL_ERROR, "Failed to retrieve tenants.");
- }
- return response;
+ HttpRequestInfo requestInfo = HttpRequestInfo.buildGetRequest(tenantMgtBaseUrl + "/tenants")
+ .addAllParameters(builder.build());
+ return httpClient.sendRequest(requestInfo, ListTenantsResponse.class);
}
private static String getTenantUrlSuffix(String tenantId) {
diff --git a/src/main/java/com/google/firebase/auth/multitenancy/ListTenantsPage.java b/src/main/java/com/google/firebase/auth/multitenancy/ListTenantsPage.java
index c1f393ddb..5f9917bce 100644
--- a/src/main/java/com/google/firebase/auth/multitenancy/ListTenantsPage.java
+++ b/src/main/java/com/google/firebase/auth/multitenancy/ListTenantsPage.java
@@ -96,14 +96,14 @@ public ListTenantsPage getNextPage() {
}
/**
- * Returns an {@link Iterable} that facilitates transparently iterating over all the tenants in
+ * Returns an {@code Iterable} that facilitates transparently iterating over all the tenants in
* the current Firebase project, starting from this page.
*
- * The {@link Iterator} instances produced by the returned {@link Iterable} never buffers more
+ *
The {@code Iterator} instances produced by the returned {@code Iterable} never buffers more
* than one page of tenants at a time. It is safe to abandon the iterators (i.e. break the loops)
* at any time.
*
- * @return a new {@link Iterable} instance.
+ * @return a new {@code Iterable} instance.
*/
@NonNull
@Override
@@ -112,9 +112,9 @@ public Iterable iterateAll() {
}
/**
- * Returns an {@link Iterable} over the tenants in this page.
+ * Returns an {@code Iterable} over the tenants in this page.
*
- * @return a {@link Iterable} instance.
+ * @return a {@code Iterable} instance.
*/
@NonNull
@Override
@@ -137,7 +137,7 @@ public Iterator iterator() {
}
/**
- * An {@link Iterator} that cycles through tenants, one at a time.
+ * An {@code Iterator} that cycles through tenants, one at a time.
*
* It buffers the last retrieved batch of tenants in memory. The {@code maxResults} parameter
* is an upper bound on the batch size.
diff --git a/src/main/java/com/google/firebase/auth/multitenancy/TenantManager.java b/src/main/java/com/google/firebase/auth/multitenancy/TenantManager.java
index dcb226d28..a30c0b884 100644
--- a/src/main/java/com/google/firebase/auth/multitenancy/TenantManager.java
+++ b/src/main/java/com/google/firebase/auth/multitenancy/TenantManager.java
@@ -54,8 +54,13 @@ public final class TenantManager {
* @hide
*/
public TenantManager(FirebaseApp firebaseApp) {
- this.firebaseApp = firebaseApp;
- this.tenantClient = new FirebaseTenantClient(firebaseApp);
+ this(firebaseApp, new FirebaseTenantClient(firebaseApp));
+ }
+
+ @VisibleForTesting
+ TenantManager(FirebaseApp firebaseApp, FirebaseTenantClient tenantClient) {
+ this.firebaseApp = checkNotNull(firebaseApp);
+ this.tenantClient = checkNotNull(tenantClient);
this.tenantAwareAuths = new HashMap<>();
}
diff --git a/src/main/java/com/google/firebase/database/core/JvmAuthTokenProvider.java b/src/main/java/com/google/firebase/database/core/JvmAuthTokenProvider.java
index a1cb79688..9b862ba86 100644
--- a/src/main/java/com/google/firebase/database/core/JvmAuthTokenProvider.java
+++ b/src/main/java/com/google/firebase/database/core/JvmAuthTokenProvider.java
@@ -112,7 +112,7 @@ private static class TokenChangeListenerWrapper implements CredentialsChangedLis
}
@Override
- public void onChanged(OAuth2Credentials credentials) throws IOException {
+ public void onChanged(OAuth2Credentials credentials) {
// When this event fires, it is guaranteed that credentials.getAccessToken() will return a
// valid OAuth2 token.
final AccessToken accessToken = credentials.getAccessToken();
diff --git a/src/main/java/com/google/firebase/iid/FirebaseInstanceId.java b/src/main/java/com/google/firebase/iid/FirebaseInstanceId.java
index 7ac527778..822654402 100644
--- a/src/main/java/com/google/firebase/iid/FirebaseInstanceId.java
+++ b/src/main/java/com/google/firebase/iid/FirebaseInstanceId.java
@@ -17,29 +17,27 @@
package com.google.firebase.iid;
import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
-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.json.JsonFactory;
-import com.google.api.client.json.JsonObjectParser;
import com.google.api.core.ApiFuture;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
-import com.google.common.io.ByteStreams;
import com.google.firebase.FirebaseApp;
+import com.google.firebase.FirebaseException;
import com.google.firebase.ImplFirebaseTrampolines;
+import com.google.firebase.IncomingHttpResponse;
+import com.google.firebase.database.annotations.Nullable;
+import com.google.firebase.internal.AbstractHttpErrorHandler;
+import com.google.firebase.internal.ApiClientUtils;
import com.google.firebase.internal.CallableOperation;
-import com.google.firebase.internal.FirebaseRequestInitializer;
+import com.google.firebase.internal.ErrorHandlingHttpClient;
import com.google.firebase.internal.FirebaseService;
+import com.google.firebase.internal.HttpRequestInfo;
import com.google.firebase.internal.NonNull;
-import java.io.IOException;
import java.util.Map;
/**
@@ -64,22 +62,30 @@ public class FirebaseInstanceId {
.build();
private final FirebaseApp app;
- private final HttpRequestFactory requestFactory;
- private final JsonFactory jsonFactory;
private final String projectId;
-
- private HttpResponseInterceptor interceptor;
+ private final ErrorHandlingHttpClient httpClient;
private FirebaseInstanceId(FirebaseApp app) {
- HttpTransport httpTransport = app.getOptions().getHttpTransport();
- this.app = app;
- this.requestFactory = httpTransport.createRequestFactory(new FirebaseRequestInitializer(app));
- this.jsonFactory = app.getOptions().getJsonFactory();
- this.projectId = ImplFirebaseTrampolines.getProjectId(app);
+ this(app, null);
+ }
+
+ @VisibleForTesting
+ FirebaseInstanceId(FirebaseApp app, @Nullable HttpRequestFactory requestFactory) {
+ this.app = checkNotNull(app, "app must not be null");
+ String projectId = ImplFirebaseTrampolines.getProjectId(app);
checkArgument(!Strings.isNullOrEmpty(projectId),
"Project ID is required to access instance ID service. Use a service account credential or "
+ "set the project ID explicitly via FirebaseOptions. Alternatively you can also "
+ "set the project ID via the GOOGLE_CLOUD_PROJECT environment variable.");
+ this.projectId = projectId;
+ if (requestFactory == null) {
+ requestFactory = ApiClientUtils.newAuthorizedRequestFactory(app);
+ }
+
+ this.httpClient = new ErrorHandlingHttpClient<>(
+ requestFactory,
+ app.getOptions().getJsonFactory(),
+ new InstanceIdErrorHandler());
}
/**
@@ -107,7 +113,7 @@ public static synchronized FirebaseInstanceId getInstance(FirebaseApp app) {
@VisibleForTesting
void setInterceptor(HttpResponseInterceptor interceptor) {
- this.interceptor = interceptor;
+ httpClient.setInterceptor(interceptor);
}
/**
@@ -146,42 +152,45 @@ private CallableOperation deleteInstanceIdOp(
protected Void execute() throws FirebaseInstanceIdException {
String url = String.format(
"%s/project/%s/instanceId/%s", IID_SERVICE_URL, projectId, instanceId);
- HttpResponse response = null;
- try {
- HttpRequest request = requestFactory.buildDeleteRequest(new GenericUrl(url));
- request.setParser(new JsonObjectParser(jsonFactory));
- request.setResponseInterceptor(interceptor);
- response = request.execute();
- ByteStreams.exhaust(response.getContent());
- } catch (Exception e) {
- handleError(instanceId, e);
- } finally {
- disconnectQuietly(response);
- }
+ HttpRequestInfo request = HttpRequestInfo.buildDeleteRequest(url);
+ httpClient.send(request);
return null;
}
};
}
- private static void disconnectQuietly(HttpResponse response) {
- if (response != null) {
- try {
- response.disconnect();
- } catch (IOException ignored) {
- // ignored
+ private static class InstanceIdErrorHandler
+ extends AbstractHttpErrorHandler {
+
+ @Override
+ protected FirebaseInstanceIdException createException(FirebaseException base) {
+ String message = base.getMessage();
+ String customMessage = getCustomMessage(base);
+ if (!Strings.isNullOrEmpty(customMessage)) {
+ message = customMessage;
}
+
+ return new FirebaseInstanceIdException(base, message);
}
- }
- private void handleError(String instanceId, Exception e) throws FirebaseInstanceIdException {
- String msg = "Error while invoking instance ID service.";
- if (e instanceof HttpResponseException) {
- int statusCode = ((HttpResponseException) e).getStatusCode();
- if (ERROR_CODES.containsKey(statusCode)) {
- msg = String.format("Instance ID \"%s\": %s", instanceId, ERROR_CODES.get(statusCode));
+ private String getCustomMessage(FirebaseException base) {
+ IncomingHttpResponse response = base.getHttpResponse();
+ if (response != null) {
+ String instanceId = extractInstanceId(response);
+ String description = ERROR_CODES.get(response.getStatusCode());
+ if (description != null) {
+ return String.format("Instance ID \"%s\": %s", instanceId, description);
+ }
}
+
+ return null;
+ }
+
+ private String extractInstanceId(IncomingHttpResponse response) {
+ String url = response.getRequest().getUrl();
+ int index = url.lastIndexOf('/');
+ return url.substring(index + 1);
}
- throw new FirebaseInstanceIdException(msg, e);
}
private static final String SERVICE_ID = FirebaseInstanceId.class.getName();
diff --git a/src/main/java/com/google/firebase/iid/FirebaseInstanceIdException.java b/src/main/java/com/google/firebase/iid/FirebaseInstanceIdException.java
index dfe0087fc..482a23a3d 100644
--- a/src/main/java/com/google/firebase/iid/FirebaseInstanceIdException.java
+++ b/src/main/java/com/google/firebase/iid/FirebaseInstanceIdException.java
@@ -21,9 +21,9 @@
/**
* Represents an exception encountered while interacting with the Firebase instance ID service.
*/
-public class FirebaseInstanceIdException extends FirebaseException {
+public final class FirebaseInstanceIdException extends FirebaseException {
- FirebaseInstanceIdException(String detailMessage, Throwable cause) {
- super(detailMessage, cause);
+ FirebaseInstanceIdException(FirebaseException base, String message) {
+ super(base.getErrorCode(), message, base.getCause(), base.getHttpResponse());
}
}
diff --git a/src/main/java/com/google/firebase/internal/AbstractHttpErrorHandler.java b/src/main/java/com/google/firebase/internal/AbstractHttpErrorHandler.java
new file mode 100644
index 000000000..4d1f6b26e
--- /dev/null
+++ b/src/main/java/com/google/firebase/internal/AbstractHttpErrorHandler.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2020 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.internal;
+
+import com.google.api.client.http.HttpResponseException;
+import com.google.api.client.http.HttpStatusCodes;
+import com.google.common.collect.ImmutableMap;
+import com.google.firebase.ErrorCode;
+import com.google.firebase.FirebaseException;
+import com.google.firebase.IncomingHttpResponse;
+import java.io.IOException;
+import java.net.NoRouteToHostException;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An abstract HttpErrorHandler implementation that maps HTTP status codes to Firebase error codes.
+ * Also provides reasonable default implementations to other error handler methods in the
+ * HttpErrorHandler interface.
+ */
+public abstract class AbstractHttpErrorHandler
+ implements HttpErrorHandler {
+
+ private static final Map HTTP_ERROR_CODES =
+ ImmutableMap.builder()
+ .put(HttpStatusCodes.STATUS_CODE_BAD_REQUEST, ErrorCode.INVALID_ARGUMENT)
+ .put(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED, ErrorCode.UNAUTHENTICATED)
+ .put(HttpStatusCodes.STATUS_CODE_FORBIDDEN, ErrorCode.PERMISSION_DENIED)
+ .put(HttpStatusCodes.STATUS_CODE_NOT_FOUND, ErrorCode.NOT_FOUND)
+ .put(HttpStatusCodes.STATUS_CODE_CONFLICT, ErrorCode.CONFLICT)
+ .put(429, ErrorCode.RESOURCE_EXHAUSTED)
+ .put(HttpStatusCodes.STATUS_CODE_SERVER_ERROR, ErrorCode.INTERNAL)
+ .put(HttpStatusCodes.STATUS_CODE_SERVICE_UNAVAILABLE, ErrorCode.UNAVAILABLE)
+ .build();
+
+ @Override
+ public final T handleHttpResponseException(
+ HttpResponseException e, IncomingHttpResponse response) {
+ FirebaseException base = this.httpResponseErrorToBaseException(e, response);
+ return this.createException(base);
+ }
+
+ @Override
+ public final T handleIOException(IOException e) {
+ FirebaseException base = this.ioErrorToBaseException(e);
+ return this.createException(base);
+ }
+
+ @Override
+ public final T handleParseException(IOException e, IncomingHttpResponse response) {
+ FirebaseException base = this.parseErrorToBaseException(e, response);
+ return this.createException(base);
+ }
+
+ /**
+ * Creates a FirebaseException from the given HTTP response error. Error code is determined from
+ * the HTTP status code of the response. Error message includes both the status code and full
+ * response payload to aid in debugging.
+ *
+ * @param e HTTP response exception.
+ * @param response Incoming HTTP response.
+ * @return A FirebaseException instance.
+ */
+ protected FirebaseException httpResponseErrorToBaseException(
+ HttpResponseException e, IncomingHttpResponse response) {
+ ErrorCode code = HTTP_ERROR_CODES.get(e.getStatusCode());
+ if (code == null) {
+ code = ErrorCode.UNKNOWN;
+ }
+
+ String message = String.format("Unexpected HTTP response with status: %d\n%s",
+ e.getStatusCode(), e.getContent());
+ return new FirebaseException(code, message, e, response);
+ }
+
+ /**
+ * Creates a FirebaseException from the given IOException. If IOException resulted from a socket
+ * timeout, sets the error code DEADLINE_EXCEEDED. If the IOException resulted from a network
+ * outage or other connectivity issue, sets the error code to UNAVAILABLE. In all other cases sets
+ * the error code to UNKNOWN.
+ *
+ * @param e IOException to create the new exception from.
+ * @return A FirebaseException instance.
+ */
+ protected FirebaseException ioErrorToBaseException(IOException e) {
+ ErrorCode code = ErrorCode.UNKNOWN;
+ String message = "Unknown error while making a remote service call" ;
+ if (isInstance(e, SocketTimeoutException.class)) {
+ code = ErrorCode.DEADLINE_EXCEEDED;
+ message = "Timed out while making an API call";
+ }
+
+ if (isInstance(e, UnknownHostException.class) || isInstance(e, NoRouteToHostException.class)) {
+ code = ErrorCode.UNAVAILABLE;
+ message = "Failed to establish a connection";
+ }
+
+ return new FirebaseException(code, message + ": " + e.getMessage(), e);
+ }
+
+ protected FirebaseException parseErrorToBaseException(
+ IOException e, IncomingHttpResponse response) {
+ return new FirebaseException(
+ ErrorCode.UNKNOWN, "Error while parsing HTTP response: " + e.getMessage(), e, response);
+ }
+
+ /**
+ * Converts the given base FirebaseException to a more specific exception type. The base exception
+ * is guaranteed to have an error code, a message and a cause. But the HTTP response is only set
+ * if the exception occurred after receiving a response from a remote server.
+ *
+ * @param base A FirebaseException.
+ * @return A more specific exception created from the base.
+ */
+ protected abstract T createException(FirebaseException base);
+
+ /**
+ * Checks if the given exception stack t contains an instance of type.
+ */
+ private boolean isInstance(IOException t, Class type) {
+ Throwable current = t;
+ Set chain = new HashSet<>();
+ while (current != null) {
+ if (!chain.add(current)) {
+ break;
+ }
+
+ if (type.isInstance(current)) {
+ return true;
+ }
+
+ current = current.getCause();
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/com/google/firebase/internal/AbstractPlatformErrorHandler.java b/src/main/java/com/google/firebase/internal/AbstractPlatformErrorHandler.java
new file mode 100644
index 000000000..b0909e4f7
--- /dev/null
+++ b/src/main/java/com/google/firebase/internal/AbstractPlatformErrorHandler.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2020 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.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.api.client.http.HttpResponseException;
+import com.google.api.client.json.JsonFactory;
+import com.google.api.client.util.Key;
+import com.google.common.base.Strings;
+import com.google.firebase.ErrorCode;
+import com.google.firebase.FirebaseException;
+import com.google.firebase.IncomingHttpResponse;
+import java.io.IOException;
+
+/**
+ * An abstract HttpErrorHandler that handles Google Cloud error responses. Format of these
+ * error responses are defined at https://cloud.google.com/apis/design/errors.
+ */
+public abstract class AbstractPlatformErrorHandler
+ extends AbstractHttpErrorHandler {
+
+ protected final JsonFactory jsonFactory;
+
+ public AbstractPlatformErrorHandler(JsonFactory jsonFactory) {
+ this.jsonFactory = checkNotNull(jsonFactory, "jsonFactory must not be null");
+ }
+
+ @Override
+ protected final FirebaseException httpResponseErrorToBaseException(
+ HttpResponseException e, IncomingHttpResponse response) {
+ FirebaseException base = super.httpResponseErrorToBaseException(e, response);
+ PlatformErrorResponse parsedError = this.parseErrorResponse(e.getContent());
+
+ ErrorCode code = base.getErrorCode();
+ String status = parsedError.getStatus();
+ if (!Strings.isNullOrEmpty(status)) {
+ code = Enum.valueOf(ErrorCode.class, parsedError.getStatus());
+ }
+
+ String message = parsedError.getMessage();
+ if (Strings.isNullOrEmpty(message)) {
+ message = base.getMessage();
+ }
+
+ return new FirebaseException(code, message, e, response);
+ }
+
+ private PlatformErrorResponse parseErrorResponse(String content) {
+ PlatformErrorResponse response = new PlatformErrorResponse();
+ if (!Strings.isNullOrEmpty(content)) {
+ try {
+ jsonFactory.createJsonParser(content).parseAndClose(response);
+ } catch (IOException e) {
+ // Ignore any error that may occur while parsing the error response. The server
+ // may have responded with a non-json payload. Return an empty return value, and
+ // let the base class logic come into play.
+ }
+ }
+
+ return response;
+ }
+
+ public static class PlatformErrorResponse {
+ @Key("error")
+ private PlatformError error;
+
+ String getStatus() {
+ return error != null ? error.status : null;
+ }
+
+ String getMessage() {
+ return error != null ? error.message : null;
+ }
+ }
+
+ public static class PlatformError {
+ @Key("status")
+ private String status;
+
+ @Key("message")
+ private String message;
+ }
+}
diff --git a/src/main/java/com/google/firebase/internal/ApiClientUtils.java b/src/main/java/com/google/firebase/internal/ApiClientUtils.java
index 36ccf5cc8..f2724196b 100644
--- a/src/main/java/com/google/firebase/internal/ApiClientUtils.java
+++ b/src/main/java/com/google/firebase/internal/ApiClientUtils.java
@@ -35,6 +35,8 @@ public class ApiClientUtils {
.setMaxIntervalMillis(60 * 1000)
.build();
+ private ApiClientUtils() { }
+
/**
* Creates a new {@code HttpRequestFactory} which provides authorization (OAuth2), timeouts and
* automatic retries.
diff --git a/src/main/java/com/google/firebase/internal/ErrorHandlingHttpClient.java b/src/main/java/com/google/firebase/internal/ErrorHandlingHttpClient.java
new file mode 100644
index 000000000..5efdd0ec2
--- /dev/null
+++ b/src/main/java/com/google/firebase/internal/ErrorHandlingHttpClient.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2020 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.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+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.json.JsonFactory;
+import com.google.api.client.json.JsonParser;
+import com.google.common.io.CharStreams;
+import com.google.firebase.FirebaseException;
+import com.google.firebase.IncomingHttpResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+/**
+ * An HTTP client implementation that handles any errors that may occur during HTTP calls, and
+ * converts them into an instance of FirebaseException.
+ */
+public final class ErrorHandlingHttpClient {
+
+ private final HttpRequestFactory requestFactory;
+ private final JsonFactory jsonFactory;
+ private final HttpErrorHandler errorHandler;
+
+ private HttpResponseInterceptor interceptor;
+
+ public ErrorHandlingHttpClient(
+ HttpRequestFactory requestFactory,
+ JsonFactory jsonFactory,
+ HttpErrorHandler errorHandler) {
+ this.requestFactory = checkNotNull(requestFactory, "requestFactory must not be null");
+ this.jsonFactory = checkNotNull(jsonFactory, "jsonFactory must not be null");
+ this.errorHandler = checkNotNull(errorHandler, "errorHandler must not be null");
+ }
+
+ public ErrorHandlingHttpClient setInterceptor(HttpResponseInterceptor interceptor) {
+ this.interceptor = interceptor;
+ return this;
+ }
+
+ /**
+ * Sends the given HTTP request to the target endpoint, and parses the response while handling
+ * any errors that may occur along the way.
+ *
+ * @param requestInfo Outgoing request configuration.
+ * @param responseType Class to parse the response into.
+ * @param Parsed response type.
+ * @return Parsed response object.
+ * @throws T If any error occurs while making the request.
+ */
+ public V sendAndParse(HttpRequestInfo requestInfo, Class responseType) throws T {
+ IncomingHttpResponse response = send(requestInfo);
+ return parse(response, responseType);
+ }
+
+ /**
+ * Sends the given HTTP request to the target endpoint, and parses the response while handling
+ * any errors that may occur along the way. This method can be used when the response should
+ * be parsed into an instance of a private or protected class, which cannot be instantiated
+ * outside the call-site.
+ *
+ * @param requestInfo Outgoing request configuration.
+ * @param destination Object to parse the response into.
+ * @throws T If any error occurs while making the request.
+ */
+ public void sendAndParse(HttpRequestInfo requestInfo, Object destination) throws T {
+ IncomingHttpResponse response = send(requestInfo);
+ parse(response, destination);
+ }
+
+ public IncomingHttpResponse send(HttpRequestInfo requestInfo) throws T {
+ HttpRequest request = createHttpRequest(requestInfo);
+
+ HttpResponse response = null;
+ try {
+ response = request.execute();
+ // Read and buffer the content. Otherwise if a parse error occurs later,
+ // we lose the content stream.
+ String content = null;
+ InputStream stream = response.getContent();
+ if (stream != null) {
+ // Stream is null when the response body is empty (e.g. 204 No Content responses).
+ content = CharStreams.toString(new InputStreamReader(stream, response.getContentCharset()));
+ }
+
+ return new IncomingHttpResponse(response, content);
+ } catch (HttpResponseException e) {
+ throw errorHandler.handleHttpResponseException(e, new IncomingHttpResponse(e, request));
+ } catch (IOException e) {
+ throw errorHandler.handleIOException(e);
+ } finally {
+ ApiClientUtils.disconnectQuietly(response);
+ }
+ }
+
+ public V parse(IncomingHttpResponse response, Class responseType) throws T {
+ checkNotNull(responseType, "responseType must not be null");
+ try {
+ JsonParser parser = jsonFactory.createJsonParser(response.getContent());
+ return parser.parseAndClose(responseType);
+ } catch (IOException e) {
+ throw errorHandler.handleParseException(e, response);
+ }
+ }
+
+ public void parse(IncomingHttpResponse response, Object destination) throws T {
+ try {
+ JsonParser parser = jsonFactory.createJsonParser(response.getContent());
+ parser.parse(destination);
+ } catch (IOException e) {
+ throw errorHandler.handleParseException(e, response);
+ }
+ }
+
+ private HttpRequest createHttpRequest(HttpRequestInfo requestInfo) throws T {
+ try {
+ return requestInfo.newHttpRequest(requestFactory, jsonFactory)
+ .setResponseInterceptor(interceptor);
+ } catch (IOException e) {
+ // Handle request initialization errors (credential loading and other config errors)
+ throw errorHandler.handleIOException(e);
+ }
+ }
+}
diff --git a/src/main/java/com/google/firebase/internal/FirebaseAppStore.java b/src/main/java/com/google/firebase/internal/FirebaseAppStore.java
deleted file mode 100644
index 778295655..000000000
--- a/src/main/java/com/google/firebase/internal/FirebaseAppStore.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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.internal;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.firebase.FirebaseApp;
-import com.google.firebase.FirebaseOptions;
-
-import java.util.Collections;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicReference;
-
-/** No-op base class of FirebaseAppStore. */
-public class FirebaseAppStore {
-
- private static final AtomicReference sInstance = new AtomicReference<>();
-
- FirebaseAppStore() {}
-
- @Nullable
- public static FirebaseAppStore getInstance() {
- return sInstance.get();
- }
-
- // TODO: reenable persistence. See b/28158809.
- public static FirebaseAppStore initialize() {
- sInstance.compareAndSet(null /* expected */, new FirebaseAppStore());
- return sInstance.get();
- }
-
- /**
- * @hide
- */
- public static void setInstanceForTest(FirebaseAppStore firebaseAppStore) {
- sInstance.set(firebaseAppStore);
- }
-
- @VisibleForTesting
- public static void clearInstanceForTest() {
- FirebaseAppStore instance = sInstance.get();
- if (instance != null) {
- instance.resetStore();
- }
- sInstance.set(null);
- }
-
- /** The returned set is mutable. */
- public Set getAllPersistedAppNames() {
- return Collections.emptySet();
- }
-
- public void persistApp(@NonNull FirebaseApp app) {}
-
- public void removeApp(@NonNull String name) {}
-
- /**
- * @return The restored {@link FirebaseOptions}, or null if it doesn't exist.
- */
- public FirebaseOptions restoreAppOptions(@NonNull String name) {
- return null;
- }
-
- protected void resetStore() {}
-}
diff --git a/src/main/java/com/google/firebase/internal/HttpErrorHandler.java b/src/main/java/com/google/firebase/internal/HttpErrorHandler.java
new file mode 100644
index 000000000..988b45a45
--- /dev/null
+++ b/src/main/java/com/google/firebase/internal/HttpErrorHandler.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2020 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.internal;
+
+import com.google.api.client.http.HttpResponseException;
+import com.google.firebase.FirebaseException;
+import com.google.firebase.IncomingHttpResponse;
+import java.io.IOException;
+
+/**
+ * An interface for handling all sorts of exceptions that may occur while making an HTTP call and
+ * converting them into some instance of FirebaseException.
+ */
+public interface HttpErrorHandler {
+
+ /**
+ * Handle any low-level transport and initialization errors.
+ */
+ T handleIOException(IOException e);
+
+ /**
+ * Handle HTTP response exceptions (caused by HTTP error responses).
+ */
+ T handleHttpResponseException(HttpResponseException e, IncomingHttpResponse response);
+
+ /**
+ * Handle any errors that may occur while parsing the response payload.
+ */
+ T handleParseException(IOException e, IncomingHttpResponse response);
+}
diff --git a/src/main/java/com/google/firebase/internal/HttpRequestInfo.java b/src/main/java/com/google/firebase/internal/HttpRequestInfo.java
new file mode 100644
index 000000000..375e332fb
--- /dev/null
+++ b/src/main/java/com/google/firebase/internal/HttpRequestInfo.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2020 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.internal;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.api.client.http.GenericUrl;
+import com.google.api.client.http.HttpContent;
+import com.google.api.client.http.HttpMethods;
+import com.google.api.client.http.HttpRequest;
+import com.google.api.client.http.HttpRequestFactory;
+import com.google.api.client.http.json.JsonHttpContent;
+import com.google.api.client.json.JsonFactory;
+import com.google.common.base.Strings;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Internal API for configuring outgoing HTTP requests. To be used with the
+ * {@link ErrorHandlingHttpClient} class.
+ */
+public final class HttpRequestInfo {
+
+ private final String method;
+ private final GenericUrl url;
+ private final HttpContent content;
+ private final Object jsonContent;
+ private final Map headers = new HashMap<>();
+
+ private HttpRequestInfo(String method, GenericUrl url, HttpContent content, Object jsonContent) {
+ checkArgument(!Strings.isNullOrEmpty(method), "method must not be null");
+ this.method = method;
+ this.url = checkNotNull(url, "url must not be null");
+ this.content = content;
+ this.jsonContent = jsonContent;
+ }
+
+ public HttpRequestInfo addHeader(String name, String value) {
+ this.headers.put(name, value);
+ return this;
+ }
+
+ public HttpRequestInfo addAllHeaders(Map headers) {
+ this.headers.putAll(headers);
+ return this;
+ }
+
+ public HttpRequestInfo addParameter(String name, Object value) {
+ this.url.put(name, value);
+ return this;
+ }
+
+ public HttpRequestInfo addAllParameters(Map params) {
+ this.url.putAll(params);
+ return this;
+ }
+
+ public static HttpRequestInfo buildGetRequest(String url) {
+ return buildRequest(HttpMethods.GET, url, null);
+ }
+
+ public static HttpRequestInfo buildDeleteRequest(String url) {
+ return buildRequest(HttpMethods.DELETE, url, null);
+ }
+
+ public static HttpRequestInfo buildRequest(
+ String method, String url, @Nullable HttpContent content) {
+ return new HttpRequestInfo(method, new GenericUrl(url), content, null);
+ }
+
+ public static HttpRequestInfo buildJsonPostRequest(String url, @Nullable Object content) {
+ return buildJsonRequest(HttpMethods.POST, url, content);
+ }
+
+ public static HttpRequestInfo buildJsonPatchRequest(String url, @Nullable Object content) {
+ return buildJsonRequest(HttpMethods.PATCH, url, content);
+ }
+
+ public static HttpRequestInfo buildJsonRequest(
+ String method, String url, @Nullable Object content) {
+ return new HttpRequestInfo(method, new GenericUrl(url), null, content);
+ }
+
+ HttpRequest newHttpRequest(
+ HttpRequestFactory factory, JsonFactory jsonFactory) throws IOException {
+ HttpRequest request;
+ HttpContent httpContent = getContent(jsonFactory);
+ if (factory.getTransport().supportsMethod(method)) {
+ request = factory.buildRequest(method, url, httpContent);
+ } else {
+ // Some HttpTransport implementations (notably NetHttpTransport) don't support new methods
+ // like PATCH. We try to emulate such requests over POST by setting the method override
+ // header, which is recognized by most Google backend APIs.
+ request = factory.buildPostRequest(url, httpContent);
+ request.getHeaders().set("X-HTTP-Method-Override", method);
+ }
+
+ for (Map.Entry entry : headers.entrySet()) {
+ request.getHeaders().set(entry.getKey(), entry.getValue());
+ }
+
+ return request;
+ }
+
+ private HttpContent getContent(JsonFactory jsonFactory) {
+ if (content != null) {
+ return content;
+ }
+
+ if (jsonContent != null) {
+ return new JsonHttpContent(jsonFactory, jsonContent);
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java b/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java
index ee25957b3..e1b4f794c 100644
--- a/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java
+++ b/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java
@@ -41,10 +41,6 @@
*/
public class FirebaseMessaging {
- static final String INTERNAL_ERROR = "internal-error";
-
- static final String UNKNOWN_ERROR = "unknown-error";
-
private final FirebaseApp app;
private final Supplier extends FirebaseMessagingClient> messagingClient;
private final Supplier extends InstanceIdClient> instanceIdClient;
diff --git a/src/main/java/com/google/firebase/messaging/FirebaseMessagingClientImpl.java b/src/main/java/com/google/firebase/messaging/FirebaseMessagingClientImpl.java
index 53d5ab00b..43b9b340b 100644
--- a/src/main/java/com/google/firebase/messaging/FirebaseMessagingClientImpl.java
+++ b/src/main/java/com/google/firebase/messaging/FirebaseMessagingClientImpl.java
@@ -21,26 +21,33 @@
import com.google.api.client.googleapis.batch.BatchCallback;
import com.google.api.client.googleapis.batch.BatchRequest;
+import com.google.api.client.googleapis.services.json.AbstractGoogleJsonClient;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpHeaders;
+import com.google.api.client.http.HttpMethods;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpRequestInitializer;
-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;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.JsonObjectParser;
-import com.google.api.client.json.JsonParser;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.firebase.ErrorCode;
import com.google.firebase.FirebaseApp;
+import com.google.firebase.FirebaseException;
import com.google.firebase.ImplFirebaseTrampolines;
+import com.google.firebase.IncomingHttpResponse;
+import com.google.firebase.OutgoingHttpRequest;
+import com.google.firebase.internal.AbstractPlatformErrorHandler;
import com.google.firebase.internal.ApiClientUtils;
-import com.google.firebase.internal.Nullable;
+import com.google.firebase.internal.ErrorHandlingHttpClient;
+import com.google.firebase.internal.HttpRequestInfo;
import com.google.firebase.internal.SdkUtils;
import com.google.firebase.messaging.internal.MessagingServiceErrorResponse;
import com.google.firebase.messaging.internal.MessagingServiceResponse;
@@ -55,37 +62,19 @@ final class FirebaseMessagingClientImpl implements FirebaseMessagingClient {
private static final String FCM_URL = "https://fcm.googleapis.com/v1/projects/%s/messages:send";
- private static final String FCM_BATCH_URL = "https://fcm.googleapis.com/batch";
-
- private static final String API_FORMAT_VERSION_HEADER = "X-GOOG-API-FORMAT-VERSION";
-
- private static final String CLIENT_VERSION_HEADER = "X-Firebase-Client";
-
- private static final Map FCM_ERROR_CODES =
- ImmutableMap.builder()
- // FCM v1 canonical error codes
- .put("NOT_FOUND", "registration-token-not-registered")
- .put("PERMISSION_DENIED", "mismatched-credential")
- .put("RESOURCE_EXHAUSTED", "message-rate-exceeded")
- .put("UNAUTHENTICATED", "third-party-auth-error")
-
- // FCM v1 new error codes
- .put("APNS_AUTH_ERROR", "third-party-auth-error")
- .put("INTERNAL", FirebaseMessaging.INTERNAL_ERROR)
- .put("INVALID_ARGUMENT", "invalid-argument")
- .put("QUOTA_EXCEEDED", "message-rate-exceeded")
- .put("SENDER_ID_MISMATCH", "mismatched-credential")
- .put("THIRD_PARTY_AUTH_ERROR", "third-party-auth-error")
- .put("UNAVAILABLE", "server-unavailable")
- .put("UNREGISTERED", "registration-token-not-registered")
- .build();
+ private static final Map COMMON_HEADERS =
+ ImmutableMap.of(
+ "X-GOOG-API-FORMAT-VERSION", "2",
+ "X-Firebase-Client", "fire-admin-java/" + SdkUtils.getVersion());
private final String fcmSendUrl;
private final HttpRequestFactory requestFactory;
private final HttpRequestFactory childRequestFactory;
private final JsonFactory jsonFactory;
private final HttpResponseInterceptor responseInterceptor;
- private final String clientVersion = "fire-admin-java/" + SdkUtils.getVersion();
+ private final MessagingErrorHandler errorHandler;
+ private final ErrorHandlingHttpClient httpClient;
+ private final MessagingBatchClient batchClient;
private FirebaseMessagingClientImpl(Builder builder) {
checkArgument(!Strings.isNullOrEmpty(builder.projectId));
@@ -94,6 +83,10 @@ private FirebaseMessagingClientImpl(Builder builder) {
this.childRequestFactory = checkNotNull(builder.childRequestFactory);
this.jsonFactory = checkNotNull(builder.jsonFactory);
this.responseInterceptor = builder.responseInterceptor;
+ this.errorHandler = new MessagingErrorHandler(this.jsonFactory);
+ this.httpClient = new ErrorHandlingHttpClient<>(requestFactory, jsonFactory, errorHandler)
+ .setInterceptor(responseInterceptor);
+ this.batchClient = new MessagingBatchClient(requestFactory.getTransport(), jsonFactory);
}
@VisibleForTesting
@@ -116,67 +109,48 @@ JsonFactory getJsonFactory() {
return jsonFactory;
}
- @VisibleForTesting
- String getClientVersion() {
- return clientVersion;
- }
-
public String send(Message message, boolean dryRun) throws FirebaseMessagingException {
- try {
- return sendSingleRequest(message, dryRun);
- } catch (HttpResponseException e) {
- throw createExceptionFromResponse(e);
- } catch (IOException e) {
- throw new FirebaseMessagingException(
- FirebaseMessaging.INTERNAL_ERROR, "Error while calling FCM backend service", e);
- }
+ return sendSingleRequest(message, dryRun);
}
public BatchResponse sendAll(
List messages, boolean dryRun) throws FirebaseMessagingException {
- try {
- return sendBatchRequest(messages, dryRun);
- } catch (HttpResponseException e) {
- throw createExceptionFromResponse(e);
- } catch (IOException e) {
- throw new FirebaseMessagingException(
- FirebaseMessaging.INTERNAL_ERROR, "Error while calling FCM backend service", e);
- }
+ return sendBatchRequest(messages, dryRun);
}
- private String sendSingleRequest(Message message, boolean dryRun) throws IOException {
- HttpRequest request = requestFactory.buildPostRequest(
- new GenericUrl(fcmSendUrl),
- new JsonHttpContent(jsonFactory, message.wrapForTransport(dryRun)));
- setCommonFcmHeaders(request.getHeaders());
- request.setParser(new JsonObjectParser(jsonFactory));
- request.setResponseInterceptor(responseInterceptor);
- HttpResponse response = request.execute();
- try {
- MessagingServiceResponse parsed = new MessagingServiceResponse();
- jsonFactory.createJsonParser(response.getContent()).parseAndClose(parsed);
- return parsed.getMessageId();
- } finally {
- ApiClientUtils.disconnectQuietly(response);
- }
+ private String sendSingleRequest(
+ Message message, boolean dryRun) throws FirebaseMessagingException {
+ HttpRequestInfo request =
+ HttpRequestInfo.buildJsonPostRequest(
+ fcmSendUrl, message.wrapForTransport(dryRun))
+ .addAllHeaders(COMMON_HEADERS);
+ MessagingServiceResponse parsed = httpClient.sendAndParse(
+ request, MessagingServiceResponse.class);
+ return parsed.getMessageId();
}
private BatchResponse sendBatchRequest(
- List messages, boolean dryRun) throws IOException {
+ List messages, boolean dryRun) throws FirebaseMessagingException {
MessagingBatchCallback callback = new MessagingBatchCallback();
- BatchRequest batch = newBatchRequest(messages, dryRun, callback);
- batch.execute();
- return new BatchResponseImpl(callback.getResponses());
+ try {
+ BatchRequest batch = newBatchRequest(messages, dryRun, callback);
+ batch.execute();
+ return new BatchResponseImpl(callback.getResponses());
+ } catch (HttpResponseException e) {
+ OutgoingHttpRequest req = new OutgoingHttpRequest(
+ HttpMethods.POST, MessagingBatchClient.FCM_BATCH_URL);
+ IncomingHttpResponse resp = new IncomingHttpResponse(e, req);
+ throw errorHandler.handleHttpResponseException(e, resp);
+ } catch (IOException e) {
+ throw errorHandler.handleIOException(e);
+ }
}
private BatchRequest newBatchRequest(
List messages, boolean dryRun, MessagingBatchCallback callback) throws IOException {
- BatchRequest batch = new BatchRequest(
- requestFactory.getTransport(), getBatchRequestInitializer());
- batch.setBatchUrl(new GenericUrl(FCM_BATCH_URL));
-
+ BatchRequest batch = batchClient.batch(getBatchRequestInitializer());
final JsonObjectParser jsonParser = new JsonObjectParser(this.jsonFactory);
final GenericUrl sendUrl = new GenericUrl(fcmSendUrl);
for (Message message : messages) {
@@ -186,36 +160,19 @@ private BatchRequest newBatchRequest(
sendUrl,
new JsonHttpContent(jsonFactory, message.wrapForTransport(dryRun)));
request.setParser(jsonParser);
- setCommonFcmHeaders(request.getHeaders());
+ request.getHeaders().putAll(COMMON_HEADERS);
batch.queue(
request, MessagingServiceResponse.class, MessagingServiceErrorResponse.class, callback);
}
return batch;
}
- private void setCommonFcmHeaders(HttpHeaders headers) {
- headers.set(API_FORMAT_VERSION_HEADER, "2");
- headers.set(CLIENT_VERSION_HEADER, clientVersion);
- }
-
- private FirebaseMessagingException createExceptionFromResponse(HttpResponseException e) {
- MessagingServiceErrorResponse response = new MessagingServiceErrorResponse();
- if (e.getContent() != null) {
- try {
- JsonParser parser = jsonFactory.createJsonParser(e.getContent());
- parser.parseAndClose(response);
- } catch (IOException ignored) {
- // ignored
- }
- }
-
- return newException(response, e);
- }
-
private HttpRequestInitializer getBatchRequestInitializer() {
return new HttpRequestInitializer() {
@Override
public void initialize(HttpRequest request) throws IOException {
+ // Batch requests are not executed on the ErrorHandlingHttpClient. Therefore, they
+ // require some special handling at initialization.
HttpRequestInitializer initializer = requestFactory.getInitializer();
if (initializer != null) {
initializer.initialize(request);
@@ -283,30 +240,6 @@ FirebaseMessagingClientImpl build() {
}
}
- private static FirebaseMessagingException newException(MessagingServiceErrorResponse response) {
- return newException(response, null);
- }
-
- private static FirebaseMessagingException newException(
- MessagingServiceErrorResponse response, @Nullable HttpResponseException e) {
- String code = FCM_ERROR_CODES.get(response.getErrorCode());
- if (code == null) {
- code = FirebaseMessaging.UNKNOWN_ERROR;
- }
-
- String msg = response.getErrorMessage();
- if (Strings.isNullOrEmpty(msg)) {
- if (e != null) {
- msg = String.format("Unexpected HTTP response with status: %d; body: %s",
- e.getStatusCode(), e.getContent());
- } else {
- msg = String.format("Unexpected HTTP response: %s", response.toString());
- }
- }
-
- return new FirebaseMessagingException(code, msg, e);
- }
-
private static class MessagingBatchCallback
implements BatchCallback {
@@ -319,13 +252,97 @@ public void onSuccess(
}
@Override
- public void onFailure(
- MessagingServiceErrorResponse error, HttpHeaders responseHeaders) {
- responses.add(SendResponse.fromException(newException(error)));
+ public void onFailure(MessagingServiceErrorResponse error, HttpHeaders responseHeaders) {
+ // We only specify error codes and message for these partial failures. Recall that these
+ // exceptions are never actually thrown, but only made accessible via SendResponse.
+ FirebaseException base = createFirebaseException(error);
+ FirebaseMessagingException exception = FirebaseMessagingException.withMessagingErrorCode(
+ base, error.getMessagingErrorCode());
+ responses.add(SendResponse.fromException(exception));
}
List getResponses() {
return this.responses.build();
}
+
+ private FirebaseException createFirebaseException(MessagingServiceErrorResponse error) {
+ String status = error.getStatus();
+ ErrorCode errorCode = Strings.isNullOrEmpty(status)
+ ? ErrorCode.UNKNOWN : Enum.valueOf(ErrorCode.class, status);
+
+ String msg = error.getErrorMessage();
+ if (Strings.isNullOrEmpty(msg)) {
+ msg = String.format("Unexpected HTTP response: %s", error.toString());
+ }
+
+ return new FirebaseException(errorCode, msg, null);
+ }
+ }
+
+ private static class MessagingErrorHandler
+ extends AbstractPlatformErrorHandler {
+
+ private MessagingErrorHandler(JsonFactory jsonFactory) {
+ super(jsonFactory);
+ }
+
+ @Override
+ protected FirebaseMessagingException createException(FirebaseException base) {
+ String response = getResponse(base);
+ MessagingServiceErrorResponse parsed = safeParse(response);
+ return FirebaseMessagingException.withMessagingErrorCode(
+ base, parsed.getMessagingErrorCode());
+ }
+
+ private String getResponse(FirebaseException base) {
+ if (base.getHttpResponse() == null) {
+ return null;
+ }
+
+ return base.getHttpResponse().getContent();
+ }
+
+ private MessagingServiceErrorResponse safeParse(String response) {
+ if (!Strings.isNullOrEmpty(response)) {
+ try {
+ return jsonFactory.createJsonParser(response)
+ .parseAndClose(MessagingServiceErrorResponse.class);
+ } catch (IOException ignore) {
+ // Ignore any error that may occur while parsing the error response. The server
+ // may have responded with a non-json payload.
+ }
+ }
+
+ return new MessagingServiceErrorResponse();
+ }
+ }
+
+ private static class MessagingBatchClient extends AbstractGoogleJsonClient {
+
+ private static final String FCM_ROOT_URL = "https://fcm.googleapis.com";
+ private static final String FCM_BATCH_PATH = "batch";
+ private static final String FCM_BATCH_URL = String.format(
+ "%s/%s", FCM_ROOT_URL, FCM_BATCH_PATH);
+
+ MessagingBatchClient(HttpTransport transport, JsonFactory jsonFactory) {
+ super(new Builder(transport, jsonFactory));
+ }
+
+ private MessagingBatchClient(Builder builder) {
+ super(builder);
+ }
+
+ private static class Builder extends AbstractGoogleJsonClient.Builder {
+ Builder(HttpTransport transport, JsonFactory jsonFactory) {
+ super(transport, jsonFactory, FCM_ROOT_URL, "", null, false);
+ setBatchPath(FCM_BATCH_PATH);
+ setApplicationName("fire-admin-java");
+ }
+
+ @Override
+ public AbstractGoogleJsonClient build() {
+ return new MessagingBatchClient(this);
+ }
+ }
}
}
diff --git a/src/main/java/com/google/firebase/messaging/FirebaseMessagingException.java b/src/main/java/com/google/firebase/messaging/FirebaseMessagingException.java
index 5f57474ea..a3d92788a 100644
--- a/src/main/java/com/google/firebase/messaging/FirebaseMessagingException.java
+++ b/src/main/java/com/google/firebase/messaging/FirebaseMessagingException.java
@@ -16,26 +16,54 @@
package com.google.firebase.messaging;
-import static com.google.common.base.Preconditions.checkArgument;
-
-import com.google.common.base.Strings;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.firebase.ErrorCode;
import com.google.firebase.FirebaseException;
+import com.google.firebase.IncomingHttpResponse;
import com.google.firebase.internal.NonNull;
+import com.google.firebase.internal.Nullable;
+
+public final class FirebaseMessagingException extends FirebaseException {
-public class FirebaseMessagingException extends FirebaseException {
+ private final MessagingErrorCode errorCode;
- private final String errorCode;
+ @VisibleForTesting
+ FirebaseMessagingException(@NonNull ErrorCode code, @NonNull String message) {
+ this(code, message, null, null, null);
+ }
- FirebaseMessagingException(String errorCode, String message, Throwable cause) {
- super(message, cause);
- checkArgument(!Strings.isNullOrEmpty(errorCode));
+ private FirebaseMessagingException(
+ @NonNull ErrorCode code,
+ @NonNull String message,
+ @Nullable Throwable cause,
+ @Nullable IncomingHttpResponse response,
+ @Nullable MessagingErrorCode errorCode) {
+ super(code, message, cause, response);
this.errorCode = errorCode;
}
+ static FirebaseMessagingException withMessagingErrorCode(
+ FirebaseException base, @Nullable MessagingErrorCode errorCode) {
+ return new FirebaseMessagingException(
+ base.getErrorCode(),
+ base.getMessage(),
+ base.getCause(),
+ base.getHttpResponse(),
+ errorCode);
+ }
+
+ static FirebaseMessagingException withCustomMessage(FirebaseException base, String message) {
+ return new FirebaseMessagingException(
+ base.getErrorCode(),
+ message,
+ base.getCause(),
+ base.getHttpResponse(),
+ null);
+ }
/** Returns an error code that may provide more information about the error. */
- @NonNull
- public String getErrorCode() {
+ @Nullable
+ public MessagingErrorCode getMessagingErrorCode() {
return errorCode;
}
}
diff --git a/src/main/java/com/google/firebase/messaging/InstanceIdClientImpl.java b/src/main/java/com/google/firebase/messaging/InstanceIdClientImpl.java
index 15f8158a5..5648fcf0c 100644
--- a/src/main/java/com/google/firebase/messaging/InstanceIdClientImpl.java
+++ b/src/main/java/com/google/firebase/messaging/InstanceIdClientImpl.java
@@ -16,27 +16,20 @@
package com.google.firebase.messaging;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-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.json.JsonHttpContent;
import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonFactory;
-import com.google.api.client.json.JsonObjectParser;
-import com.google.api.client.json.JsonParser;
import com.google.api.client.util.Key;
-import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.firebase.FirebaseApp;
+import com.google.firebase.FirebaseException;
+import com.google.firebase.internal.AbstractHttpErrorHandler;
import com.google.firebase.internal.ApiClientUtils;
+import com.google.firebase.internal.ErrorHandlingHttpClient;
+import com.google.firebase.internal.HttpRequestInfo;
import com.google.firebase.internal.Nullable;
-
import java.io.IOException;
import java.util.List;
import java.util.Map;
@@ -53,18 +46,7 @@ final class InstanceIdClientImpl implements InstanceIdClient {
private static final String IID_UNSUBSCRIBE_PATH = "iid/v1:batchRemove";
- static final Map IID_ERROR_CODES =
- ImmutableMap.builder()
- .put(400, "invalid-argument")
- .put(401, "authentication-error")
- .put(403, "authentication-error")
- .put(500, FirebaseMessaging.INTERNAL_ERROR)
- .put(503, "server-unavailable")
- .build();
-
- private final HttpRequestFactory requestFactory;
- private final JsonFactory jsonFactory;
- private final HttpResponseInterceptor responseInterceptor;
+ private final ErrorHandlingHttpClient requestFactory;
InstanceIdClientImpl(HttpRequestFactory requestFactory, JsonFactory jsonFactory) {
this(requestFactory, jsonFactory, null);
@@ -74,9 +56,9 @@ final class InstanceIdClientImpl implements InstanceIdClient {
HttpRequestFactory requestFactory,
JsonFactory jsonFactory,
@Nullable HttpResponseInterceptor responseInterceptor) {
- this.requestFactory = checkNotNull(requestFactory);
- this.jsonFactory = checkNotNull(jsonFactory);
- this.responseInterceptor = responseInterceptor;
+ InstanceIdErrorHandler errorHandler = new InstanceIdErrorHandler(jsonFactory);
+ this.requestFactory = new ErrorHandlingHttpClient<>(requestFactory, jsonFactory, errorHandler)
+ .setInterceptor(responseInterceptor);
}
static InstanceIdClientImpl fromApp(FirebaseApp app) {
@@ -85,76 +67,32 @@ static InstanceIdClientImpl fromApp(FirebaseApp app) {
app.getOptions().getJsonFactory());
}
- @VisibleForTesting
- HttpRequestFactory getRequestFactory() {
- return requestFactory;
- }
-
- @VisibleForTesting
- JsonFactory getJsonFactory() {
- return jsonFactory;
- }
-
public TopicManagementResponse subscribeToTopic(
String topic, List registrationTokens) throws FirebaseMessagingException {
- try {
- return sendInstanceIdRequest(topic, registrationTokens, IID_SUBSCRIBE_PATH);
- } catch (HttpResponseException e) {
- throw createExceptionFromResponse(e);
- } catch (IOException e) {
- throw new FirebaseMessagingException(
- FirebaseMessaging.INTERNAL_ERROR, "Error while calling IID backend service", e);
- }
+ return sendInstanceIdRequest(topic, registrationTokens, IID_SUBSCRIBE_PATH);
}
public TopicManagementResponse unsubscribeFromTopic(
String topic, List registrationTokens) throws FirebaseMessagingException {
- try {
- return sendInstanceIdRequest(topic, registrationTokens, IID_UNSUBSCRIBE_PATH);
- } catch (HttpResponseException e) {
- throw createExceptionFromResponse(e);
- } catch (IOException e) {
- throw new FirebaseMessagingException(
- FirebaseMessaging.INTERNAL_ERROR, "Error while calling IID backend service", e);
- }
+ return sendInstanceIdRequest(topic, registrationTokens, IID_UNSUBSCRIBE_PATH);
}
private TopicManagementResponse sendInstanceIdRequest(
- String topic, List registrationTokens, String path) throws IOException {
+ String topic,
+ List registrationTokens,
+ String path) throws FirebaseMessagingException {
+
String url = String.format("%s/%s", IID_HOST, path);
Map payload = ImmutableMap.of(
"to", getPrefixedTopic(topic),
"registration_tokens", registrationTokens
);
- HttpResponse response = null;
- try {
- HttpRequest request = requestFactory.buildPostRequest(
- new GenericUrl(url), new JsonHttpContent(jsonFactory, payload));
- request.getHeaders().set("access_token_auth", "true");
- request.setParser(new JsonObjectParser(jsonFactory));
- request.setResponseInterceptor(responseInterceptor);
- response = request.execute();
-
- JsonParser parser = jsonFactory.createJsonParser(response.getContent());
- InstanceIdServiceResponse parsedResponse = new InstanceIdServiceResponse();
- parser.parse(parsedResponse);
- return new TopicManagementResponse(parsedResponse.results);
- } finally {
- ApiClientUtils.disconnectQuietly(response);
- }
- }
- private FirebaseMessagingException createExceptionFromResponse(HttpResponseException e) {
- InstanceIdServiceErrorResponse response = new InstanceIdServiceErrorResponse();
- if (e.getContent() != null) {
- try {
- JsonParser parser = jsonFactory.createJsonParser(e.getContent());
- parser.parseAndClose(response);
- } catch (IOException ignored) {
- // ignored
- }
- }
- return newException(response, e);
+ HttpRequestInfo request = HttpRequestInfo.buildJsonPostRequest(url, payload)
+ .addHeader("access_token_auth", "true");
+ InstanceIdServiceResponse response = new InstanceIdServiceResponse();
+ requestFactory.sendAndParse(request, response);
+ return new TopicManagementResponse(response.results);
}
private String getPrefixedTopic(String topic) {
@@ -165,21 +103,6 @@ private String getPrefixedTopic(String topic) {
}
}
- private static FirebaseMessagingException newException(
- InstanceIdServiceErrorResponse response, HttpResponseException e) {
- // Infer error code from HTTP status
- String code = IID_ERROR_CODES.get(e.getStatusCode());
- if (code == null) {
- code = FirebaseMessaging.UNKNOWN_ERROR;
- }
- String msg = response.error;
- if (Strings.isNullOrEmpty(msg)) {
- msg = String.format("Unexpected HTTP response with status: %d; body: %s",
- e.getStatusCode(), e.getContent());
- }
- return new FirebaseMessagingException(code, msg, e);
- }
-
private static class InstanceIdServiceResponse {
@Key("results")
private List results;
@@ -189,4 +112,54 @@ private static class InstanceIdServiceErrorResponse {
@Key("error")
private String error;
}
+
+ private static class InstanceIdErrorHandler
+ extends AbstractHttpErrorHandler {
+
+ private final JsonFactory jsonFactory;
+
+ InstanceIdErrorHandler(JsonFactory jsonFactory) {
+ this.jsonFactory = jsonFactory;
+ }
+
+ @Override
+ protected FirebaseMessagingException createException(FirebaseException base) {
+ String message = getCustomMessage(base);
+ return FirebaseMessagingException.withCustomMessage(base, message);
+ }
+
+ private String getCustomMessage(FirebaseException base) {
+ String response = getResponse(base);
+ InstanceIdServiceErrorResponse parsed = safeParse(response);
+ if (!Strings.isNullOrEmpty(parsed.error)) {
+ return "Error while calling the IID service: " + parsed.error;
+ }
+
+ return base.getMessage();
+ }
+
+ private String getResponse(FirebaseException base) {
+ if (base.getHttpResponse() == null) {
+ return null;
+ }
+
+ return base.getHttpResponse().getContent();
+ }
+
+ private InstanceIdServiceErrorResponse safeParse(String response) {
+ InstanceIdServiceErrorResponse parsed = new InstanceIdServiceErrorResponse();
+ if (!Strings.isNullOrEmpty(response)) {
+ // Parse the error response from the IID service.
+ // Sample response: {"error": "error message text"}
+ try {
+ jsonFactory.createJsonParser(response).parse(parsed);
+ } catch (IOException ignore) {
+ // Ignore any error that may occur while parsing the error response. The server
+ // may have responded with a non-json payload.
+ }
+ }
+
+ return parsed;
+ }
+ }
}
diff --git a/src/main/java/com/google/firebase/messaging/LightSettings.java b/src/main/java/com/google/firebase/messaging/LightSettings.java
index 75a692090..e0e898374 100644
--- a/src/main/java/com/google/firebase/messaging/LightSettings.java
+++ b/src/main/java/com/google/firebase/messaging/LightSettings.java
@@ -61,7 +61,7 @@ private Builder() {}
/**
* Sets the lightSettingsColor value with a string.
*
- * @param lightSettingsColor LightSettingsColor specified in the {@code #rrggbb} format.
+ * @param color LightSettingsColor specified in the {@code #rrggbb} format.
* @return This builder.
*/
public Builder setColorFromString(String color) {
@@ -72,7 +72,7 @@ public Builder setColorFromString(String color) {
/**
* Sets the lightSettingsColor value in the light settings.
*
- * @param lightSettingsColor Color to be used in the light settings.
+ * @param color Color to be used in the light settings.
* @return This builder.
*/
public Builder setColor(LightSettingsColor color) {
diff --git a/src/main/java/com/google/firebase/messaging/MessagingErrorCode.java b/src/main/java/com/google/firebase/messaging/MessagingErrorCode.java
new file mode 100644
index 000000000..b566befbd
--- /dev/null
+++ b/src/main/java/com/google/firebase/messaging/MessagingErrorCode.java
@@ -0,0 +1,43 @@
+package com.google.firebase.messaging;
+
+/**
+ * Error codes that can be raised by the Cloud Messaging APIs.
+ */
+public enum MessagingErrorCode {
+
+ /**
+ * APNs certificate or web push auth key was invalid or missing.
+ */
+ THIRD_PARTY_AUTH_ERROR,
+
+ /**
+ * One or more arguments specified in the request were invalid.
+ */
+ INVALID_ARGUMENT,
+
+ /**
+ * Internal server error.
+ */
+ INTERNAL,
+
+ /**
+ * Sending limit exceeded for the message target.
+ */
+ QUOTA_EXCEEDED,
+
+ /**
+ * The authenticated sender ID is different from the sender ID for the registration token.
+ */
+ SENDER_ID_MISMATCH,
+
+ /**
+ * Cloud Messaging service is temporarily unavailable.
+ */
+ UNAVAILABLE,
+
+ /**
+ * App instance was unregistered from FCM. This usually means that the token used is no longer
+ * valid and a new one must be used.
+ */
+ UNREGISTERED,
+}
diff --git a/src/main/java/com/google/firebase/messaging/Notification.java b/src/main/java/com/google/firebase/messaging/Notification.java
index d9b2034ee..a8f48c2ef 100644
--- a/src/main/java/com/google/firebase/messaging/Notification.java
+++ b/src/main/java/com/google/firebase/messaging/Notification.java
@@ -33,33 +33,6 @@ public class Notification {
@Key("image")
private final String image;
- /**
- * Creates a new {@code Notification} using the given title and body.
- *
- * @param title Title of the notification.
- * @param body Body of the notification.
- *
- * @deprecated Use {@link #Notification(Builder)} instead.
- */
- public Notification(String title, String body) {
- this(title, body, null);
- }
-
- /**
- * Creates a new {@code Notification} using the given title, body, and image.
- *
- * @param title Title of the notification.
- * @param body Body of the notification.
- * @param imageUrl URL of the image that is going to be displayed in the notification.
- *
- * @deprecated Use {@link #Notification(Builder)} instead.
- */
- public Notification(String title, String body, String imageUrl) {
- this.title = title;
- this.body = body;
- this.image = imageUrl;
- }
-
private Notification(Builder builder) {
this.title = builder.title;
this.body = builder.body;
@@ -67,9 +40,9 @@ private Notification(Builder builder) {
}
/**
- * Creates a new {@link Notification.Builder}.
+ * Creates a new {@link Builder}.
*
- * @return A {@link Notification.Builder} instance.
+ * @return A {@link Builder} instance.
*/
public static Builder builder() {
return new Builder();
diff --git a/src/main/java/com/google/firebase/messaging/internal/MessagingServiceErrorResponse.java b/src/main/java/com/google/firebase/messaging/internal/MessagingServiceErrorResponse.java
index d63f3af95..7c21199cc 100644
--- a/src/main/java/com/google/firebase/messaging/internal/MessagingServiceErrorResponse.java
+++ b/src/main/java/com/google/firebase/messaging/internal/MessagingServiceErrorResponse.java
@@ -2,14 +2,28 @@
import com.google.api.client.json.GenericJson;
import com.google.api.client.util.Key;
+import com.google.common.collect.ImmutableMap;
import com.google.firebase.internal.Nullable;
+import com.google.firebase.messaging.MessagingErrorCode;
import java.util.List;
import java.util.Map;
/**
* The DTO for parsing error responses from the FCM service.
*/
-public class MessagingServiceErrorResponse extends GenericJson {
+public final class MessagingServiceErrorResponse extends GenericJson {
+
+ private static final Map MESSAGING_ERROR_CODES =
+ ImmutableMap.builder()
+ .put("APNS_AUTH_ERROR", MessagingErrorCode.THIRD_PARTY_AUTH_ERROR)
+ .put("INTERNAL", MessagingErrorCode.INTERNAL)
+ .put("INVALID_ARGUMENT", MessagingErrorCode.INVALID_ARGUMENT)
+ .put("QUOTA_EXCEEDED", MessagingErrorCode.QUOTA_EXCEEDED)
+ .put("SENDER_ID_MISMATCH", MessagingErrorCode.SENDER_ID_MISMATCH)
+ .put("THIRD_PARTY_AUTH_ERROR", MessagingErrorCode.THIRD_PARTY_AUTH_ERROR)
+ .put("UNAVAILABLE", MessagingErrorCode.UNAVAILABLE)
+ .put("UNREGISTERED", MessagingErrorCode.UNREGISTERED)
+ .build();
private static final String FCM_ERROR_TYPE =
"type.googleapis.com/google.firebase.fcm.v1.FcmError";
@@ -17,23 +31,35 @@ public class MessagingServiceErrorResponse extends GenericJson {
@Key("error")
private Map error;
+ public String getStatus() {
+ if (error == null) {
+ return null;
+ }
+
+ return (String) error.get("status");
+ }
+
+
@Nullable
- public String getErrorCode() {
+ public MessagingErrorCode getMessagingErrorCode() {
if (error == null) {
return null;
}
+
Object details = error.get("details");
- if (details != null && details instanceof List) {
+ if (details instanceof List) {
for (Object detail : (List>) details) {
if (detail instanceof Map) {
Map,?> detailMap = (Map,?>) detail;
if (FCM_ERROR_TYPE.equals(detailMap.get("@type"))) {
- return (String) detailMap.get("errorCode");
+ String errorCode = (String) detailMap.get("errorCode");
+ return MESSAGING_ERROR_CODES.get(errorCode);
}
}
}
}
- return (String) error.get("status");
+
+ return null;
}
@Nullable
@@ -41,6 +67,7 @@ public String getErrorMessage() {
if (error != null) {
return (String) error.get("message");
}
+
return null;
}
}
diff --git a/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementException.java b/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementException.java
index fa939b0c4..580ae76f6 100644
--- a/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementException.java
+++ b/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementException.java
@@ -15,18 +15,27 @@
package com.google.firebase.projectmanagement;
+import com.google.firebase.ErrorCode;
import com.google.firebase.FirebaseException;
-import com.google.firebase.internal.Nullable;
+import com.google.firebase.IncomingHttpResponse;
+import com.google.firebase.database.annotations.Nullable;
+import com.google.firebase.internal.NonNull;
/**
* An exception encountered while interacting with the Firebase Project Management Service.
*/
-public class FirebaseProjectManagementException extends FirebaseException {
- FirebaseProjectManagementException(String detailMessage) {
- this(detailMessage, null);
+public final class FirebaseProjectManagementException extends FirebaseException {
+
+ FirebaseProjectManagementException(@NonNull FirebaseException base) {
+ this(base, base.getMessage());
+ }
+
+ FirebaseProjectManagementException(@NonNull FirebaseException base, @NonNull String message) {
+ super(base.getErrorCode(), message, base.getCause(), base.getHttpResponse());
}
- FirebaseProjectManagementException(String detailMessage, @Nullable Throwable cause) {
- super(detailMessage, cause);
+ FirebaseProjectManagementException(
+ @NonNull ErrorCode code, @NonNull String message, @Nullable IncomingHttpResponse response) {
+ super(code, message, null, response);
}
}
diff --git a/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java b/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java
index 8abced696..0f9eb3b55 100644
--- a/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java
+++ b/src/main/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImpl.java
@@ -31,8 +31,10 @@
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.firebase.ErrorCode;
import com.google.firebase.FirebaseApp;
import com.google.firebase.ImplFirebaseTrampolines;
+import com.google.firebase.IncomingHttpResponse;
import com.google.firebase.internal.ApiClientUtils;
import com.google.firebase.internal.CallableOperation;
import java.nio.charset.StandardCharsets;
@@ -56,7 +58,6 @@ class FirebaseProjectManagementServiceImpl implements AndroidAppService, IosAppS
private final FirebaseApp app;
private final Sleeper sleeper;
private final Scheduler scheduler;
- private final HttpRequestFactory requestFactory;
private final HttpHelper httpHelper;
private final CreateAndroidAppFromAppIdFunction createAndroidAppFromAppIdFunction =
@@ -78,15 +79,9 @@ class FirebaseProjectManagementServiceImpl implements AndroidAppService, IosAppS
this.app = checkNotNull(app);
this.sleeper = checkNotNull(sleeper);
this.scheduler = checkNotNull(scheduler);
- this.requestFactory = checkNotNull(requestFactory);
this.httpHelper = new HttpHelper(app.getOptions().getJsonFactory(), requestFactory);
}
- @VisibleForTesting
- HttpRequestFactory getRequestFactory() {
- return requestFactory;
- }
-
@VisibleForTesting
void setInterceptor(HttpResponseInterceptor interceptor) {
httpHelper.setInterceptor(interceptor);
@@ -318,14 +313,14 @@ protected String execute() throws FirebaseProjectManagementException {
payloadBuilder.put("display_name", displayName);
}
OperationResponse operationResponseInstance = new OperationResponse();
- httpHelper.makePostRequest(
+ IncomingHttpResponse response = httpHelper.makePostRequest(
url, payloadBuilder.build(), operationResponseInstance, projectId, "Project ID");
if (Strings.isNullOrEmpty(operationResponseInstance.name)) {
- throw HttpHelper.createFirebaseProjectManagementException(
+ String message = buildMessage(
namespace,
"Bundle ID",
- "Unable to create App: server returned null operation name.",
- /* cause= */ null);
+ "Unable to create App: server returned null operation name.");
+ throw new FirebaseProjectManagementException(ErrorCode.INTERNAL, message, response);
}
return operationResponseInstance.name;
}
@@ -341,7 +336,8 @@ private String pollOperation(String projectId, String operationName)
* Math.pow(POLL_EXPONENTIAL_BACKOFF_FACTOR, currentAttempt));
sleepOrThrow(projectId, delayMillis);
OperationResponse operationResponseInstance = new OperationResponse();
- httpHelper.makeGetRequest(url, operationResponseInstance, projectId, "Project ID");
+ IncomingHttpResponse response = httpHelper.makeGetRequest(
+ url, operationResponseInstance, projectId, "Project ID");
if (!operationResponseInstance.done) {
continue;
}
@@ -349,19 +345,20 @@ private String pollOperation(String projectId, String operationName)
// or 'error' is set.
if (operationResponseInstance.response == null
|| Strings.isNullOrEmpty(operationResponseInstance.response.appId)) {
- throw HttpHelper.createFirebaseProjectManagementException(
+ String message = buildMessage(
projectId,
"Project ID",
- "Unable to create App: internal server error.",
- /* cause= */ null);
+ "Unable to create App: internal server error.");
+ throw new FirebaseProjectManagementException(ErrorCode.INTERNAL, message, response);
}
return operationResponseInstance.response.appId;
}
- throw HttpHelper.createFirebaseProjectManagementException(
+
+ String message = buildMessage(
projectId,
"Project ID",
- "Unable to create App: deadline exceeded.",
- /* cause= */ null);
+ "Unable to create App: deadline exceeded.");
+ throw new FirebaseProjectManagementException(ErrorCode.DEADLINE_EXCEEDED, message, null);
}
/**
@@ -420,19 +417,22 @@ private WaitOperationRunnable(
public void run() {
String url = String.format("%s/v1/%s", FIREBASE_PROJECT_MANAGEMENT_URL, operationName);
OperationResponse operationResponseInstance = new OperationResponse();
+ IncomingHttpResponse httpResponse;
try {
- httpHelper.makeGetRequest(url, operationResponseInstance, projectId, "Project ID");
+ httpResponse = httpHelper.makeGetRequest(
+ url, operationResponseInstance, projectId, "Project ID");
} catch (FirebaseProjectManagementException e) {
settableFuture.setException(e);
return;
}
if (!operationResponseInstance.done) {
if (numberOfPreviousPolls + 1 >= MAXIMUM_POLLING_ATTEMPTS) {
- settableFuture.setException(HttpHelper.createFirebaseProjectManagementException(
- projectId,
+ String message = buildMessage(projectId,
"Project ID",
- "Unable to create App: deadline exceeded.",
- /* cause= */ null));
+ "Unable to create App: deadline exceeded.");
+ FirebaseProjectManagementException exception = new FirebaseProjectManagementException(
+ ErrorCode.DEADLINE_EXCEEDED, message, httpResponse);
+ settableFuture.setException(exception);
} else {
long delayMillis = (long) (
POLL_BASE_WAIT_TIME_MILLIS
@@ -451,11 +451,12 @@ public void run() {
// or 'error' is set.
if (operationResponseInstance.response == null
|| Strings.isNullOrEmpty(operationResponseInstance.response.appId)) {
- settableFuture.setException(HttpHelper.createFirebaseProjectManagementException(
- projectId,
+ String message = buildMessage(projectId,
"Project ID",
- "Unable to create App: internal server error.",
- /* cause= */ null));
+ "Unable to create App: internal server error.");
+ FirebaseProjectManagementException exception = new FirebaseProjectManagementException(
+ ErrorCode.INTERNAL, message, httpResponse);
+ settableFuture.setException(exception);
} else {
settableFuture.set(operationResponseInstance.response.appId);
}
@@ -765,14 +766,17 @@ private void sleepOrThrow(String projectId, long delayMillis)
try {
sleeper.sleep(delayMillis);
} catch (InterruptedException e) {
- throw HttpHelper.createFirebaseProjectManagementException(
- projectId,
+ String message = buildMessage(projectId,
"Project ID",
- "Unable to create App: exponential backoff interrupted.",
- /* cause= */ null);
+ "Unable to create App: exponential backoff interrupted.");
+ throw new FirebaseProjectManagementException(ErrorCode.ABORTED, message, null);
}
}
+ private String buildMessage(String resourceId, String resourceIdName, String description) {
+ return String.format("%s \"%s\": %s", resourceIdName, resourceId, description);
+ }
+
/* Helper types. */
private interface CreateAppFromAppIdFunction extends ApiFunction {}
diff --git a/src/main/java/com/google/firebase/projectmanagement/HttpHelper.java b/src/main/java/com/google/firebase/projectmanagement/HttpHelper.java
index 2143c1453..fb29f7b86 100644
--- a/src/main/java/com/google/firebase/projectmanagement/HttpHelper.java
+++ b/src/main/java/com/google/firebase/projectmanagement/HttpHelper.java
@@ -16,84 +16,57 @@
package com.google.firebase.projectmanagement;
-import com.google.api.client.http.GenericUrl;
-import com.google.api.client.http.HttpRequest;
+import com.google.api.client.http.HttpMethods;
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.json.JsonHttpContent;
import com.google.api.client.json.JsonFactory;
-import com.google.api.client.json.JsonObjectParser;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Charsets;
-import com.google.common.collect.ImmutableMap;
-import com.google.firebase.internal.Nullable;
+import com.google.firebase.FirebaseException;
+import com.google.firebase.IncomingHttpResponse;
+import com.google.firebase.internal.AbstractPlatformErrorHandler;
+import com.google.firebase.internal.ErrorHandlingHttpClient;
+import com.google.firebase.internal.HttpRequestInfo;
import com.google.firebase.internal.SdkUtils;
-import java.io.IOException;
-
-class HttpHelper {
-
- @VisibleForTesting static final String PATCH_OVERRIDE_KEY = "X-HTTP-Method-Override";
- @VisibleForTesting static final String PATCH_OVERRIDE_VALUE = "PATCH";
- private static final ImmutableMap ERROR_CODES =
- ImmutableMap.builder()
- .put(401, "Request not authorized.")
- .put(403, "Client does not have sufficient privileges.")
- .put(404, "Failed to find the resource.")
- .put(409, "The resource already exists.")
- .put(429, "Request throttled by the backend server.")
- .put(500, "Internal server error.")
- .put(503, "Backend servers are over capacity. Try again later.")
- .build();
+
+final class HttpHelper {
+
private static final String CLIENT_VERSION_HEADER = "X-Client-Version";
- private final String clientVersion = "Java/Admin/" + SdkUtils.getVersion();
- private final JsonFactory jsonFactory;
- private final HttpRequestFactory requestFactory;
- private HttpResponseInterceptor interceptor;
+ private static final String CLIENT_VERSION = "Java/Admin/" + SdkUtils.getVersion();
+
+ private final ErrorHandlingHttpClient httpClient;
HttpHelper(JsonFactory jsonFactory, HttpRequestFactory requestFactory) {
- this.jsonFactory = jsonFactory;
- this.requestFactory = requestFactory;
+ ProjectManagementErrorHandler errorHandler = new ProjectManagementErrorHandler(jsonFactory);
+ this.httpClient = new ErrorHandlingHttpClient<>(requestFactory, jsonFactory, errorHandler);
}
void setInterceptor(HttpResponseInterceptor interceptor) {
- this.interceptor = interceptor;
+ httpClient.setInterceptor(interceptor);
}
- void makeGetRequest(
+ IncomingHttpResponse makeGetRequest(
String url,
T parsedResponseInstance,
String requestIdentifier,
String requestIdentifierDescription) throws FirebaseProjectManagementException {
- try {
- makeRequest(
- requestFactory.buildGetRequest(new GenericUrl(url)),
- parsedResponseInstance,
- requestIdentifier,
- requestIdentifierDescription);
- } catch (IOException e) {
- handleError(requestIdentifier, requestIdentifierDescription, e);
- }
+ return makeRequest(
+ HttpRequestInfo.buildGetRequest(url),
+ parsedResponseInstance,
+ requestIdentifier,
+ requestIdentifierDescription);
}
- void makePostRequest(
+ IncomingHttpResponse makePostRequest(
String url,
Object payload,
T parsedResponseInstance,
String requestIdentifier,
String requestIdentifierDescription) throws FirebaseProjectManagementException {
- try {
- makeRequest(
- requestFactory.buildPostRequest(
- new GenericUrl(url), new JsonHttpContent(jsonFactory, payload)),
- parsedResponseInstance,
- requestIdentifier,
- requestIdentifierDescription);
- } catch (IOException e) {
- handleError(requestIdentifier, requestIdentifierDescription, e);
- }
+ return makeRequest(
+ HttpRequestInfo.buildJsonPostRequest(url, payload),
+ parsedResponseInstance,
+ requestIdentifier,
+ requestIdentifierDescription);
}
void makePatchRequest(
@@ -102,15 +75,11 @@ void makePatchRequest(
T parsedResponseInstance,
String requestIdentifier,
String requestIdentifierDescription) throws FirebaseProjectManagementException {
- try {
- HttpRequest baseRequest = requestFactory.buildPostRequest(
- new GenericUrl(url), new JsonHttpContent(jsonFactory, payload));
- baseRequest.getHeaders().set(PATCH_OVERRIDE_KEY, PATCH_OVERRIDE_VALUE);
- makeRequest(
- baseRequest, parsedResponseInstance, requestIdentifier, requestIdentifierDescription);
- } catch (IOException e) {
- handleError(requestIdentifier, requestIdentifierDescription, e);
- }
+ makeRequest(
+ HttpRequestInfo.buildJsonRequest(HttpMethods.PATCH, url, payload),
+ parsedResponseInstance,
+ requestIdentifier,
+ requestIdentifierDescription);
}
void makeDeleteRequest(
@@ -118,69 +87,40 @@ void makeDeleteRequest(
T parsedResponseInstance,
String requestIdentifier,
String requestIdentifierDescription) throws FirebaseProjectManagementException {
- try {
- makeRequest(
- requestFactory.buildDeleteRequest(new GenericUrl(url)),
- parsedResponseInstance,
- requestIdentifier,
- requestIdentifierDescription);
- } catch (IOException e) {
- handleError(requestIdentifier, requestIdentifierDescription, e);
- }
+ makeRequest(
+ HttpRequestInfo.buildDeleteRequest(url),
+ parsedResponseInstance,
+ requestIdentifier,
+ requestIdentifierDescription);
}
- void makeRequest(
- HttpRequest baseRequest,
+ private IncomingHttpResponse makeRequest(
+ HttpRequestInfo baseRequest,
T parsedResponseInstance,
String requestIdentifier,
String requestIdentifierDescription) throws FirebaseProjectManagementException {
- HttpResponse response = null;
try {
- baseRequest.getHeaders().set(CLIENT_VERSION_HEADER, clientVersion);
- baseRequest.setParser(new JsonObjectParser(jsonFactory));
- baseRequest.setResponseInterceptor(interceptor);
- response = baseRequest.execute();
- jsonFactory.createJsonParser(response.getContent(), Charsets.UTF_8)
- .parseAndClose(parsedResponseInstance);
- } catch (Exception e) {
- handleError(requestIdentifier, requestIdentifierDescription, e);
- } finally {
- disconnectQuietly(response);
+ baseRequest.addHeader(CLIENT_VERSION_HEADER, CLIENT_VERSION);
+ IncomingHttpResponse response = httpClient.send(baseRequest);
+ httpClient.parse(response, parsedResponseInstance);
+ return response;
+ } catch (FirebaseProjectManagementException e) {
+ String message = String.format(
+ "%s \"%s\": %s", requestIdentifierDescription, requestIdentifier, e.getMessage());
+ throw new FirebaseProjectManagementException(e, message);
}
}
- private static void disconnectQuietly(HttpResponse response) {
- if (response != null) {
- try {
- response.disconnect();
- } catch (IOException ignored) {
- // Ignored.
- }
- }
- }
+ private static class ProjectManagementErrorHandler
+ extends AbstractPlatformErrorHandler {
- private static void handleError(
- String requestIdentifier, String requestIdentifierDescription, Exception e)
- throws FirebaseProjectManagementException {
- String messageBody = "Error while invoking Firebase Project Management service.";
- if (e instanceof HttpResponseException) {
- int statusCode = ((HttpResponseException) e).getStatusCode();
- if (ERROR_CODES.containsKey(statusCode)) {
- messageBody = ERROR_CODES.get(statusCode);
- }
+ ProjectManagementErrorHandler(JsonFactory jsonFactory) {
+ super(jsonFactory);
}
- throw createFirebaseProjectManagementException(
- requestIdentifier, requestIdentifierDescription, messageBody, e);
- }
- static FirebaseProjectManagementException createFirebaseProjectManagementException(
- String requestIdentifier,
- String requestIdentifierDescription,
- String messageBody,
- @Nullable Exception cause) {
- return new FirebaseProjectManagementException(
- String.format(
- "%s \"%s\": %s", requestIdentifierDescription, requestIdentifier, messageBody),
- cause);
+ @Override
+ protected FirebaseProjectManagementException createException(FirebaseException base) {
+ return new FirebaseProjectManagementException(base);
+ }
}
}
diff --git a/src/test/java/com/google/firebase/FirebaseAppTest.java b/src/test/java/com/google/firebase/FirebaseAppTest.java
index eee9efdb8..d481e3c0e 100644
--- a/src/test/java/com/google/firebase/FirebaseAppTest.java
+++ b/src/test/java/com/google/firebase/FirebaseAppTest.java
@@ -35,13 +35,11 @@
import com.google.auth.oauth2.OAuth2Credentials.CredentialsChangedListener;
import com.google.common.base.Defaults;
import com.google.common.base.Strings;
-import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.firebase.FirebaseApp.TokenRefresher;
-import com.google.firebase.FirebaseOptions.Builder;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.testing.FirebaseAppRule;
import com.google.firebase.testing.ServiceAccount;
@@ -74,7 +72,7 @@
public class FirebaseAppTest {
private static final FirebaseOptions OPTIONS =
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setCredentials(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream()))
.build();
@@ -110,7 +108,7 @@ public void testGetInstancePersistedNotInitialized() {
@Test
public void testGetProjectIdFromOptions() {
- FirebaseOptions options = new FirebaseOptions.Builder(OPTIONS)
+ FirebaseOptions options = OPTIONS.toBuilder()
.setProjectId("explicit-project-id")
.build();
FirebaseApp app = FirebaseApp.initializeApp(options, "myApp");
@@ -131,7 +129,7 @@ public void testGetProjectIdFromEnvironment() {
for (String variable : variables) {
String gcloudProject = System.getenv(variable);
TestUtils.setEnvironmentVariables(ImmutableMap.of(variable, "project-id-1"));
- FirebaseOptions options = new FirebaseOptions.Builder()
+ FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(new MockGoogleCredentials())
.build();
try {
@@ -155,7 +153,7 @@ public void testProjectIdEnvironmentVariablePrecedence() {
TestUtils.setEnvironmentVariables(ImmutableMap.of(
"GCLOUD_PROJECT", "project-id-1", "GOOGLE_CLOUD_PROJECT", "project-id-2"));
- FirebaseOptions options = new FirebaseOptions.Builder()
+ FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(new MockGoogleCredentials())
.build();
try {
@@ -239,7 +237,7 @@ public void testGetNullApp() {
@Test
public void testToString() throws IOException {
FirebaseOptions options =
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
.build();
FirebaseApp app = FirebaseApp.initializeApp(options, "app");
@@ -461,14 +459,13 @@ public void testTokenRefresherStateMachine() {
@Test
public void testAppWithAuthVariableOverrides() {
Map authVariableOverrides = ImmutableMap.of("uid", "uid1");
- FirebaseOptions options =
- new FirebaseOptions.Builder(getMockCredentialOptions())
- .setDatabaseAuthVariableOverride(authVariableOverrides)
- .build();
+ FirebaseOptions options = getMockCredentialOptions().toBuilder()
+ .setDatabaseAuthVariableOverride(authVariableOverrides)
+ .build();
FirebaseApp app = FirebaseApp.initializeApp(options, "testGetAppWithUid");
assertEquals("uid1", app.getOptions().getDatabaseAuthVariableOverride().get("uid"));
String token = TestOnlyImplFirebaseTrampolines.getToken(app, false);
- Assert.assertTrue(!token.isEmpty());
+ Assert.assertFalse(token.isEmpty());
}
@Test(expected = IllegalArgumentException.class)
@@ -579,16 +576,6 @@ public void testFirebaseConfigStringIgnoresInvalidKey() {
assertEquals("hipster-chat-mock", firebaseApp.getOptions().getProjectId());
}
- @Test(expected = IllegalArgumentException.class)
- public void testFirebaseExceptionNullDetail() {
- new FirebaseException(null);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testFirebaseExceptionEmptyDetail() {
- new FirebaseException("");
- }
-
@Test
public void testFirebaseAppCreationWithEmptySupplier() {
FirebaseApp.initializeApp(FirebaseOptions.builder()
@@ -609,7 +596,7 @@ private static void setFirebaseConfigEnvironmentVariable(String configJSON) {
}
private static FirebaseOptions getMockCredentialOptions() {
- return new Builder().setCredentials(new MockGoogleCredentials()).build();
+ return FirebaseOptions.builder().setCredentials(new MockGoogleCredentials()).build();
}
private static void invokePublicInstanceMethodWithDefaultValues(Object instance, Method method)
diff --git a/src/test/java/com/google/firebase/FirebaseExceptionTest.java b/src/test/java/com/google/firebase/FirebaseExceptionTest.java
new file mode 100644
index 000000000..efe4e39fa
--- /dev/null
+++ b/src/test/java/com/google/firebase/FirebaseExceptionTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2020 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import com.google.api.client.http.HttpRequest;
+import com.google.api.client.http.HttpResponseException;
+import com.google.api.client.http.HttpStatusCodes;
+import com.google.api.client.testing.http.MockLowLevelHttpRequest;
+import com.google.api.client.testing.http.MockLowLevelHttpResponse;
+import com.google.firebase.testing.TestUtils;
+import java.io.IOException;
+import org.junit.Test;
+
+@SuppressWarnings("ThrowableNotThrown")
+public class FirebaseExceptionTest {
+
+ @Test(expected = NullPointerException.class)
+ public void testFirebaseExceptionWithoutErrorCode() {
+ new FirebaseException(
+ null,
+ "Test error",
+ null,
+ null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testFirebaseExceptionWithNullMessage() {
+ new FirebaseException(
+ ErrorCode.INTERNAL,
+ null,
+ null,
+ null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testFirebaseExceptionWithEmptyMessage() {
+ new FirebaseException(
+ ErrorCode.INTERNAL,
+ "",
+ null,
+ null);
+ }
+
+ @Test
+ public void testFirebaseExceptionWithoutResponseAndCause() {
+ FirebaseException exception = new FirebaseException(
+ ErrorCode.INTERNAL,
+ "Test error",
+ null,
+ null);
+
+ assertEquals(ErrorCode.INTERNAL, exception.getErrorCode());
+ assertEquals("Test error", exception.getMessage());
+ assertNull(exception.getHttpResponse());
+ assertNull(exception.getCause());
+ }
+
+ @Test
+ public void testFirebaseExceptionWithResponse() throws IOException {
+ HttpResponseException httpError = createHttpResponseException();
+ OutgoingHttpRequest request = new OutgoingHttpRequest(
+ "GET", "https://firebase.google.com");
+ IncomingHttpResponse response = new IncomingHttpResponse(httpError, request);
+
+ FirebaseException exception = new FirebaseException(
+ ErrorCode.INTERNAL,
+ "Test error",
+ null,
+ response);
+
+ assertEquals(ErrorCode.INTERNAL, exception.getErrorCode());
+ assertEquals("Test error", exception.getMessage());
+ assertSame(response, exception.getHttpResponse());
+ assertNull(exception.getCause());
+ }
+
+ @Test
+ public void testFirebaseExceptionWithCause() {
+ Exception cause = new Exception("root cause");
+
+ FirebaseException exception = new FirebaseException(
+ ErrorCode.INTERNAL,
+ "Test error",
+ cause);
+
+ assertEquals(ErrorCode.INTERNAL, exception.getErrorCode());
+ assertEquals("Test error", exception.getMessage());
+ assertNull(exception.getHttpResponse());
+ assertSame(cause, exception.getCause());
+ }
+
+ private HttpResponseException createHttpResponseException() throws IOException {
+ MockLowLevelHttpResponse lowLevelResponse = new MockLowLevelHttpResponse()
+ .setStatusCode(HttpStatusCodes.STATUS_CODE_SERVER_ERROR)
+ .setContent("{}");
+ MockLowLevelHttpRequest lowLevelRequest = new MockLowLevelHttpRequest()
+ .setResponse(lowLevelResponse);
+ HttpRequest request = TestUtils.createRequest(lowLevelRequest);
+ try {
+ request.execute();
+ throw new IOException("HttpResponseException not thrown");
+ } catch (HttpResponseException e) {
+ return e;
+ }
+ }
+}
diff --git a/src/test/java/com/google/firebase/FirebaseOptionsTest.java b/src/test/java/com/google/firebase/FirebaseOptionsTest.java
index a3dac26fd..c74215beb 100644
--- a/src/test/java/com/google/firebase/FirebaseOptionsTest.java
+++ b/src/test/java/com/google/firebase/FirebaseOptionsTest.java
@@ -17,14 +17,13 @@
package com.google.firebase;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
-import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import com.google.auth.oauth2.AccessToken;
@@ -49,7 +48,7 @@ public class FirebaseOptionsTest {
private static final String FIREBASE_PROJECT_ID = "explicit-project-id";
private static final FirebaseOptions ALL_VALUES_OPTIONS =
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setDatabaseUrl(FIREBASE_DB_URL)
.setStorageBucket(FIREBASE_STORAGE_BUCKET)
.setProjectId(FIREBASE_PROJECT_ID)
@@ -76,11 +75,9 @@ protected ThreadFactory getThreadFactory() {
public void createOptionsWithAllValuesSet() throws IOException {
GsonFactory jsonFactory = new GsonFactory();
NetHttpTransport httpTransport = new NetHttpTransport();
- FirestoreOptions firestoreOptions = FirestoreOptions.newBuilder()
- .setTimestampsInSnapshotsEnabled(true)
- .build();
+ FirestoreOptions firestoreOptions = FirestoreOptions.newBuilder().build();
FirebaseOptions firebaseOptions =
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setDatabaseUrl(FIREBASE_DB_URL)
.setStorageBucket(FIREBASE_STORAGE_BUCKET)
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
@@ -106,14 +103,14 @@ public void createOptionsWithAllValuesSet() throws IOException {
assertNotNull(credentials);
assertTrue(credentials instanceof ServiceAccountCredentials);
assertEquals(
- GoogleCredential.fromStream(ServiceAccount.EDITOR.asStream()).getServiceAccountId(),
+ ServiceAccount.EDITOR.getEmail(),
((ServiceAccountCredentials) credentials).getClientEmail());
}
@Test
public void createOptionsWithOnlyMandatoryValuesSet() throws IOException {
FirebaseOptions firebaseOptions =
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
.build();
assertNotNull(firebaseOptions.getJsonFactory());
@@ -128,7 +125,7 @@ public void createOptionsWithOnlyMandatoryValuesSet() throws IOException {
assertNotNull(credentials);
assertTrue(credentials instanceof ServiceAccountCredentials);
assertEquals(
- GoogleCredential.fromStream(ServiceAccount.EDITOR.asStream()).getServiceAccountId(),
+ ServiceAccount.EDITOR.getEmail(),
((ServiceAccountCredentials) credentials).getClientEmail());
assertNull(firebaseOptions.getFirestoreOptions());
}
@@ -136,7 +133,7 @@ public void createOptionsWithOnlyMandatoryValuesSet() throws IOException {
@Test
public void createOptionsWithCustomFirebaseCredential() {
FirebaseOptions firebaseOptions =
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setCredentials(new GoogleCredentials() {
@Override
public AccessToken refreshAccessToken() {
@@ -156,17 +153,17 @@ public AccessToken refreshAccessToken() {
@Test(expected = NullPointerException.class)
public void createOptionsWithCredentialMissing() {
- new FirebaseOptions.Builder().build().getCredentials();
+ FirebaseOptions.builder().build().getCredentials();
}
@Test(expected = NullPointerException.class)
public void createOptionsWithNullCredentials() {
- new FirebaseOptions.Builder().setCredentials((GoogleCredentials) null).build();
+ FirebaseOptions.builder().setCredentials((GoogleCredentials) null).build();
}
@Test(expected = IllegalArgumentException.class)
public void createOptionsWithStorageBucketUrl() throws IOException {
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
.setStorageBucket("gs://mock-storage-bucket")
.build();
@@ -174,7 +171,7 @@ public void createOptionsWithStorageBucketUrl() throws IOException {
@Test(expected = NullPointerException.class)
public void createOptionsWithNullThreadManager() {
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setCredentials(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream()))
.setThreadManager(null)
.build();
@@ -182,7 +179,7 @@ public void createOptionsWithNullThreadManager() {
@Test
public void checkToBuilderCreatesNewEquivalentInstance() {
- FirebaseOptions allValuesOptionsCopy = new FirebaseOptions.Builder(ALL_VALUES_OPTIONS).build();
+ FirebaseOptions allValuesOptionsCopy = ALL_VALUES_OPTIONS.toBuilder().build();
assertNotSame(ALL_VALUES_OPTIONS, allValuesOptionsCopy);
assertEquals(ALL_VALUES_OPTIONS.getCredentials(), allValuesOptionsCopy.getCredentials());
assertEquals(ALL_VALUES_OPTIONS.getDatabaseUrl(), allValuesOptionsCopy.getDatabaseUrl());
@@ -198,7 +195,7 @@ public void checkToBuilderCreatesNewEquivalentInstance() {
@Test(expected = IllegalArgumentException.class)
public void createOptionsWithInvalidConnectTimeout() {
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setCredentials(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream()))
.setConnectTimeout(-1)
.build();
@@ -206,7 +203,7 @@ public void createOptionsWithInvalidConnectTimeout() {
@Test(expected = IllegalArgumentException.class)
public void createOptionsWithInvalidReadTimeout() {
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setCredentials(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream()))
.setReadTimeout(-1)
.build();
@@ -216,14 +213,14 @@ public void createOptionsWithInvalidReadTimeout() {
public void testNotEquals() throws IOException {
GoogleCredentials credentials = GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream());
FirebaseOptions options1 =
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setCredentials(credentials)
.build();
FirebaseOptions options2 =
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setCredentials(credentials)
.setDatabaseUrl("https://test.firebaseio.com")
.build();
- assertFalse(options1.equals(options2));
+ assertNotEquals(options1, options2);
}
}
diff --git a/src/test/java/com/google/firebase/IncomingHttpResponseTest.java b/src/test/java/com/google/firebase/IncomingHttpResponseTest.java
new file mode 100644
index 000000000..4da63d12b
--- /dev/null
+++ b/src/test/java/com/google/firebase/IncomingHttpResponseTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2020 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.api.client.http.GenericUrl;
+import com.google.api.client.http.HttpMethods;
+import com.google.api.client.http.HttpRequest;
+import com.google.api.client.http.HttpResponse;
+import com.google.api.client.http.HttpResponseException;
+import com.google.api.client.http.HttpStatusCodes;
+import com.google.api.client.testing.http.MockLowLevelHttpRequest;
+import com.google.api.client.testing.http.MockLowLevelHttpResponse;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.firebase.testing.TestUtils;
+import java.io.IOException;
+import java.util.Map;
+import org.junit.Test;
+
+public class IncomingHttpResponseTest {
+
+ private static final String TEST_URL = "https://firebase.google.com/response";
+ private static final OutgoingHttpRequest REQUEST = new OutgoingHttpRequest("GET", TEST_URL);
+ private static final Map RESPONSE_HEADERS =
+ ImmutableMap.of("x-firebase-client", ImmutableList.of("test-version"));
+ private static final String RESPONSE_BODY = "test response";
+
+ @Test(expected = NullPointerException.class)
+ public void testNullHttpResponse() {
+ new IncomingHttpResponse(null, "content");
+ }
+
+ @Test
+ public void testNullHttpResponseException() throws IOException {
+ try {
+ new IncomingHttpResponse(null, REQUEST);
+ fail("No exception thrown for null HttpResponseException");
+ } catch (NullPointerException ignore) {
+ // expected
+ }
+
+ HttpRequest request = createHttpRequest();
+ try {
+ new IncomingHttpResponse(null, request);
+ fail("No exception thrown for null HttpResponseException");
+ } catch (NullPointerException ignore) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testIncomingHttpResponse() throws IOException {
+ HttpResponseException httpError = createHttpResponseException();
+
+ IncomingHttpResponse response = new IncomingHttpResponse(httpError, REQUEST);
+
+ assertEquals(HttpStatusCodes.STATUS_CODE_SERVER_ERROR, response.getStatusCode());
+ assertEquals(RESPONSE_BODY, response.getContent());
+ assertEquals(RESPONSE_HEADERS, response.getHeaders());
+ assertFalse(response.getHeaders().isEmpty());
+ assertSame(REQUEST, response.getRequest());
+ }
+
+ @Test
+ public void testIncomingHttpResponseWithRequest() throws IOException {
+ HttpResponseException httpError = createHttpResponseException();
+ HttpRequest httpRequest = createHttpRequest();
+
+ IncomingHttpResponse response = new IncomingHttpResponse(httpError, httpRequest);
+
+ assertEquals(HttpStatusCodes.STATUS_CODE_SERVER_ERROR, response.getStatusCode());
+ assertEquals(RESPONSE_BODY, response.getContent());
+ assertEquals(RESPONSE_HEADERS, response.getHeaders());
+ OutgoingHttpRequest request = response.getRequest();
+ assertEquals(HttpMethods.POST, request.getMethod());
+ assertEquals(TEST_URL, request.getUrl());
+ }
+
+ @Test
+ public void testIncomingHttpResponseWithResponse() throws IOException {
+ HttpResponse httpResponse = createHttpResponse();
+
+ IncomingHttpResponse response = new IncomingHttpResponse(httpResponse, RESPONSE_BODY);
+
+ assertEquals(HttpStatusCodes.STATUS_CODE_OK, response.getStatusCode());
+ assertEquals(RESPONSE_BODY, response.getContent());
+ assertTrue(response.getHeaders().isEmpty());
+ OutgoingHttpRequest request = response.getRequest();
+ assertEquals(HttpMethods.POST, request.getMethod());
+ assertEquals(TEST_URL, request.getUrl());
+ }
+
+ private HttpResponseException createHttpResponseException() throws IOException {
+ MockLowLevelHttpResponse lowLevelResponse = new MockLowLevelHttpResponse()
+ .setStatusCode(HttpStatusCodes.STATUS_CODE_SERVER_ERROR)
+ .addHeader("X-Firebase-Client", "test-version")
+ .setContent(RESPONSE_BODY);
+ MockLowLevelHttpRequest lowLevelRequest = new MockLowLevelHttpRequest()
+ .setResponse(lowLevelResponse);
+ HttpRequest request = TestUtils.createRequest(lowLevelRequest, new GenericUrl(TEST_URL));
+ try {
+ request.execute();
+ throw new IOException("HttpResponseException not thrown");
+ } catch (HttpResponseException e) {
+ return e;
+ }
+ }
+
+ private HttpRequest createHttpRequest() throws IOException {
+ MockLowLevelHttpResponse lowLevelResponse = new MockLowLevelHttpResponse()
+ .setContent("{}");
+ MockLowLevelHttpRequest lowLevelRequest = new MockLowLevelHttpRequest()
+ .setResponse(lowLevelResponse);
+ return TestUtils.createRequest(lowLevelRequest, new GenericUrl(TEST_URL));
+ }
+
+ private HttpResponse createHttpResponse() throws IOException {
+ HttpRequest request = createHttpRequest();
+ return request.execute();
+ }
+}
diff --git a/src/test/java/com/google/firebase/OutgoingHttpRequestTest.java b/src/test/java/com/google/firebase/OutgoingHttpRequestTest.java
new file mode 100644
index 000000000..792d621f5
--- /dev/null
+++ b/src/test/java/com/google/firebase/OutgoingHttpRequestTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2020 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import com.google.api.client.googleapis.util.Utils;
+import com.google.api.client.http.GenericUrl;
+import com.google.api.client.http.HttpMethods;
+import com.google.api.client.http.HttpRequest;
+import com.google.api.client.http.json.JsonHttpContent;
+import com.google.api.client.testing.http.MockHttpTransport;
+import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import org.junit.Test;
+
+public class OutgoingHttpRequestTest {
+
+ private static final String TEST_URL = "https://firebase.google.com/request";
+
+ @Test(expected = NullPointerException.class)
+ public void testNullHttpRequest() {
+ new OutgoingHttpRequest(null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNullMethod() {
+ new OutgoingHttpRequest(null, TEST_URL);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testEmptyMethod() {
+ new OutgoingHttpRequest("", TEST_URL);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testNullUrl() {
+ new OutgoingHttpRequest(HttpMethods.GET, null);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testEmptyUrl() {
+ new OutgoingHttpRequest(HttpMethods.GET, "");
+ }
+
+ @Test
+ public void testOutgoingHttpRequest() {
+ OutgoingHttpRequest request = new OutgoingHttpRequest(HttpMethods.GET, TEST_URL);
+
+ assertEquals(HttpMethods.GET, request.getMethod());
+ assertEquals(TEST_URL, request.getUrl());
+ assertNull(request.getContent());
+ assertTrue(request.getHeaders().isEmpty());
+ }
+
+ @Test
+ public void testOutgoingHttpRequestWithContent() throws IOException {
+ JsonHttpContent streamingContent = new JsonHttpContent(
+ Utils.getDefaultJsonFactory(),
+ ImmutableMap.of("key", "value"));
+ HttpRequest httpRequest = new MockHttpTransport().createRequestFactory()
+ .buildPostRequest(new GenericUrl(TEST_URL), streamingContent);
+ httpRequest.getHeaders().set("X-Firebase-Client", "test-version");
+
+ OutgoingHttpRequest request = new OutgoingHttpRequest(httpRequest);
+
+ assertEquals(HttpMethods.POST, request.getMethod());
+ assertEquals(TEST_URL, request.getUrl());
+ assertSame(streamingContent, request.getContent());
+ assertEquals("test-version", request.getHeaders().get("x-firebase-client"));
+ }
+}
diff --git a/src/test/java/com/google/firebase/ThreadManagerTest.java b/src/test/java/com/google/firebase/ThreadManagerTest.java
index d8689a4da..22401de30 100644
--- a/src/test/java/com/google/firebase/ThreadManagerTest.java
+++ b/src/test/java/com/google/firebase/ThreadManagerTest.java
@@ -187,8 +187,7 @@ public void testAppLifecycleWithServiceCall() {
}
@Test
- public void testAppLifecycleWithMultipleServiceCalls()
- throws ExecutionException, InterruptedException {
+ public void testAppLifecycleWithMultipleServiceCalls() {
MockThreadManager threadManager = new MockThreadManager(executor);
// Initializing an app should initialize the executor.
@@ -235,7 +234,7 @@ public void testAppLifecycleWithMultipleServiceCalls()
}
private FirebaseOptions buildOptions(ThreadManager threadManager) {
- return new FirebaseOptions.Builder()
+ return FirebaseOptions.builder()
.setCredentials(new MockGoogleCredentials())
.setProjectId("mock-project-id")
.setThreadManager(threadManager)
@@ -278,7 +277,7 @@ private static class Event {
private final FirebaseApp app;
private ExecutorService executor;
- public Event(int type, @Nullable FirebaseApp app, @Nullable ExecutorService executor) {
+ Event(int type, @Nullable FirebaseApp app, @Nullable ExecutorService executor) {
this.type = type;
this.app = app;
this.executor = executor;
diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java
index 803591d81..35fa21d4d 100644
--- a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java
+++ b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java
@@ -43,6 +43,7 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.io.BaseEncoding;
import com.google.common.util.concurrent.MoreExecutors;
+import com.google.firebase.ErrorCode;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.ImplFirebaseTrampolines;
@@ -50,7 +51,6 @@
import com.google.firebase.auth.UserTestUtils.RandomUser;
import com.google.firebase.auth.UserTestUtils.TemporaryUser;
import com.google.firebase.auth.hash.Scrypt;
-import com.google.firebase.auth.internal.AuthHttpClient;
import com.google.firebase.internal.Nullable;
import com.google.firebase.testing.IntegrationTestUtils;
import java.io.IOException;
@@ -96,8 +96,14 @@ public void testGetNonExistingUser() throws Exception {
fail("No error thrown for non existing uid");
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof FirebaseAuthException);
- assertEquals(AuthHttpClient.USER_NOT_FOUND_ERROR,
- ((FirebaseAuthException) e.getCause()).getErrorCode());
+ FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
+ assertEquals(
+ "No user record found for the provided user ID: non.existing",
+ authException.getMessage());
+ assertEquals(ErrorCode.NOT_FOUND, authException.getErrorCode());
+ assertNull(authException.getCause());
+ assertNotNull(authException.getHttpResponse());
+ assertEquals(AuthErrorCode.USER_NOT_FOUND, authException.getAuthErrorCode());
}
}
@@ -108,8 +114,14 @@ public void testGetNonExistingUserByEmail() throws Exception {
fail("No error thrown for non existing email");
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof FirebaseAuthException);
- assertEquals(AuthHttpClient.USER_NOT_FOUND_ERROR,
- ((FirebaseAuthException) e.getCause()).getErrorCode());
+ FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
+ assertEquals(
+ "No user record found for the provided email: non.existing@definitely.non.existing",
+ authException.getMessage());
+ assertEquals(ErrorCode.NOT_FOUND, authException.getErrorCode());
+ assertNull(authException.getCause());
+ assertNotNull(authException.getHttpResponse());
+ assertEquals(AuthErrorCode.USER_NOT_FOUND, authException.getAuthErrorCode());
}
}
@@ -120,8 +132,14 @@ public void testUpdateNonExistingUser() throws Exception {
fail("No error thrown for non existing uid");
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof FirebaseAuthException);
- assertEquals(AuthHttpClient.USER_NOT_FOUND_ERROR,
- ((FirebaseAuthException) e.getCause()).getErrorCode());
+ FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
+ assertEquals(
+ "No user record found for the given identifier (USER_NOT_FOUND).",
+ authException.getMessage());
+ assertEquals(ErrorCode.NOT_FOUND, authException.getErrorCode());
+ assertNotNull(authException.getCause());
+ assertNotNull(authException.getHttpResponse());
+ assertEquals(AuthErrorCode.USER_NOT_FOUND, authException.getAuthErrorCode());
}
}
@@ -132,8 +150,14 @@ public void testDeleteNonExistingUser() throws Exception {
fail("No error thrown for non existing uid");
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof FirebaseAuthException);
- assertEquals(AuthHttpClient.USER_NOT_FOUND_ERROR,
- ((FirebaseAuthException) e.getCause()).getErrorCode());
+ FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
+ assertEquals(
+ "No user record found for the given identifier (USER_NOT_FOUND).",
+ authException.getMessage());
+ assertEquals(ErrorCode.NOT_FOUND, authException.getErrorCode());
+ assertNotNull(authException.getCause());
+ assertNotNull(authException.getHttpResponse());
+ assertEquals(AuthErrorCode.USER_NOT_FOUND, authException.getAuthErrorCode());
}
}
@@ -313,43 +337,39 @@ public void testUserLifecycle() throws Exception {
@Test
public void testLastRefreshTime() throws Exception {
RandomUser user = UserTestUtils.generateRandomUserInfo();
- UserRecord newUserRecord = auth.createUser(new UserRecord.CreateRequest()
- .setUid(user.getUid())
- .setEmail(user.getEmail())
- .setEmailVerified(false)
- .setPassword("password"));
+ UserRecord newUserRecord = temporaryUser.create(new UserRecord.CreateRequest()
+ .setUid(user.getUid())
+ .setEmail(user.getEmail())
+ .setEmailVerified(false)
+ .setPassword("password"));
- try {
- // New users should not have a lastRefreshTimestamp set.
- assertEquals(0, newUserRecord.getUserMetadata().getLastRefreshTimestamp());
-
- // Login to cause the lastRefreshTimestamp to be set.
- signInWithPassword(newUserRecord.getEmail(), "password");
-
- // Attempt to retrieve the user 3 times (with a small delay between each
- // attempt). Occassionally, this call retrieves the user data without the
- // lastLoginTime/lastRefreshTime set; possibly because it's hitting a
- // different server than the login request uses.
- UserRecord userRecord = null;
- for (int i = 0; i < 3; i++) {
- userRecord = auth.getUser(newUserRecord.getUid());
-
- if (userRecord.getUserMetadata().getLastRefreshTimestamp() != 0) {
- break;
- }
+ // New users should not have a lastRefreshTimestamp set.
+ assertEquals(0, newUserRecord.getUserMetadata().getLastRefreshTimestamp());
- TimeUnit.SECONDS.sleep((long)Math.pow(2, i));
+ // Login to cause the lastRefreshTimestamp to be set.
+ signInWithPassword(newUserRecord.getEmail(), "password");
+
+ // Attempt to retrieve the user 3 times (with a small delay between each
+ // attempt). Occasionally, this call retrieves the user data without the
+ // lastLoginTime/lastRefreshTime set; possibly because it's hitting a
+ // different server than the login request uses.
+ UserRecord userRecord = null;
+ for (int i = 0; i < 3; i++) {
+ userRecord = auth.getUser(newUserRecord.getUid());
+
+ if (userRecord.getUserMetadata().getLastRefreshTimestamp() != 0) {
+ break;
}
- // Ensure the lastRefreshTimestamp is approximately "now" (with a tollerance of 10 minutes).
- long now = System.currentTimeMillis();
- long tollerance = TimeUnit.MINUTES.toMillis(10);
- long lastRefreshTimestamp = userRecord.getUserMetadata().getLastRefreshTimestamp();
- assertTrue(now - tollerance <= lastRefreshTimestamp);
- assertTrue(lastRefreshTimestamp <= now + tollerance);
- } finally {
- auth.deleteUser(newUserRecord.getUid());
+ TimeUnit.SECONDS.sleep((long)Math.pow(2, i));
}
+
+ // Ensure the lastRefreshTimestamp is approximately "now" (with a tolerance of 10 minutes).
+ long now = System.currentTimeMillis();
+ long tolerance = TimeUnit.MINUTES.toMillis(10);
+ long lastRefreshTimestamp = userRecord.getUserMetadata().getLastRefreshTimestamp();
+ assertTrue(now - tolerance <= lastRefreshTimestamp);
+ assertTrue(lastRefreshTimestamp <= now + tolerance);
}
@Test
@@ -473,7 +493,7 @@ public void testCustomTokenWithIAM() throws Exception {
if (token == null) {
token = credentials.refreshAccessToken();
}
- FirebaseOptions options = new FirebaseOptions.Builder()
+ FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.create(token))
.setServiceAccountId(((ServiceAccountSigner) credentials).getAccount())
.setProjectId(IntegrationTestUtils.getProjectId())
@@ -507,8 +527,8 @@ public void testVerifyIdToken() throws Exception {
fail("expecting exception");
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof FirebaseAuthException);
- assertEquals(RevocationCheckDecorator.ID_TOKEN_REVOKED_ERROR,
- ((FirebaseAuthException) e.getCause()).getErrorCode());
+ assertEquals(AuthErrorCode.REVOKED_ID_TOKEN,
+ ((FirebaseAuthException) e.getCause()).getAuthErrorCode());
}
idToken = signInWithCustomToken(customToken);
decoded = auth.verifyIdTokenAsync(idToken, true).get();
@@ -541,8 +561,8 @@ public void testVerifySessionCookie() throws Exception {
fail("expecting exception");
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof FirebaseAuthException);
- assertEquals(RevocationCheckDecorator.SESSION_COOKIE_REVOKED_ERROR,
- ((FirebaseAuthException) e.getCause()).getErrorCode());
+ assertEquals(AuthErrorCode.REVOKED_SESSION_COOKIE,
+ ((FirebaseAuthException) e.getCause()).getAuthErrorCode());
}
idToken = signInWithCustomToken(customToken);
@@ -1009,7 +1029,14 @@ private void checkRecreateUser(String uid) throws Exception {
fail("No error thrown for creating user with existing ID");
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof FirebaseAuthException);
- assertEquals("uid-already-exists", ((FirebaseAuthException) e.getCause()).getErrorCode());
+ FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
+ assertEquals(ErrorCode.ALREADY_EXISTS, authException.getErrorCode());
+ assertEquals(
+ "The user with the provided uid already exists (DUPLICATE_LOCAL_ID).",
+ authException.getMessage());
+ assertNotNull(authException.getCause());
+ assertNotNull(authException.getHttpResponse());
+ assertEquals(AuthErrorCode.UID_ALREADY_EXISTS, authException.getAuthErrorCode());
}
}
diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java b/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java
index bfc8d1790..e34fede1f 100644
--- a/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java
+++ b/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java
@@ -25,10 +25,15 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import com.google.api.client.testing.http.MockHttpTransport;
+import com.google.api.client.testing.http.MockLowLevelHttpResponse;
import com.google.api.core.ApiFuture;
import com.google.common.base.Defaults;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableMap;
+import com.google.firebase.ErrorCode;
+
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.TestOnlyImplFirebaseTrampolines;
@@ -53,6 +58,11 @@ public class FirebaseAuthTest {
.setCredentials(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream()))
.build();
+ private static final FirebaseAuthException testException = new FirebaseAuthException(
+ ErrorCode.INVALID_ARGUMENT, "Test error message", null, null, null);
+ private static final long VALID_SINCE = 1494364393;
+ private static final String TEST_USER = "testUser";
+
@After
public void cleanup() {
TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
@@ -145,14 +155,14 @@ public void testProjectIdNotRequiredAtInitialization() {
assertNotNull(FirebaseAuth.getInstance(app));
}
- @Test(expected = IllegalArgumentException.class)
+ @Test(expected = NullPointerException.class)
public void testAuthExceptionNullErrorCode() {
- new FirebaseAuthException(null, "test");
+ new FirebaseAuthException(null, "test", null, null, null);
}
@Test(expected = IllegalArgumentException.class)
- public void testAuthExceptionEmptyErrorCode() {
- new FirebaseAuthException("", "test");
+ public void testAuthExceptionNullMessage() {
+ new FirebaseAuthException(ErrorCode.INTERNAL, null, null, null, null);
}
@Test
@@ -219,19 +229,48 @@ public void testVerifyIdToken() throws Exception {
assertEquals("idtoken", tokenVerifier.getLastTokenString());
}
+ @Test
+ public void testVerifyIdTokenWithRevocationCheck() throws Exception {
+ MockTokenVerifier tokenVerifier = MockTokenVerifier.fromResult(
+ getFirebaseToken(VALID_SINCE + 1000));
+ FirebaseAuth auth = getAuthForIdTokenVerificationWithRevocationCheck(tokenVerifier);
+
+ FirebaseToken firebaseToken = auth.verifyIdToken("idtoken", true);
+
+ assertEquals("testUser", firebaseToken.getUid());
+ assertEquals("idtoken", tokenVerifier.getLastTokenString());
+ }
+
+ @Test
+ public void testVerifyIdTokenWithRevocationCheckFailure() {
+ MockTokenVerifier tokenVerifier = MockTokenVerifier.fromResult(
+ getFirebaseToken(VALID_SINCE - 1000));
+ FirebaseAuth auth = getAuthForIdTokenVerificationWithRevocationCheck(tokenVerifier);
+
+ try {
+ auth.verifyIdToken("idtoken", true);
+ fail("No error thrown for revoked ID token");
+ } catch (FirebaseAuthException e) {
+ assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode());
+ assertEquals("Firebase id token is revoked.", e.getMessage());
+ assertNull(e.getCause());
+ assertNull(e.getHttpResponse());
+ assertEquals(AuthErrorCode.REVOKED_ID_TOKEN, e.getAuthErrorCode());
+ }
+
+ assertEquals("idtoken", tokenVerifier.getLastTokenString());
+ }
+
@Test
public void testVerifyIdTokenFailure() {
- MockTokenVerifier tokenVerifier = MockTokenVerifier.fromException(
- new FirebaseAuthException("TEST_CODE", "Test error message"));
+ MockTokenVerifier tokenVerifier = MockTokenVerifier.fromException(testException);
FirebaseAuth auth = getAuthForIdTokenVerification(tokenVerifier);
try {
auth.verifyIdToken("idtoken");
fail("No error thrown for invalid token");
} catch (FirebaseAuthException authException) {
- assertEquals("TEST_CODE", authException.getErrorCode());
- assertEquals("Test error message", authException.getMessage());
- assertEquals("idtoken", tokenVerifier.getLastTokenString());
+ assertSame(testException, authException);
}
}
@@ -248,8 +287,7 @@ public void testVerifyIdTokenAsync() throws Exception {
@Test
public void testVerifyIdTokenAsyncFailure() throws InterruptedException {
- MockTokenVerifier tokenVerifier = MockTokenVerifier.fromException(
- new FirebaseAuthException("TEST_CODE", "Test error message"));
+ MockTokenVerifier tokenVerifier = MockTokenVerifier.fromException(testException);
FirebaseAuth auth = getAuthForIdTokenVerification(tokenVerifier);
try {
@@ -257,16 +295,13 @@ public void testVerifyIdTokenAsyncFailure() throws InterruptedException {
fail("No error thrown for invalid token");
} catch (ExecutionException e) {
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
- assertEquals("TEST_CODE", authException.getErrorCode());
- assertEquals("Test error message", authException.getMessage());
- assertEquals("idtoken", tokenVerifier.getLastTokenString());
+ assertSame(testException, authException);
}
}
@Test
public void testVerifyIdTokenWithCheckRevokedAsyncFailure() throws InterruptedException {
- MockTokenVerifier tokenVerifier = MockTokenVerifier.fromException(
- new FirebaseAuthException("TEST_CODE", "Test error message"));
+ MockTokenVerifier tokenVerifier = MockTokenVerifier.fromException(testException);
FirebaseAuth auth = getAuthForIdTokenVerification(tokenVerifier);
try {
@@ -274,9 +309,7 @@ public void testVerifyIdTokenWithCheckRevokedAsyncFailure() throws InterruptedEx
fail("No error thrown for invalid token");
} catch (ExecutionException e) {
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
- assertEquals("TEST_CODE", authException.getErrorCode());
- assertEquals("Test error message", authException.getMessage());
- assertEquals("idtoken", tokenVerifier.getLastTokenString());
+ assertSame(testException, authException);
}
}
@@ -346,18 +379,47 @@ public void testVerifySessionCookie() throws Exception {
@Test
public void testVerifySessionCookieFailure() {
- MockTokenVerifier tokenVerifier = MockTokenVerifier.fromException(
- new FirebaseAuthException("TEST_CODE", "Test error message"));
+ MockTokenVerifier tokenVerifier = MockTokenVerifier.fromException(testException);
FirebaseAuth auth = getAuthForSessionCookieVerification(tokenVerifier);
try {
auth.verifySessionCookie("idtoken");
fail("No error thrown for invalid token");
} catch (FirebaseAuthException authException) {
- assertEquals("TEST_CODE", authException.getErrorCode());
- assertEquals("Test error message", authException.getMessage());
- assertEquals("idtoken", tokenVerifier.getLastTokenString());
+ assertSame(testException, authException);
+ }
+ }
+
+ @Test
+ public void testVerifySessionCookieWithRevocationCheck() throws Exception {
+ MockTokenVerifier tokenVerifier = MockTokenVerifier.fromResult(
+ getFirebaseToken(VALID_SINCE + 1000));
+ FirebaseAuth auth = getAuthForSessionCookieVerificationWithRevocationCheck(tokenVerifier);
+
+ FirebaseToken firebaseToken = auth.verifySessionCookie("cookie", true);
+
+ assertEquals("testUser", firebaseToken.getUid());
+ assertEquals("cookie", tokenVerifier.getLastTokenString());
+ }
+
+ @Test
+ public void testVerifySessionCookieWithRevocationCheckFailure() {
+ MockTokenVerifier tokenVerifier = MockTokenVerifier.fromResult(
+ getFirebaseToken(VALID_SINCE - 1000));
+ FirebaseAuth auth = getAuthForSessionCookieVerificationWithRevocationCheck(tokenVerifier);
+
+ try {
+ auth.verifySessionCookie("cookie", true);
+ fail("No error thrown for revoked session cookie");
+ } catch (FirebaseAuthException e) {
+ assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode());
+ assertEquals("Firebase session cookie is revoked.", e.getMessage());
+ assertNull(e.getCause());
+ assertNull(e.getHttpResponse());
+ assertEquals(AuthErrorCode.REVOKED_SESSION_COOKIE, e.getAuthErrorCode());
}
+
+ assertEquals("cookie", tokenVerifier.getLastTokenString());
}
@Test
@@ -373,8 +435,7 @@ public void testVerifySessionCookieAsync() throws Exception {
@Test
public void testVerifySessionCookieAsyncFailure() throws InterruptedException {
- MockTokenVerifier tokenVerifier = MockTokenVerifier.fromException(
- new FirebaseAuthException("TEST_CODE", "Test error message"));
+ MockTokenVerifier tokenVerifier = MockTokenVerifier.fromException(testException);
FirebaseAuth auth = getAuthForSessionCookieVerification(tokenVerifier);
try {
@@ -382,16 +443,13 @@ public void testVerifySessionCookieAsyncFailure() throws InterruptedException {
fail("No error thrown for invalid token");
} catch (ExecutionException e) {
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
- assertEquals("TEST_CODE", authException.getErrorCode());
- assertEquals("Test error message", authException.getMessage());
- assertEquals("idtoken", tokenVerifier.getLastTokenString());
+ assertSame(testException, authException);
}
}
@Test
public void testVerifySessionCookieWithCheckRevokedAsyncFailure() throws InterruptedException {
- MockTokenVerifier tokenVerifier = MockTokenVerifier.fromException(
- new FirebaseAuthException("TEST_CODE", "Test error message"));
+ MockTokenVerifier tokenVerifier = MockTokenVerifier.fromException(testException);
FirebaseAuth auth = getAuthForSessionCookieVerification(tokenVerifier);
try {
@@ -399,12 +457,24 @@ public void testVerifySessionCookieWithCheckRevokedAsyncFailure() throws Interru
fail("No error thrown for invalid token");
} catch (ExecutionException e) {
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
- assertEquals("TEST_CODE", authException.getErrorCode());
- assertEquals("Test error message", authException.getMessage());
- assertEquals("idtoken", tokenVerifier.getLastTokenString());
+ assertSame(testException, authException);
}
}
+ private FirebaseToken getFirebaseToken(String subject) {
+ return new FirebaseToken(ImmutableMap.of("sub", subject));
+ }
+
+ private FirebaseToken getFirebaseToken(long issuedAt) {
+ return new FirebaseToken(ImmutableMap.of("sub", TEST_USER, "iat", issuedAt));
+ }
+
+ FirebaseAuth getAuthForIdTokenVerificationWithRevocationCheck(
+ FirebaseTokenVerifier tokenVerifier) {
+ FirebaseApp app = getFirebaseAppForUserRetrieval();
+ return getAuthForIdTokenVerification(app, Suppliers.ofInstance(tokenVerifier));
+ }
+
private FirebaseAuth getAuthForIdTokenVerification(FirebaseTokenVerifier tokenVerifier) {
return getAuthForIdTokenVerification(Suppliers.ofInstance(tokenVerifier));
}
@@ -412,7 +482,13 @@ private FirebaseAuth getAuthForIdTokenVerification(FirebaseTokenVerifier tokenVe
private FirebaseAuth getAuthForIdTokenVerification(
Supplier extends FirebaseTokenVerifier> tokenVerifierSupplier) {
FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions);
- FirebaseUserManager userManager = FirebaseUserManager.builder().setFirebaseApp(app).build();
+ return getAuthForIdTokenVerification(app, tokenVerifierSupplier);
+ }
+
+ private FirebaseAuth getAuthForIdTokenVerification(
+ FirebaseApp app,
+ Supplier extends FirebaseTokenVerifier> tokenVerifierSupplier) {
+ FirebaseUserManager userManager = FirebaseUserManager.createUserManager(app, null);
return FirebaseAuth.builder()
.setFirebaseApp(app)
.setIdTokenVerifier(tokenVerifierSupplier)
@@ -420,6 +496,12 @@ private FirebaseAuth getAuthForIdTokenVerification(
.build();
}
+ FirebaseAuth getAuthForSessionCookieVerificationWithRevocationCheck(
+ FirebaseTokenVerifier tokenVerifier) {
+ FirebaseApp app = getFirebaseAppForUserRetrieval();
+ return getAuthForSessionCookieVerification(app, Suppliers.ofInstance(tokenVerifier));
+ }
+
private FirebaseAuth getAuthForSessionCookieVerification(FirebaseTokenVerifier tokenVerifier) {
return getAuthForSessionCookieVerification(Suppliers.ofInstance(tokenVerifier));
}
@@ -427,7 +509,13 @@ private FirebaseAuth getAuthForSessionCookieVerification(FirebaseTokenVerifier t
private FirebaseAuth getAuthForSessionCookieVerification(
Supplier extends FirebaseTokenVerifier> tokenVerifierSupplier) {
FirebaseApp app = FirebaseApp.initializeApp(firebaseOptions);
- FirebaseUserManager userManager = FirebaseUserManager.builder().setFirebaseApp(app).build();
+ return getAuthForSessionCookieVerification(app, tokenVerifierSupplier);
+ }
+
+ private FirebaseAuth getAuthForSessionCookieVerification(
+ FirebaseApp app,
+ Supplier extends FirebaseTokenVerifier> tokenVerifierSupplier) {
+ FirebaseUserManager userManager = FirebaseUserManager.createUserManager(app, null);
return FirebaseAuth.builder()
.setFirebaseApp(app)
.setCookieVerifier(tokenVerifierSupplier)
@@ -435,13 +523,22 @@ private FirebaseAuth getAuthForSessionCookieVerification(
.build();
}
+ private FirebaseApp getFirebaseAppForUserRetrieval() {
+ String getUserResponse = TestUtils.loadResource("getUser.json");
+ MockHttpTransport transport = new MockHttpTransport.Builder()
+ .setLowLevelHttpResponse(new MockLowLevelHttpResponse().setContent(getUserResponse))
+ .build();
+ return FirebaseApp.initializeApp(FirebaseOptions.builder()
+ .setCredentials(new MockGoogleCredentials("test-token"))
+ .setHttpTransport(transport)
+ .setProjectId("test-project-id")
+ .build());
+ }
+
public static TestResponseInterceptor setUserManager(
AbstractFirebaseAuth.Builder> builder, FirebaseApp app, String tenantId) {
TestResponseInterceptor interceptor = new TestResponseInterceptor();
- FirebaseUserManager userManager = FirebaseUserManager.builder()
- .setFirebaseApp(app)
- .setTenantId(tenantId)
- .build();
+ FirebaseUserManager userManager = FirebaseUserManager.createUserManager(app, tenantId);
userManager.setInterceptor(interceptor);
builder.setUserManager(Suppliers.ofInstance(userManager));
return interceptor;
diff --git a/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java b/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java
index 379da0a57..833e2ed95 100644
--- a/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java
+++ b/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java
@@ -17,6 +17,7 @@
package com.google.firebase.auth;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import com.google.api.client.auth.openidconnect.IdTokenVerifier;
@@ -30,15 +31,15 @@
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.firebase.ErrorCode;
import com.google.firebase.testing.ServiceAccount;
import java.io.IOException;
+import java.security.GeneralSecurityException;
import java.util.concurrent.TimeUnit;
import org.junit.Assert;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.ExpectedException;
public class FirebaseTokenVerifierImplTest {
@@ -55,9 +56,6 @@ public class FirebaseTokenVerifierImplTest {
private static final String TEST_TOKEN_ISSUER = "https://test.token.issuer";
- @Rule
- public ExpectedException thrown = ExpectedException.none();
-
private FirebaseTokenVerifier tokenVerifier;
private TestTokenFactory tokenFactory;
@@ -80,108 +78,188 @@ public void testVerifyToken() throws Exception {
}
@Test
- public void testVerifyTokenWithoutKeyId() throws Exception {
+ public void testVerifyTokenWithoutKeyId() {
String token = createTokenWithoutKeyId();
- thrown.expectMessage("Firebase test token has no \"kid\" claim.");
- tokenVerifier.verifyToken(token);
+ try {
+ tokenVerifier.verifyToken(token);
+ } catch (FirebaseAuthException e) {
+ String message = "Firebase test token has no \"kid\" claim. "
+ + "See https://test.doc.url for details on how to retrieve a test token.";
+ checkInvalidTokenException(e, message);
+ }
}
@Test
- public void testVerifyTokenFirebaseCustomToken() throws Exception {
+ public void testVerifyTokenFirebaseCustomToken() {
String token = createCustomToken();
- thrown.expectMessage("verifyTestToken() expects a test token, but was given a custom token.");
- tokenVerifier.verifyToken(token);
+ try {
+ tokenVerifier.verifyToken(token);
+ } catch (FirebaseAuthException e) {
+ String message = "verifyTestToken() expects a test token, but was given a custom token. "
+ + "See https://test.doc.url for details on how to retrieve a test token.";
+ checkInvalidTokenException(e, message);
+ }
}
@Test
- public void testVerifyTokenIncorrectAlgorithm() throws Exception {
+ public void testVerifyTokenIncorrectAlgorithm() {
String token = createTokenWithIncorrectAlgorithm();
- thrown.expectMessage("Firebase test token has incorrect algorithm.");
- tokenVerifier.verifyToken(token);
+ try {
+ tokenVerifier.verifyToken(token);
+ } catch (FirebaseAuthException e) {
+ String message = "Firebase test token has incorrect algorithm. "
+ + "Expected \"RS256\" but got \"HSA\". "
+ + "See https://test.doc.url for details on how to retrieve a test token.";
+ checkInvalidTokenException(e, message);
+ }
}
@Test
- public void testVerifyTokenIncorrectAudience() throws Exception {
+ public void testVerifyTokenIncorrectAudience() {
String token = createTokenWithIncorrectAudience();
- thrown.expectMessage("Firebase test token has incorrect \"aud\" (audience) claim.");
- tokenVerifier.verifyToken(token);
+ try {
+ tokenVerifier.verifyToken(token);
+ } catch (FirebaseAuthException e) {
+ String message = "Firebase test token has incorrect \"aud\" (audience) claim. "
+ + "Expected \"proj-test-101\" but got \"invalid-audience\". "
+ + "Make sure the test token comes from the same Firebase project as the service account "
+ + "used to authenticate this SDK. "
+ + "See https://test.doc.url for details on how to retrieve a test token.";
+ checkInvalidTokenException(e, message);
+ }
}
@Test
- public void testVerifyTokenIncorrectIssuer() throws Exception {
+ public void testVerifyTokenIncorrectIssuer() {
String token = createTokenWithIncorrectIssuer();
- thrown.expectMessage("Firebase test token has incorrect \"iss\" (issuer) claim.");
- tokenVerifier.verifyToken(token);
+ try {
+ tokenVerifier.verifyToken(token);
+ } catch (FirebaseAuthException e) {
+ String message = "Firebase test token has incorrect \"iss\" (issuer) claim. "
+ + "Expected \"https://test.token.issuer\" but got "
+ + "\"https://incorrect.issuer.prefix/proj-test-101\". Make sure the test token comes "
+ + "from the same Firebase project as the service account used to authenticate this SDK. "
+ + "See https://test.doc.url for details on how to retrieve a test token.";
+ checkInvalidTokenException(e, message);
+ }
}
@Test
- public void testVerifyTokenMissingSubject() throws Exception {
+ public void testVerifyTokenMissingSubject() {
String token = createTokenWithSubject(null);
- thrown.expectMessage("Firebase test token has no \"sub\" (subject) claim.");
- tokenVerifier.verifyToken(token);
+ try {
+ tokenVerifier.verifyToken(token);
+ } catch (FirebaseAuthException e) {
+ String message = "Firebase test token has no \"sub\" (subject) claim. "
+ + "See https://test.doc.url for details on how to retrieve a test token.";
+ checkInvalidTokenException(e, message);
+ }
}
@Test
- public void testVerifyTokenEmptySubject() throws Exception {
+ public void testVerifyTokenEmptySubject() {
String token = createTokenWithSubject("");
- thrown.expectMessage("Firebase test token has an empty string \"sub\" (subject) claim.");
- tokenVerifier.verifyToken(token);
+ try {
+ tokenVerifier.verifyToken(token);
+ } catch (FirebaseAuthException e) {
+ String message = "Firebase test token has an empty string \"sub\" (subject) claim. "
+ + "See https://test.doc.url for details on how to retrieve a test token.";
+ checkInvalidTokenException(e, message);
+ }
}
@Test
- public void testVerifyTokenLongSubject() throws Exception {
+ public void testVerifyTokenLongSubject() {
String token = createTokenWithSubject(Strings.repeat("a", 129));
- thrown.expectMessage(
- "Firebase test token has \"sub\" (subject) claim longer than 128 characters.");
- tokenVerifier.verifyToken(token);
+ try {
+ tokenVerifier.verifyToken(token);
+ } catch (FirebaseAuthException e) {
+ String message = "Firebase test token has \"sub\" (subject) claim longer "
+ + "than 128 characters. "
+ + "See https://test.doc.url for details on how to retrieve a test token.";
+ checkInvalidTokenException(e, message);
+ }
}
@Test
- public void testVerifyTokenIssuedAtInFuture() throws Exception {
+ public void testVerifyTokenIssuedAtInFuture() {
long tenMinutesIntoTheFuture = (TestTokenFactory.CLOCK.currentTimeMillis() / 1000)
+ TimeUnit.MINUTES.toSeconds(10);
String token = createTokenWithTimestamps(
tenMinutesIntoTheFuture,
tenMinutesIntoTheFuture + TimeUnit.HOURS.toSeconds(1));
- thrown.expectMessage("Firebase test token has expired or is not yet valid.");
- tokenVerifier.verifyToken(token);
+ try {
+ tokenVerifier.verifyToken(token);
+ } catch (FirebaseAuthException e) {
+ String message = "Firebase test token is not yet valid. "
+ + "See https://test.doc.url for details on how to retrieve a test token.";
+ checkInvalidTokenException(e, message);
+ }
}
@Test
- public void testVerifyTokenExpired() throws Exception {
+ public void testVerifyTokenExpired() {
long twoHoursInPast = (TestTokenFactory.CLOCK.currentTimeMillis() / 1000)
- TimeUnit.HOURS.toSeconds(2);
String token = createTokenWithTimestamps(
twoHoursInPast,
twoHoursInPast + TimeUnit.HOURS.toSeconds(1));
- thrown.expectMessage("Firebase test token has expired or is not yet valid.");
- tokenVerifier.verifyToken(token);
+ try {
+ tokenVerifier.verifyToken(token);
+ } catch (FirebaseAuthException e) {
+ String message = "Firebase test token has expired. "
+ + "Get a fresh test token and try again. "
+ + "See https://test.doc.url for details on how to retrieve a test token.";
+ checkException(e, message, AuthErrorCode.EXPIRED_ID_TOKEN);
+ }
}
@Test
- public void testVerifyTokenIncorrectCert() throws Exception {
+ public void testVerifyTokenSignatureMismatch() {
String token = tokenFactory.createToken();
GooglePublicKeysManager publicKeysManager = newPublicKeysManager(
ServiceAccount.NONE.getCert());
FirebaseTokenVerifier tokenVerifier = newTestTokenVerifier(publicKeysManager);
- thrown.expectMessage("Failed to verify the signature of Firebase test token. "
- + "See https://test.doc.url for details on how to retrieve a test token.");
- tokenVerifier.verifyToken(token);
+ try {
+ tokenVerifier.verifyToken(token);
+ } catch (FirebaseAuthException e) {
+ String message = "Failed to verify the signature of Firebase test token. "
+ + "See https://test.doc.url for details on how to retrieve a test token.";
+ checkInvalidTokenException(e, message);
+ }
}
@Test
- public void verifyTokenCertificateError() {
+ public void testMalformedCert() {
+ String token = tokenFactory.createToken();
+ GooglePublicKeysManager publicKeysManager = newPublicKeysManager("malformed.cert");
+ FirebaseTokenVerifier tokenVerifier = newTestTokenVerifier(publicKeysManager);
+
+ try {
+ tokenVerifier.verifyToken(token);
+ } catch (FirebaseAuthException e) {
+ String message = "Error while fetching public key certificates: Could not parse certificate";
+ assertEquals(ErrorCode.UNKNOWN, e.getErrorCode());
+ assertTrue(e.getMessage().startsWith(message));
+ assertTrue(e.getCause() instanceof GeneralSecurityException);
+ assertNull(e.getHttpResponse());
+ assertEquals(AuthErrorCode.CERTIFICATE_FETCH_FAILED, e.getAuthErrorCode());
+ }
+ }
+
+ @Test
+ public void testCertificateFetchError() {
MockHttpTransport failingTransport = new MockHttpTransport() {
@Override
public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
@@ -195,26 +273,57 @@ public LowLevelHttpRequest buildRequest(String method, String url) throws IOExce
try {
idTokenVerifier.verifyToken(token);
Assert.fail("No exception thrown");
- } catch (FirebaseAuthException expected) {
- assertTrue(expected.getCause() instanceof IOException);
- assertEquals("Expected error", expected.getCause().getMessage());
+ } catch (FirebaseAuthException e) {
+ String message = "Error while fetching public key certificates: Expected error";
+ assertEquals(ErrorCode.UNKNOWN, e.getErrorCode());
+ assertEquals(message, e.getMessage());
+ assertTrue(e.getCause() instanceof IOException);
+ assertNull(e.getHttpResponse());
+ assertEquals(AuthErrorCode.CERTIFICATE_FETCH_FAILED, e.getAuthErrorCode());
}
}
@Test
- public void testLegacyCustomToken() throws Exception {
- thrown.expectMessage(
- "verifyTestToken() expects a test token, but was given a legacy custom token.");
- tokenVerifier.verifyToken(LEGACY_CUSTOM_TOKEN);
+ public void testMalformedSignature() {
+ String token = tokenFactory.createToken();
+ String[] segments = token.split("\\.");
+ token = String.format("%s.%s.%s", segments[0], segments[1], "MalformedSignature");
+
+ try {
+ tokenVerifier.verifyToken(token);
+ } catch (FirebaseAuthException e) {
+ String message = "Failed to verify the signature of Firebase test token. "
+ + "See https://test.doc.url for details on how to retrieve a test token.";
+ checkInvalidTokenException(e, message);
+ }
}
@Test
- public void testMalformedToken() throws Exception {
- thrown.expectMessage(
- "Failed to parse Firebase test token. Make sure you passed a string that represents a "
- + "complete and valid JWT. See https://test.doc.url for details on how to retrieve "
- + "a test token.");
- tokenVerifier.verifyToken("not.a.jwt");
+ public void testLegacyCustomToken() {
+ try {
+ tokenVerifier.verifyToken(LEGACY_CUSTOM_TOKEN);
+ } catch (FirebaseAuthException e) {
+ String message = "verifyTestToken() expects a test token, but was given a "
+ + "legacy custom token. "
+ + "See https://test.doc.url for details on how to retrieve a test token.";
+ checkInvalidTokenException(e, message);
+ }
+ }
+
+ @Test
+ public void testMalformedToken() {
+ try {
+ tokenVerifier.verifyToken("not.a.jwt");
+ } catch (FirebaseAuthException e) {
+ String message = "Failed to parse Firebase test token. "
+ + "Make sure you passed a string that represents a complete and valid JWT. "
+ + "See https://test.doc.url for details on how to retrieve a test token.";
+ assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode());
+ assertEquals(message, e.getMessage());
+ assertTrue(e.getCause() instanceof IllegalArgumentException);
+ assertNull(e.getHttpResponse());
+ assertEquals(AuthErrorCode.INVALID_ID_TOKEN, e.getAuthErrorCode());
+ }
}
@Test
@@ -238,7 +347,7 @@ public void testVerifyTokenDifferentTenantIds() {
.build()
.verifyToken(createTokenWithTenantId("TENANT_2"));
} catch (FirebaseAuthException e) {
- assertEquals(FirebaseTokenVerifierImpl.TENANT_ID_MISMATCH_ERROR, e.getErrorCode());
+ assertEquals(AuthErrorCode.TENANT_ID_MISMATCH, e.getAuthErrorCode());
assertEquals(
"The tenant ID ('TENANT_2') of the token did not match the expected value ('TENANT_1')",
e.getMessage());
@@ -253,7 +362,7 @@ public void testVerifyTokenMissingTenantId() {
.build()
.verifyToken(tokenFactory.createToken());
} catch (FirebaseAuthException e) {
- assertEquals(FirebaseTokenVerifierImpl.TENANT_ID_MISMATCH_ERROR, e.getErrorCode());
+ assertEquals(AuthErrorCode.TENANT_ID_MISMATCH, e.getAuthErrorCode());
assertEquals(
"The tenant ID ('') of the token did not match the expected value ('TENANT_ID')",
e.getMessage());
@@ -267,7 +376,7 @@ public void testVerifyTokenUnexpectedTenantId() {
.build()
.verifyToken(createTokenWithTenantId("TENANT_ID"));
} catch (FirebaseAuthException e) {
- assertEquals(FirebaseTokenVerifierImpl.TENANT_ID_MISMATCH_ERROR, e.getErrorCode());
+ assertEquals(AuthErrorCode.TENANT_ID_MISMATCH, e.getAuthErrorCode());
assertEquals(
"The tenant ID ('TENANT_ID') of the token did not match the expected value ('')",
e.getMessage());
@@ -323,13 +432,8 @@ private GooglePublicKeysManager newPublicKeysManager(HttpTransport transport) {
}
private FirebaseTokenVerifier newTestTokenVerifier(GooglePublicKeysManager publicKeysManager) {
- return FirebaseTokenVerifierImpl.builder()
- .setShortName("test token")
- .setMethod("verifyTestToken()")
- .setDocUrl("https://test.doc.url")
- .setJsonFactory(TestTokenFactory.JSON_FACTORY)
+ return fullyPopulatedBuilder()
.setPublicKeysManager(publicKeysManager)
- .setIdTokenVerifier(newIdTokenVerifier())
.build();
}
@@ -340,6 +444,8 @@ private FirebaseTokenVerifierImpl.Builder fullyPopulatedBuilder() {
.setDocUrl("https://test.doc.url")
.setJsonFactory(TestTokenFactory.JSON_FACTORY)
.setPublicKeysManager(newPublicKeysManager(ServiceAccount.EDITOR.getCert()))
+ .setInvalidTokenErrorCode(AuthErrorCode.INVALID_ID_TOKEN)
+ .setExpiredTokenErrorCode(AuthErrorCode.EXPIRED_ID_TOKEN)
.setIdTokenVerifier(newIdTokenVerifier());
}
@@ -396,6 +502,18 @@ private String createTokenWithTimestamps(long issuedAtSeconds, long expirationSe
return tokenFactory.createToken(payload);
}
+ private void checkInvalidTokenException(FirebaseAuthException e, String message) {
+ checkException(e, message, AuthErrorCode.INVALID_ID_TOKEN);
+ }
+
+ private void checkException(FirebaseAuthException e, String message, AuthErrorCode errorCode) {
+ assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode());
+ assertEquals(message, e.getMessage());
+ assertNull(e.getCause());
+ assertNull(e.getHttpResponse());
+ assertEquals(errorCode, e.getAuthErrorCode());
+ }
+
private String createTokenWithTenantId(String tenantId) {
Payload payload = tokenFactory.createTokenPayload();
payload.set("firebase", ImmutableMap.of("tenant", tenantId));
diff --git a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java
index 7012ef6bf..772f9ba8d 100644
--- a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java
+++ b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java
@@ -40,18 +40,17 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
+import com.google.firebase.ErrorCode;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.TestOnlyImplFirebaseTrampolines;
import com.google.firebase.auth.FirebaseUserManager.EmailLinkType;
-import com.google.firebase.auth.internal.AuthHttpClient;
import com.google.firebase.auth.multitenancy.TenantAwareFirebaseAuth;
import com.google.firebase.auth.multitenancy.TenantManager;
import com.google.firebase.internal.SdkUtils;
import com.google.firebase.testing.MultiRequestMockHttpTransport;
import com.google.firebase.testing.TestResponseInterceptor;
import com.google.firebase.testing.TestUtils;
-
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
@@ -60,7 +59,6 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import org.junit.After;
@@ -92,6 +90,10 @@ public class FirebaseUserManagerTest {
private static final String TENANTS_BASE_URL = PROJECT_BASE_URL + "/tenants";
+ private static final String SAML_RESPONSE = TestUtils.loadResource("saml.json");
+
+ private static final String OIDC_RESPONSE = TestUtils.loadResource("oidc.json");
+
@After
public void tearDown() {
TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
@@ -99,7 +101,7 @@ public void tearDown() {
@Test
public void testProjectIdRequired() {
- FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+ FirebaseApp.initializeApp(FirebaseOptions.builder()
.setCredentials(credentials)
.build());
FirebaseAuth auth = FirebaseAuth.getInstance();
@@ -133,7 +135,12 @@ public void testGetUserWithNotFoundError() throws Exception {
} catch (ExecutionException e) {
assertThat(e.getCause(), instanceOf(FirebaseAuthException.class));
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
- assertEquals(AuthHttpClient.USER_NOT_FOUND_ERROR, authException.getErrorCode());
+ assertEquals(ErrorCode.NOT_FOUND, authException.getErrorCode());
+ assertEquals(
+ "No user record found for the provided user ID: testuser", authException.getMessage());
+ assertNull(authException.getCause());
+ assertNotNull(authException.getHttpResponse());
+ assertEquals(AuthErrorCode.USER_NOT_FOUND, authException.getAuthErrorCode());
}
}
@@ -156,7 +163,13 @@ public void testGetUserByEmailWithNotFoundError() throws Exception {
} catch (ExecutionException e) {
assertThat(e.getCause(), instanceOf(FirebaseAuthException.class));
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
- assertEquals(AuthHttpClient.USER_NOT_FOUND_ERROR, authException.getErrorCode());
+ assertEquals(ErrorCode.NOT_FOUND, authException.getErrorCode());
+ assertEquals(
+ "No user record found for the provided email: testuser@example.com",
+ authException.getMessage());
+ assertNull(authException.getCause());
+ assertNotNull(authException.getHttpResponse());
+ assertEquals(AuthErrorCode.USER_NOT_FOUND, authException.getAuthErrorCode());
}
}
@@ -179,13 +192,19 @@ public void testGetUserByPhoneNumberWithNotFoundError() throws Exception {
} catch (ExecutionException e) {
assertThat(e.getCause(), instanceOf(FirebaseAuthException.class));
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
- assertEquals(AuthHttpClient.USER_NOT_FOUND_ERROR, authException.getErrorCode());
+ assertEquals(ErrorCode.NOT_FOUND, authException.getErrorCode());
+ assertEquals(
+ "No user record found for the provided phone number: +1234567890",
+ authException.getMessage());
+ assertNull(authException.getCause());
+ assertNotNull(authException.getHttpResponse());
+ assertEquals(AuthErrorCode.USER_NOT_FOUND, authException.getAuthErrorCode());
}
}
@Test
public void testGetUsersExceeds100() throws Exception {
- FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+ FirebaseApp.initializeApp(FirebaseOptions.builder()
.setCredentials(credentials)
.build());
List identifiers = new ArrayList<>();
@@ -203,7 +222,7 @@ public void testGetUsersExceeds100() throws Exception {
@Test
public void testGetUsersNull() throws Exception {
- FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+ FirebaseApp.initializeApp(FirebaseOptions.builder()
.setCredentials(credentials)
.build());
try {
@@ -262,7 +281,7 @@ public void testGetUsersMultipleIdentifierTypes() throws Exception {
).replace("'", "\""));
UidIdentifier doesntExist = new UidIdentifier("this-uid-doesnt-exist");
- List ids = ImmutableList.of(
+ List ids = ImmutableList.of(
new UidIdentifier("uid1"),
new EmailIdentifier("user2@example.com"),
new PhoneIdentifier("+15555550003"),
@@ -284,7 +303,7 @@ private Collection userRecordsToUids(Collection userRecords)
}
@Test
- public void testInvalidUidIdentifier() throws Exception {
+ public void testInvalidUidIdentifier() {
try {
new UidIdentifier("too long " + Strings.repeat(".", 128));
fail("No error thrown for invalid uid");
@@ -294,7 +313,7 @@ public void testInvalidUidIdentifier() throws Exception {
}
@Test
- public void testInvalidEmailIdentifier() throws Exception {
+ public void testInvalidEmailIdentifier() {
try {
new EmailIdentifier("invalid email addr");
fail("No error thrown for invalid email");
@@ -304,7 +323,7 @@ public void testInvalidEmailIdentifier() throws Exception {
}
@Test
- public void testInvalidPhoneIdentifier() throws Exception {
+ public void testInvalidPhoneIdentifier() {
try {
new PhoneIdentifier("invalid phone number");
fail("No error thrown for invalid phone number");
@@ -314,7 +333,7 @@ public void testInvalidPhoneIdentifier() throws Exception {
}
@Test
- public void testInvalidProviderIdentifier() throws Exception {
+ public void testInvalidProviderIdentifier() {
try {
new ProviderIdentifier("", "valid-uid");
fail("No error thrown for invalid provider id");
@@ -437,8 +456,8 @@ public void testDeleteUser() throws Exception {
}
@Test
- public void testDeleteUsersExceeds1000() throws Exception {
- FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+ public void testDeleteUsersExceeds1000() {
+ FirebaseApp.initializeApp(FirebaseOptions.builder()
.setCredentials(credentials)
.build());
List ids = new ArrayList<>();
@@ -454,8 +473,8 @@ public void testDeleteUsersExceeds1000() throws Exception {
}
@Test
- public void testDeleteUsersInvalidId() throws Exception {
- FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+ public void testDeleteUsersInvalidId() {
+ FirebaseApp.initializeApp(FirebaseOptions.builder()
.setCredentials(credentials)
.build());
try {
@@ -773,9 +792,15 @@ public void call(FirebaseAuth auth) throws Exception {
MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
FirebaseAuth auth = getRetryDisabledAuth(response);
+ Map codes = ImmutableMap.of(
+ 302, ErrorCode.UNKNOWN,
+ 400, ErrorCode.INVALID_ARGUMENT,
+ 401, ErrorCode.UNAUTHENTICATED,
+ 404, ErrorCode.NOT_FOUND,
+ 500, ErrorCode.INTERNAL);
// Test for common HTTP error codes
- for (int code : ImmutableList.of(302, 400, 401, 404, 500)) {
+ for (int code : codes.keySet()) {
for (UserManagerOp operation : operations) {
// Need to reset these every iteration
response.setContent("{}");
@@ -786,15 +811,17 @@ public void call(FirebaseAuth auth) throws Exception {
} catch (ExecutionException e) {
assertThat(e.getCause(), instanceOf(FirebaseAuthException.class));
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
- String msg = String.format("Unexpected HTTP response with status: %d; body: {}", code);
+ assertEquals(codes.get(code), authException.getErrorCode());
+ String msg = String.format("Unexpected HTTP response with status: %d\n{}", code);
assertEquals(msg, authException.getMessage());
- assertThat(authException.getCause(), instanceOf(HttpResponseException.class));
- assertEquals(AuthHttpClient.INTERNAL_ERROR, authException.getErrorCode());
+ assertTrue(authException.getCause() instanceof HttpResponseException);
+ assertNotNull(authException.getHttpResponse());
+ assertNull(authException.getAuthErrorCode());
}
}
}
- // Test error payload parsing
+ // Test error payload with code
for (UserManagerOp operation : operations) {
response.setContent("{\"error\": {\"message\": \"USER_NOT_FOUND\"}}");
response.setStatusCode(500);
@@ -804,9 +831,33 @@ public void call(FirebaseAuth auth) throws Exception {
} catch (ExecutionException e) {
assertThat(e.getCause().toString(), e.getCause(), instanceOf(FirebaseAuthException.class));
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
- assertEquals("Firebase Auth service responded with an error", authException.getMessage());
- assertThat(authException.getCause(), instanceOf(HttpResponseException.class));
- assertEquals(AuthHttpClient.USER_NOT_FOUND_ERROR, authException.getErrorCode());
+ assertEquals(ErrorCode.NOT_FOUND, authException.getErrorCode());
+ assertEquals(
+ "No user record found for the given identifier (USER_NOT_FOUND).",
+ authException.getMessage());
+ assertTrue(authException.getCause() instanceof HttpResponseException);
+ assertNotNull(authException.getHttpResponse());
+ assertEquals(AuthErrorCode.USER_NOT_FOUND, authException.getAuthErrorCode());
+ }
+ }
+
+ // Test error payload with code and details
+ for (UserManagerOp operation : operations) {
+ response.setContent("{\"error\": {\"message\": \"USER_NOT_FOUND: Extra details\"}}");
+ response.setStatusCode(500);
+ try {
+ operation.call(auth);
+ fail("No error thrown for HTTP error");
+ } catch (ExecutionException e) {
+ assertTrue(e.getCause().toString(), e.getCause() instanceof FirebaseAuthException);
+ FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
+ assertEquals(ErrorCode.NOT_FOUND, authException.getErrorCode());
+ assertEquals(
+ "No user record found for the given identifier (USER_NOT_FOUND): Extra details",
+ authException.getMessage());
+ assertTrue(authException.getCause() instanceof HttpResponseException);
+ assertNotNull(authException.getHttpResponse());
+ assertEquals(AuthErrorCode.USER_NOT_FOUND, authException.getAuthErrorCode());
}
}
}
@@ -820,8 +871,12 @@ public void testGetUserMalformedJsonError() throws Exception {
} catch (ExecutionException e) {
assertThat(e.getCause(), instanceOf(FirebaseAuthException.class));
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
- assertThat(authException.getCause(), instanceOf(IOException.class));
- assertEquals(AuthHttpClient.INTERNAL_ERROR, authException.getErrorCode());
+ assertEquals(ErrorCode.UNKNOWN, authException.getErrorCode());
+ assertTrue(
+ authException.getMessage().startsWith("Error while parsing HTTP response: "));
+ assertTrue(authException.getCause() instanceof IOException);
+ assertNotNull(authException.getHttpResponse());
+ assertNull(authException.getAuthErrorCode());
}
}
@@ -837,10 +892,12 @@ public void testGetUserUnexpectedHttpError() throws Exception {
} catch (ExecutionException e) {
assertThat(e.getCause(), instanceOf(FirebaseAuthException.class));
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
- assertThat(authException.getCause(), instanceOf(HttpResponseException.class));
- assertEquals("Unexpected HTTP response with status: 500; body: {\"not\" json}",
+ assertEquals(ErrorCode.INTERNAL, authException.getErrorCode());
+ assertEquals("Unexpected HTTP response with status: 500\n{\"not\" json}",
authException.getMessage());
- assertEquals(AuthHttpClient.INTERNAL_ERROR, authException.getErrorCode());
+ assertTrue(authException.getCause() instanceof HttpResponseException);
+ assertNotNull(authException.getHttpResponse());
+ assertNull(authException.getAuthErrorCode());
}
}
@@ -848,7 +905,7 @@ public void testGetUserUnexpectedHttpError() throws Exception {
public void testTimeout() throws Exception {
MockHttpTransport transport = new MultiRequestMockHttpTransport(ImmutableList.of(
new MockLowLevelHttpResponse().setContent(TestUtils.loadResource("getUser.json"))));
- FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+ FirebaseApp.initializeApp(FirebaseOptions.builder()
.setCredentials(credentials)
.setProjectId("test-project-id")
.setHttpTransport(transport)
@@ -1375,8 +1432,33 @@ public void testHttpErrorWithCode() {
userManager.getEmailActionLink(EmailLinkType.PASSWORD_RESET, "test@example.com", null);
fail("No exception thrown for HTTP error");
} catch (FirebaseAuthException e) {
- assertEquals("unauthorized-continue-uri", e.getErrorCode());
- assertThat(e.getCause(), instanceOf(HttpResponseException.class));
+ assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode());
+ assertEquals(
+ "The domain of the continue URL is not whitelisted (UNAUTHORIZED_DOMAIN).",
+ e.getMessage());
+ assertEquals(AuthErrorCode.UNAUTHORIZED_CONTINUE_URL, e.getAuthErrorCode());
+ assertTrue(e.getCause() instanceof HttpResponseException);
+ assertNotNull(e.getHttpResponse());
+ }
+ }
+
+ @Test
+ public void testHttpErrorWithUnknownCode() {
+ String content = "{\"error\": {\"message\": \"SOMETHING_NEW\"}}";
+ MockLowLevelHttpResponse response = new MockLowLevelHttpResponse()
+ .setContent(content)
+ .setStatusCode(500);
+ FirebaseAuth auth = getRetryDisabledAuth(response);
+ FirebaseUserManager userManager = auth.getUserManager();
+ try {
+ userManager.getEmailActionLink(EmailLinkType.PASSWORD_RESET, "test@example.com", null);
+ fail("No exception thrown for HTTP error");
+ } catch (FirebaseAuthException e) {
+ assertEquals(ErrorCode.INTERNAL, e.getErrorCode());
+ assertEquals("Unexpected HTTP response with status: 500\n" + content, e.getMessage());
+ assertNull(e.getAuthErrorCode());
+ assertTrue(e.getCause() instanceof HttpResponseException);
+ assertNotNull(e.getHttpResponse());
}
}
@@ -1391,15 +1473,17 @@ public void testUnexpectedHttpError() {
userManager.getEmailActionLink(EmailLinkType.PASSWORD_RESET, "test@example.com", null);
fail("No exception thrown for HTTP error");
} catch (FirebaseAuthException e) {
- assertEquals("internal-error", e.getErrorCode());
- assertThat(e.getCause(), instanceOf(HttpResponseException.class));
+ assertEquals(ErrorCode.INTERNAL, e.getErrorCode());
+ assertEquals("Unexpected HTTP response with status: 500\n{}", e.getMessage());
+ assertTrue(e.getCause() instanceof HttpResponseException);
+ assertNotNull(e.getHttpResponse());
+ assertNull(e.getAuthErrorCode());
}
}
@Test
public void testCreateOidcProvider() throws Exception {
- TestResponseInterceptor interceptor = initializeAppForUserManagement(
- TestUtils.loadResource("oidc.json"));
+ TestResponseInterceptor interceptor = initializeAppForUserManagement(OIDC_RESPONSE);
OidcProviderConfig.CreateRequest createRequest =
new OidcProviderConfig.CreateRequest()
.setProviderId("oidc.provider-id")
@@ -1424,8 +1508,7 @@ public void testCreateOidcProvider() throws Exception {
@Test
public void testCreateOidcProviderAsync() throws Exception {
- TestResponseInterceptor interceptor = initializeAppForUserManagement(
- TestUtils.loadResource("oidc.json"));
+ TestResponseInterceptor interceptor = initializeAppForUserManagement(OIDC_RESPONSE);
OidcProviderConfig.CreateRequest createRequest =
new OidcProviderConfig.CreateRequest()
.setProviderId("oidc.provider-id")
@@ -1451,8 +1534,7 @@ public void testCreateOidcProviderAsync() throws Exception {
@Test
public void testCreateOidcProviderMinimal() throws Exception {
- TestResponseInterceptor interceptor = initializeAppForUserManagement(
- TestUtils.loadResource("oidc.json"));
+ TestResponseInterceptor interceptor = initializeAppForUserManagement(OIDC_RESPONSE);
// Only the 'enabled' and 'displayName' fields can be omitted from an OIDC provider config
// creation request.
OidcProviderConfig.CreateRequest createRequest =
@@ -1474,24 +1556,32 @@ public void testCreateOidcProviderMinimal() throws Exception {
}
@Test
- public void testCreateOidcProviderError() throws Exception {
- TestResponseInterceptor interceptor =
- initializeAppForUserManagementWithStatusCode(404,
- "{\"error\": {\"message\": \"INTERNAL_ERROR\"}}");
+ public void testCreateOidcProviderError() {
+ String message = "{\"error\": {\"message\": \"INTERNAL_ERROR\"}}";
+ MockLowLevelHttpResponse response = new MockLowLevelHttpResponse()
+ .setContent(message)
+ .setStatusCode(500);
+ FirebaseAuth auth = getRetryDisabledAuth(response);
OidcProviderConfig.CreateRequest createRequest =
new OidcProviderConfig.CreateRequest().setProviderId("oidc.provider-id");
+
try {
- FirebaseAuth.getInstance().createOidcProviderConfig(createRequest);
+ auth.createOidcProviderConfig(createRequest);
fail("No error thrown for invalid response");
} catch (FirebaseAuthException e) {
- assertEquals(AuthHttpClient.INTERNAL_ERROR, e.getErrorCode());
+ assertEquals(ErrorCode.INTERNAL, e.getErrorCode());
+ assertEquals(
+ "Unexpected HTTP response with status: 500\n" + message,
+ e.getMessage());
+ assertTrue(e.getCause() instanceof HttpResponseException);
+ assertNotNull(e.getHttpResponse());
+ assertNull(e.getAuthErrorCode());
}
- checkUrl(interceptor, "POST", PROJECT_BASE_URL + "/oauthIdpConfigs");
}
@Test
public void testCreateOidcProviderMissingId() throws Exception {
- initializeAppForUserManagement(TestUtils.loadResource("oidc.json"));
+ initializeAppForUserManagement(OIDC_RESPONSE);
OidcProviderConfig.CreateRequest createRequest =
new OidcProviderConfig.CreateRequest()
.setDisplayName("DISPLAY_NAME")
@@ -1509,8 +1599,7 @@ public void testCreateOidcProviderMissingId() throws Exception {
@Test
public void testTenantAwareCreateOidcProvider() throws Exception {
TestResponseInterceptor interceptor = initializeAppForTenantAwareUserManagement(
- "TENANT_ID",
- TestUtils.loadResource("oidc.json"));
+ "TENANT_ID", OIDC_RESPONSE);
OidcProviderConfig.CreateRequest createRequest =
new OidcProviderConfig.CreateRequest()
.setProviderId("oidc.provider-id")
@@ -1529,8 +1618,7 @@ public void testTenantAwareCreateOidcProvider() throws Exception {
@Test
public void testUpdateOidcProvider() throws Exception {
- TestResponseInterceptor interceptor = initializeAppForUserManagement(
- TestUtils.loadResource("oidc.json"));
+ TestResponseInterceptor interceptor = initializeAppForUserManagement(OIDC_RESPONSE);
OidcProviderConfig.UpdateRequest request =
new OidcProviderConfig.UpdateRequest("oidc.provider-id")
.setDisplayName("DISPLAY_NAME")
@@ -1554,8 +1642,7 @@ public void testUpdateOidcProvider() throws Exception {
@Test
public void testUpdateOidcProviderAsync() throws Exception {
- TestResponseInterceptor interceptor = initializeAppForUserManagement(
- TestUtils.loadResource("oidc.json"));
+ TestResponseInterceptor interceptor = initializeAppForUserManagement(OIDC_RESPONSE);
OidcProviderConfig.UpdateRequest request =
new OidcProviderConfig.UpdateRequest("oidc.provider-id")
.setDisplayName("DISPLAY_NAME")
@@ -1580,8 +1667,7 @@ public void testUpdateOidcProviderAsync() throws Exception {
@Test
public void testUpdateOidcProviderMinimal() throws Exception {
- TestResponseInterceptor interceptor = initializeAppForUserManagement(
- TestUtils.loadResource("oidc.json"));
+ TestResponseInterceptor interceptor = initializeAppForUserManagement(OIDC_RESPONSE);
OidcProviderConfig.UpdateRequest request =
new OidcProviderConfig.UpdateRequest("oidc.provider-id").setDisplayName("DISPLAY_NAME");
@@ -1599,7 +1685,7 @@ public void testUpdateOidcProviderMinimal() throws Exception {
@Test
public void testUpdateOidcProviderConfigNoValues() throws Exception {
- initializeAppForUserManagement(TestUtils.loadResource("oidc.json"));
+ initializeAppForUserManagement(OIDC_RESPONSE);
try {
FirebaseAuth.getInstance().updateOidcProviderConfig(
new OidcProviderConfig.UpdateRequest("oidc.provider-id"));
@@ -1611,25 +1697,32 @@ public void testUpdateOidcProviderConfigNoValues() throws Exception {
@Test
public void testUpdateOidcProviderConfigError() {
- TestResponseInterceptor interceptor =
- initializeAppForUserManagementWithStatusCode(404,
- "{\"error\": {\"message\": \"INTERNAL_ERROR\"}}");
+ String message = "{\"error\": {\"message\": \"INTERNAL_ERROR\"}}";
+ MockLowLevelHttpResponse response = new MockLowLevelHttpResponse()
+ .setContent(message)
+ .setStatusCode(500);
+ FirebaseAuth auth = getRetryDisabledAuth(response);
OidcProviderConfig.UpdateRequest request =
new OidcProviderConfig.UpdateRequest("oidc.provider-id").setDisplayName("DISPLAY_NAME");
+
try {
- FirebaseAuth.getInstance().updateOidcProviderConfig(request);
+ auth.updateOidcProviderConfig(request);
fail("No error thrown for invalid response");
} catch (FirebaseAuthException e) {
- assertEquals(AuthHttpClient.INTERNAL_ERROR, e.getErrorCode());
+ assertEquals(ErrorCode.INTERNAL, e.getErrorCode());
+ assertEquals(
+ "Unexpected HTTP response with status: 500\n" + message,
+ e.getMessage());
+ assertTrue(e.getCause() instanceof HttpResponseException);
+ assertNotNull(e.getHttpResponse());
+ assertNull(e.getAuthErrorCode());
}
- checkUrl(interceptor, "PATCH", PROJECT_BASE_URL + "/oauthIdpConfigs/oidc.provider-id");
}
@Test
public void testTenantAwareUpdateOidcProvider() throws Exception {
TestResponseInterceptor interceptor = initializeAppForTenantAwareUserManagement(
- "TENANT_ID",
- TestUtils.loadResource("oidc.json"));
+ "TENANT_ID", OIDC_RESPONSE);
TenantAwareFirebaseAuth tenantAwareAuth =
FirebaseAuth.getInstance().getTenantManager().getAuthForTenant("TENANT_ID");
OidcProviderConfig.UpdateRequest request =
@@ -1656,8 +1749,7 @@ public void testTenantAwareUpdateOidcProvider() throws Exception {
@Test
public void testGetOidcProviderConfig() throws Exception {
- TestResponseInterceptor interceptor = initializeAppForUserManagement(
- TestUtils.loadResource("oidc.json"));
+ TestResponseInterceptor interceptor = initializeAppForUserManagement(OIDC_RESPONSE);
OidcProviderConfig config =
FirebaseAuth.getInstance().getOidcProviderConfig("oidc.provider-id");
@@ -1669,8 +1761,7 @@ public void testGetOidcProviderConfig() throws Exception {
@Test
public void testGetOidcProviderConfigAsync() throws Exception {
- TestResponseInterceptor interceptor = initializeAppForUserManagement(
- TestUtils.loadResource("oidc.json"));
+ TestResponseInterceptor interceptor = initializeAppForUserManagement(OIDC_RESPONSE);
OidcProviderConfig config =
FirebaseAuth.getInstance().getOidcProviderConfigAsync("oidc.provider-id").get();
@@ -1682,7 +1773,7 @@ public void testGetOidcProviderConfigAsync() throws Exception {
@Test
public void testGetOidcProviderConfigMissingId() throws Exception {
- initializeAppForUserManagement(TestUtils.loadResource("oidc.json"));
+ initializeAppForUserManagement(OIDC_RESPONSE);
try {
FirebaseAuth.getInstance().getOidcProviderConfig(null);
@@ -1694,7 +1785,7 @@ public void testGetOidcProviderConfigMissingId() throws Exception {
@Test
public void testGetOidcProviderConfigInvalidId() throws Exception {
- initializeAppForUserManagement(TestUtils.loadResource("oidc.json"));
+ initializeAppForUserManagement(OIDC_RESPONSE);
try {
FirebaseAuth.getInstance().getOidcProviderConfig("saml.invalid-oidc-provider-id");
@@ -1705,7 +1796,7 @@ public void testGetOidcProviderConfigInvalidId() throws Exception {
}
@Test
- public void testGetOidcProviderConfigWithNotFoundError() throws Exception {
+ public void testGetOidcProviderConfigWithNotFoundError() {
TestResponseInterceptor interceptor =
initializeAppForUserManagementWithStatusCode(404,
"{\"error\": {\"message\": \"CONFIGURATION_NOT_FOUND\"}}");
@@ -1713,7 +1804,14 @@ public void testGetOidcProviderConfigWithNotFoundError() throws Exception {
FirebaseAuth.getInstance().getOidcProviderConfig("oidc.provider-id");
fail("No error thrown for invalid response");
} catch (FirebaseAuthException e) {
- assertEquals(AuthHttpClient.CONFIGURATION_NOT_FOUND_ERROR, e.getErrorCode());
+ assertEquals(ErrorCode.NOT_FOUND, e.getErrorCode());
+ assertEquals(
+ "No IdP configuration found corresponding to the provided identifier "
+ + "(CONFIGURATION_NOT_FOUND).",
+ e.getMessage());
+ assertTrue(e.getCause() instanceof HttpResponseException);
+ assertNotNull(e.getHttpResponse());
+ assertEquals(AuthErrorCode.CONFIGURATION_NOT_FOUND, e.getAuthErrorCode());
}
checkUrl(interceptor, "GET", PROJECT_BASE_URL + "/oauthIdpConfigs/oidc.provider-id");
}
@@ -1721,8 +1819,7 @@ public void testGetOidcProviderConfigWithNotFoundError() throws Exception {
@Test
public void testGetTenantAwareOidcProviderConfig() throws Exception {
TestResponseInterceptor interceptor = initializeAppForTenantAwareUserManagement(
- "TENANT_ID",
- TestUtils.loadResource("oidc.json"));
+ "TENANT_ID", OIDC_RESPONSE);
TenantAwareFirebaseAuth tenantAwareAuth =
FirebaseAuth.getInstance().getTenantManager().getAuthForTenant("TENANT_ID");
@@ -1772,18 +1869,25 @@ public void testListOidcProviderConfigsAsync() throws Exception {
}
@Test
- public void testListOidcProviderConfigsError() throws Exception {
- TestResponseInterceptor interceptor =
- initializeAppForUserManagementWithStatusCode(404,
- "{\"error\": {\"message\": \"INTERNAL_ERROR\"}}");
+ public void testListOidcProviderConfigsError() {
+ String message = "{\"error\": {\"message\": \"INTERNAL_ERROR\"}}";
+ MockLowLevelHttpResponse response = new MockLowLevelHttpResponse()
+ .setContent(message)
+ .setStatusCode(500);
+ FirebaseAuth auth = getRetryDisabledAuth(response);
try {
- FirebaseAuth.getInstance().listOidcProviderConfigs(null, 99);
+ auth.listOidcProviderConfigs(null, 99);
fail("No error thrown for invalid response");
} catch (FirebaseAuthException e) {
- assertEquals(AuthHttpClient.INTERNAL_ERROR, e.getErrorCode());
+ assertEquals(ErrorCode.INTERNAL, e.getErrorCode());
+ assertEquals(
+ "Unexpected HTTP response with status: 500\n" + message,
+ e.getMessage());
+ assertTrue(e.getCause() instanceof HttpResponseException);
+ assertNotNull(e.getHttpResponse());
+ assertNull(e.getAuthErrorCode());
}
- checkUrl(interceptor, "GET", PROJECT_BASE_URL + "/oauthIdpConfigs");
}
@Test
@@ -1890,7 +1994,14 @@ public void testDeleteOidcProviderConfigWithNotFoundError() {
FirebaseAuth.getInstance().deleteOidcProviderConfig("oidc.UNKNOWN");
fail("No error thrown for invalid response");
} catch (FirebaseAuthException e) {
- assertEquals(AuthHttpClient.CONFIGURATION_NOT_FOUND_ERROR, e.getErrorCode());
+ assertEquals(ErrorCode.NOT_FOUND, e.getErrorCode());
+ assertEquals(
+ "No IdP configuration found corresponding to the provided identifier "
+ + "(CONFIGURATION_NOT_FOUND).",
+ e.getMessage());
+ assertTrue(e.getCause() instanceof HttpResponseException);
+ assertNotNull(e.getHttpResponse());
+ assertEquals(AuthErrorCode.CONFIGURATION_NOT_FOUND, e.getAuthErrorCode());
}
checkUrl(interceptor, "DELETE", PROJECT_BASE_URL + "/oauthIdpConfigs/oidc.UNKNOWN");
}
@@ -1912,8 +2023,7 @@ public void testTenantAwareDeleteOidcProviderConfig() throws Exception {
@Test
public void testCreateSamlProvider() throws Exception {
- TestResponseInterceptor interceptor = initializeAppForUserManagement(
- TestUtils.loadResource("saml.json"));
+ TestResponseInterceptor interceptor = initializeAppForUserManagement(SAML_RESPONSE);
SamlProviderConfig.CreateRequest createRequest =
new SamlProviderConfig.CreateRequest()
.setProviderId("saml.provider-id")
@@ -1958,8 +2068,7 @@ public void testCreateSamlProvider() throws Exception {
@Test
public void testCreateSamlProviderAsync() throws Exception {
- TestResponseInterceptor interceptor = initializeAppForUserManagement(
- TestUtils.loadResource("saml.json"));
+ TestResponseInterceptor interceptor = initializeAppForUserManagement(SAML_RESPONSE);
SamlProviderConfig.CreateRequest createRequest =
new SamlProviderConfig.CreateRequest()
.setProviderId("saml.provider-id")
@@ -2005,8 +2114,7 @@ public void testCreateSamlProviderAsync() throws Exception {
@Test
public void testCreateSamlProviderMinimal() throws Exception {
- TestResponseInterceptor interceptor = initializeAppForUserManagement(
- TestUtils.loadResource("saml.json"));
+ TestResponseInterceptor interceptor = initializeAppForUserManagement(SAML_RESPONSE);
// Only the 'enabled', 'displayName', and 'signRequest' fields can be omitted from a SAML
// provider config creation request.
SamlProviderConfig.CreateRequest createRequest =
@@ -2046,23 +2154,31 @@ public void testCreateSamlProviderMinimal() throws Exception {
@Test
public void testCreateSamlProviderError() {
- TestResponseInterceptor interceptor =
- initializeAppForUserManagementWithStatusCode(404,
- "{\"error\": {\"message\": \"INTERNAL_ERROR\"}}");
+ String message = "{\"error\": {\"message\": \"INTERNAL_ERROR\"}}";
+ MockLowLevelHttpResponse response = new MockLowLevelHttpResponse()
+ .setContent(message)
+ .setStatusCode(500);
+ FirebaseAuth auth = getRetryDisabledAuth(response);
SamlProviderConfig.CreateRequest createRequest =
new SamlProviderConfig.CreateRequest().setProviderId("saml.provider-id");
+
try {
- FirebaseAuth.getInstance().createSamlProviderConfig(createRequest);
+ auth.createSamlProviderConfig(createRequest);
fail("No error thrown for invalid response");
} catch (FirebaseAuthException e) {
- assertEquals(AuthHttpClient.INTERNAL_ERROR, e.getErrorCode());
+ assertEquals(ErrorCode.INTERNAL, e.getErrorCode());
+ assertEquals(
+ "Unexpected HTTP response with status: 500\n" + message,
+ e.getMessage());
+ assertTrue(e.getCause() instanceof HttpResponseException);
+ assertNotNull(e.getHttpResponse());
+ assertNull(e.getAuthErrorCode());
}
- checkUrl(interceptor, "POST", PROJECT_BASE_URL + "/inboundSamlConfigs");
}
@Test
public void testCreateSamlProviderMissingId() throws Exception {
- initializeAppForUserManagement(TestUtils.loadResource("saml.json"));
+ initializeAppForUserManagement(SAML_RESPONSE);
SamlProviderConfig.CreateRequest createRequest =
new SamlProviderConfig.CreateRequest()
.setDisplayName("DISPLAY_NAME")
@@ -2084,8 +2200,7 @@ public void testCreateSamlProviderMissingId() throws Exception {
@Test
public void testTenantAwareCreateSamlProvider() throws Exception {
TestResponseInterceptor interceptor = initializeAppForTenantAwareUserManagement(
- "TENANT_ID",
- TestUtils.loadResource("saml.json"));
+ "TENANT_ID", SAML_RESPONSE);
SamlProviderConfig.CreateRequest createRequest =
new SamlProviderConfig.CreateRequest()
.setProviderId("saml.provider-id")
@@ -2108,8 +2223,7 @@ public void testTenantAwareCreateSamlProvider() throws Exception {
@Test
public void testUpdateSamlProvider() throws Exception {
- TestResponseInterceptor interceptor = initializeAppForUserManagement(
- TestUtils.loadResource("saml.json"));
+ TestResponseInterceptor interceptor = initializeAppForUserManagement(SAML_RESPONSE);
SamlProviderConfig.UpdateRequest updateRequest =
new SamlProviderConfig.UpdateRequest("saml.provider-id")
.setDisplayName("DISPLAY_NAME")
@@ -2156,8 +2270,7 @@ public void testUpdateSamlProvider() throws Exception {
@Test
public void testUpdateSamlProviderAsync() throws Exception {
- TestResponseInterceptor interceptor = initializeAppForUserManagement(
- TestUtils.loadResource("saml.json"));
+ TestResponseInterceptor interceptor = initializeAppForUserManagement(SAML_RESPONSE);
SamlProviderConfig.UpdateRequest updateRequest =
new SamlProviderConfig.UpdateRequest("saml.provider-id")
.setDisplayName("DISPLAY_NAME")
@@ -2205,8 +2318,7 @@ public void testUpdateSamlProviderAsync() throws Exception {
@Test
public void testUpdateSamlProviderMinimal() throws Exception {
- TestResponseInterceptor interceptor = initializeAppForUserManagement(
- TestUtils.loadResource("saml.json"));
+ TestResponseInterceptor interceptor = initializeAppForUserManagement(SAML_RESPONSE);
SamlProviderConfig.UpdateRequest request =
new SamlProviderConfig.UpdateRequest("saml.provider-id").setDisplayName("DISPLAY_NAME");
@@ -2224,7 +2336,7 @@ public void testUpdateSamlProviderMinimal() throws Exception {
@Test
public void testUpdateSamlProviderConfigNoValues() throws Exception {
- initializeAppForUserManagement(TestUtils.loadResource("saml.json"));
+ initializeAppForUserManagement(SAML_RESPONSE);
try {
FirebaseAuth.getInstance().updateSamlProviderConfig(
new SamlProviderConfig.UpdateRequest("saml.provider-id"));
@@ -2235,26 +2347,33 @@ public void testUpdateSamlProviderConfigNoValues() throws Exception {
}
@Test
- public void testUpdateSamlProviderConfigError() throws Exception {
- TestResponseInterceptor interceptor =
- initializeAppForUserManagementWithStatusCode(404,
- "{\"error\": {\"message\": \"INTERNAL_ERROR\"}}");
+ public void testUpdateSamlProviderConfigError() {
+ String message = "{\"error\": {\"message\": \"INTERNAL_ERROR\"}}";
+ MockLowLevelHttpResponse response = new MockLowLevelHttpResponse()
+ .setContent(message)
+ .setStatusCode(500);
+ FirebaseAuth auth = getRetryDisabledAuth(response);
SamlProviderConfig.UpdateRequest request =
new SamlProviderConfig.UpdateRequest("saml.provider-id").setDisplayName("DISPLAY_NAME");
+
try {
- FirebaseAuth.getInstance().updateSamlProviderConfig(request);
+ auth.updateSamlProviderConfig(request);
fail("No error thrown for invalid response");
} catch (FirebaseAuthException e) {
- assertEquals(AuthHttpClient.INTERNAL_ERROR, e.getErrorCode());
+ assertEquals(ErrorCode.INTERNAL, e.getErrorCode());
+ assertEquals(
+ "Unexpected HTTP response with status: 500\n" + message,
+ e.getMessage());
+ assertTrue(e.getCause() instanceof HttpResponseException);
+ assertNotNull(e.getHttpResponse());
+ assertNull(e.getAuthErrorCode());
}
- checkUrl(interceptor, "PATCH", PROJECT_BASE_URL + "/inboundSamlConfigs/saml.provider-id");
}
@Test
public void testTenantAwareUpdateSamlProvider() throws Exception {
TestResponseInterceptor interceptor = initializeAppForTenantAwareUserManagement(
- "TENANT_ID",
- TestUtils.loadResource("saml.json"));
+ "TENANT_ID", SAML_RESPONSE);
TenantAwareFirebaseAuth tenantAwareAuth =
FirebaseAuth.getInstance().getTenantManager().getAuthForTenant("TENANT_ID");
SamlProviderConfig.UpdateRequest updateRequest =
@@ -2286,8 +2405,7 @@ public void testTenantAwareUpdateSamlProvider() throws Exception {
@Test
public void testGetSamlProviderConfig() throws Exception {
- TestResponseInterceptor interceptor = initializeAppForUserManagement(
- TestUtils.loadResource("saml.json"));
+ TestResponseInterceptor interceptor = initializeAppForUserManagement(SAML_RESPONSE);
SamlProviderConfig config =
FirebaseAuth.getInstance().getSamlProviderConfig("saml.provider-id");
@@ -2299,8 +2417,7 @@ public void testGetSamlProviderConfig() throws Exception {
@Test
public void testGetSamlProviderConfigAsync() throws Exception {
- TestResponseInterceptor interceptor = initializeAppForUserManagement(
- TestUtils.loadResource("saml.json"));
+ TestResponseInterceptor interceptor = initializeAppForUserManagement(SAML_RESPONSE);
SamlProviderConfig config =
FirebaseAuth.getInstance().getSamlProviderConfigAsync("saml.provider-id").get();
@@ -2324,7 +2441,7 @@ public void testGetSamlProviderConfigMissingId() throws Exception {
@Test
public void testGetSamlProviderConfigInvalidId() throws Exception {
- initializeAppForUserManagement(TestUtils.loadResource("saml.json"));
+ initializeAppForUserManagement(SAML_RESPONSE);
try {
FirebaseAuth.getInstance().getSamlProviderConfig("oidc.invalid-saml-provider-id");
@@ -2343,7 +2460,14 @@ public void testGetSamlProviderConfigWithNotFoundError() {
FirebaseAuth.getInstance().getSamlProviderConfig("saml.provider-id");
fail("No error thrown for invalid response");
} catch (FirebaseAuthException e) {
- assertEquals(AuthHttpClient.CONFIGURATION_NOT_FOUND_ERROR, e.getErrorCode());
+ assertEquals(ErrorCode.NOT_FOUND, e.getErrorCode());
+ assertEquals(
+ "No IdP configuration found corresponding to the provided identifier "
+ + "(CONFIGURATION_NOT_FOUND).",
+ e.getMessage());
+ assertTrue(e.getCause() instanceof HttpResponseException);
+ assertNotNull(e.getHttpResponse());
+ assertEquals(AuthErrorCode.CONFIGURATION_NOT_FOUND, e.getAuthErrorCode());
}
checkUrl(interceptor, "GET", PROJECT_BASE_URL + "/inboundSamlConfigs/saml.provider-id");
}
@@ -2351,8 +2475,7 @@ public void testGetSamlProviderConfigWithNotFoundError() {
@Test
public void testGetTenantAwareSamlProviderConfig() throws Exception {
TestResponseInterceptor interceptor = initializeAppForTenantAwareUserManagement(
- "TENANT_ID",
- TestUtils.loadResource("saml.json"));
+ "TENANT_ID", SAML_RESPONSE);
TenantAwareFirebaseAuth tenantAwareAuth =
FirebaseAuth.getInstance().getTenantManager().getAuthForTenant("TENANT_ID");
@@ -2403,18 +2526,25 @@ public void testListSamlProviderConfigsAsync() throws Exception {
}
@Test
- public void testListSamlProviderConfigsError() throws Exception {
- TestResponseInterceptor interceptor =
- initializeAppForUserManagementWithStatusCode(404,
- "{\"error\": {\"message\": \"INTERNAL_ERROR\"}}");
+ public void testListSamlProviderConfigsError() {
+ String message = "{\"error\": {\"message\": \"INTERNAL_ERROR\"}}";
+ MockLowLevelHttpResponse response = new MockLowLevelHttpResponse()
+ .setContent(message)
+ .setStatusCode(500);
+ FirebaseAuth auth = getRetryDisabledAuth(response);
try {
- FirebaseAuth.getInstance().listSamlProviderConfigs(null, 99);
+ auth.listSamlProviderConfigs(null, 99);
fail("No error thrown for invalid response");
} catch (FirebaseAuthException e) {
- assertEquals(AuthHttpClient.INTERNAL_ERROR, e.getErrorCode());
+ assertEquals(ErrorCode.INTERNAL, e.getErrorCode());
+ assertEquals(
+ "Unexpected HTTP response with status: 500\n" + message,
+ e.getMessage());
+ assertTrue(e.getCause() instanceof HttpResponseException);
+ assertNotNull(e.getHttpResponse());
+ assertNull(e.getAuthErrorCode());
}
- checkUrl(interceptor, "GET", PROJECT_BASE_URL + "/inboundSamlConfigs");
}
@Test
@@ -2521,7 +2651,14 @@ public void testDeleteSamlProviderConfigWithNotFoundError() {
FirebaseAuth.getInstance().deleteSamlProviderConfig("saml.UNKNOWN");
fail("No error thrown for invalid response");
} catch (FirebaseAuthException e) {
- assertEquals(AuthHttpClient.CONFIGURATION_NOT_FOUND_ERROR, e.getErrorCode());
+ assertEquals(ErrorCode.NOT_FOUND, e.getErrorCode());
+ assertEquals(
+ "No IdP configuration found corresponding to the provided identifier "
+ + "(CONFIGURATION_NOT_FOUND).",
+ e.getMessage());
+ assertTrue(e.getCause() instanceof HttpResponseException);
+ assertNotNull(e.getHttpResponse());
+ assertEquals(AuthErrorCode.CONFIGURATION_NOT_FOUND, e.getAuthErrorCode());
}
checkUrl(interceptor, "DELETE", PROJECT_BASE_URL + "/inboundSamlConfigs/saml.UNKNOWN");
}
@@ -2543,7 +2680,7 @@ public void testTenantAwareDeleteSamlProviderConfig() throws Exception {
private static TestResponseInterceptor initializeAppForUserManagementWithStatusCode(
int statusCode, String response) {
- FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+ FirebaseApp.initializeApp(FirebaseOptions.builder()
.setCredentials(credentials)
.setHttpTransport(
new MockHttpTransport.Builder().setLowLevelHttpResponse(
@@ -2579,7 +2716,7 @@ private static void initializeAppWithResponses(String... responses) {
mocks.add(new MockLowLevelHttpResponse().setContent(response));
}
MockHttpTransport transport = new MultiRequestMockHttpTransport(mocks);
- FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+ FirebaseApp.initializeApp(FirebaseOptions.builder()
.setCredentials(credentials)
.setHttpTransport(transport)
.setProjectId("test-project-id")
@@ -2597,9 +2734,8 @@ private static FirebaseAuth getRetryDisabledAuth(MockLowLevelHttpResponse respon
final MockHttpTransport transport = new MockHttpTransport.Builder()
.setLowLevelHttpResponse(response)
.build();
- final FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+ final FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
.setCredentials(credentials)
- .setProjectId("test-project-id")
.setHttpTransport(transport)
.build());
return FirebaseAuth.builder()
@@ -2608,9 +2744,10 @@ private static FirebaseAuth getRetryDisabledAuth(MockLowLevelHttpResponse respon
@Override
public FirebaseUserManager get() {
return FirebaseUserManager.builder()
- .setFirebaseApp(app)
- .setHttpRequestFactory(transport.createRequestFactory())
- .build();
+ .setProjectId("test-project-id")
+ .setHttpRequestFactory(transport.createRequestFactory())
+ .setJsonFactory(Utils.getDefaultJsonFactory())
+ .build();
}
})
.build();
@@ -2680,13 +2817,7 @@ private static void checkRequestHeaders(TestResponseInterceptor interceptor) {
private static void checkUrl(TestResponseInterceptor interceptor, String method, String url) {
HttpRequest request = interceptor.getResponse().getRequest();
- if (method.equals("PATCH")) {
- assertEquals("PATCH",
- request.getHeaders().getFirstHeaderStringValue("X-HTTP-Method-Override"));
- assertEquals("POST", request.getRequestMethod());
- } else {
- assertEquals(method, request.getRequestMethod());
- }
+ assertEquals(method, request.getRequestMethod());
assertEquals(url, request.getUrl().toString().split("\\?")[0]);
}
diff --git a/src/test/java/com/google/firebase/auth/ProviderConfigTestUtils.java b/src/test/java/com/google/firebase/auth/ProviderConfigTestUtils.java
index c01ac6501..e027882c0 100644
--- a/src/test/java/com/google/firebase/auth/ProviderConfigTestUtils.java
+++ b/src/test/java/com/google/firebase/auth/ProviderConfigTestUtils.java
@@ -21,7 +21,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import com.google.firebase.auth.internal.AuthHttpClient;
+import com.google.firebase.ErrorCode;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
@@ -36,8 +36,9 @@ public static void assertOidcProviderConfigDoesNotExist(
fail("No error thrown for getting a deleted OIDC provider config.");
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof FirebaseAuthException);
- assertEquals(AuthHttpClient.CONFIGURATION_NOT_FOUND_ERROR,
- ((FirebaseAuthException) e.getCause()).getErrorCode());
+ FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
+ assertEquals(ErrorCode.NOT_FOUND, authException.getErrorCode());
+ assertEquals(AuthErrorCode.CONFIGURATION_NOT_FOUND, authException.getAuthErrorCode());
}
}
@@ -48,8 +49,9 @@ public static void assertSamlProviderConfigDoesNotExist(
fail("No error thrown for getting a deleted SAML provider config.");
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof FirebaseAuthException);
- assertEquals(AuthHttpClient.CONFIGURATION_NOT_FOUND_ERROR,
- ((FirebaseAuthException) e.getCause()).getErrorCode());
+ FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
+ assertEquals(ErrorCode.NOT_FOUND, authException.getErrorCode());
+ assertEquals(AuthErrorCode.CONFIGURATION_NOT_FOUND, authException.getAuthErrorCode());
}
}
diff --git a/src/test/java/com/google/firebase/auth/UserTestUtils.java b/src/test/java/com/google/firebase/auth/UserTestUtils.java
index aa86e6e19..8d6f7fc32 100644
--- a/src/test/java/com/google/firebase/auth/UserTestUtils.java
+++ b/src/test/java/com/google/firebase/auth/UserTestUtils.java
@@ -20,7 +20,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import com.google.firebase.auth.internal.AuthHttpClient;
+import com.google.firebase.ErrorCode;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@@ -37,7 +37,7 @@ public static void assertUserDoesNotExist(AbstractFirebaseAuth firebaseAuth, Str
fail("No error thrown for getting a user which was expected to be absent.");
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof FirebaseAuthException);
- assertEquals(AuthHttpClient.USER_NOT_FOUND_ERROR,
+ assertEquals(ErrorCode.NOT_FOUND,
((FirebaseAuthException) e.getCause()).getErrorCode());
}
}
diff --git a/src/test/java/com/google/firebase/auth/internal/CryptoSignersTest.java b/src/test/java/com/google/firebase/auth/internal/CryptoSignersTest.java
index bc98add3c..32f9fd543 100644
--- a/src/test/java/com/google/firebase/auth/internal/CryptoSignersTest.java
+++ b/src/test/java/com/google/firebase/auth/internal/CryptoSignersTest.java
@@ -18,10 +18,14 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.google.api.client.googleapis.util.Utils;
+import com.google.api.client.http.HttpRequest;
+import com.google.api.client.http.HttpStatusCodes;
import com.google.api.client.testing.http.MockHttpTransport;
import com.google.api.client.testing.http.MockLowLevelHttpResponse;
import com.google.auth.ServiceAccountSigner;
@@ -29,21 +33,22 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.BaseEncoding;
+import com.google.firebase.ErrorCode;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.TestOnlyImplFirebaseTrampolines;
+import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.auth.MockGoogleCredentials;
import com.google.firebase.testing.MultiRequestMockHttpTransport;
import com.google.firebase.testing.ServiceAccount;
import com.google.firebase.testing.TestResponseInterceptor;
-import java.io.IOException;
import org.junit.After;
import org.junit.Test;
public class CryptoSignersTest {
@Test
- public void testServiceAccountCryptoSigner() throws IOException {
+ public void testServiceAccountCryptoSigner() throws Exception {
ServiceAccountCredentials credentials = ServiceAccountCredentials.fromStream(
ServiceAccount.EDITOR.asStream());
byte[] expected = credentials.sign("foo".getBytes());
@@ -63,7 +68,7 @@ public void testInvalidServiceAccountCryptoSigner() {
}
@Test
- public void testIAMCryptoSigner() throws IOException {
+ public void testIAMCryptoSigner() throws Exception {
String signature = BaseEncoding.base64().encode("signed-bytes".getBytes());
String response = Utils.getDefaultJsonFactory().toString(
ImmutableMap.of("signature", signature));
@@ -84,6 +89,29 @@ public void testIAMCryptoSigner() throws IOException {
assertEquals(url, interceptor.getResponse().getRequest().getUrl().toString());
}
+ @Test
+ public void testIAMCryptoSignerHttpError() {
+ String error = "{\"error\": {\"status\":\"INTERNAL\", \"message\": \"Test error\"}}";
+ MockHttpTransport transport = new MockHttpTransport.Builder()
+ .setLowLevelHttpResponse(new MockLowLevelHttpResponse()
+ .setStatusCode(HttpStatusCodes.STATUS_CODE_SERVER_ERROR)
+ .setContent(error))
+ .build();
+ CryptoSigners.IAMCryptoSigner signer = new CryptoSigners.IAMCryptoSigner(
+ transport.createRequestFactory(),
+ Utils.getDefaultJsonFactory(),
+ "test-service-account@iam.gserviceaccount.com");
+ try {
+ signer.sign("foo".getBytes());
+ } catch (FirebaseAuthException e) {
+ assertEquals(ErrorCode.INTERNAL, e.getErrorCode());
+ assertEquals("Test error", e.getMessage());
+ assertNotNull(e.getCause());
+ assertNotNull(e.getHttpResponse());
+ assertNull(e.getAuthErrorCode());
+ }
+ }
+
@Test
public void testInvalidIAMCryptoSigner() {
try {
@@ -119,7 +147,7 @@ public void testInvalidIAMCryptoSigner() {
}
@Test
- public void testMetadataService() throws IOException {
+ public void testMetadataService() throws Exception {
String signature = BaseEncoding.base64().encode("signed-bytes".getBytes());
String response = Utils.getDefaultJsonFactory().toString(
ImmutableMap.of("signature", signature));
@@ -127,7 +155,7 @@ public void testMetadataService() throws IOException {
ImmutableList.of(
new MockLowLevelHttpResponse().setContent("metadata-server@iam.gserviceaccount.com"),
new MockLowLevelHttpResponse().setContent(response)));
- FirebaseOptions options = new FirebaseOptions.Builder()
+ FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(new MockGoogleCredentials("test-token"))
.setHttpTransport(transport)
.build();
@@ -142,11 +170,13 @@ public void testMetadataService() throws IOException {
assertArrayEquals("signed-bytes".getBytes(), data);
final String url = "https://iam.googleapis.com/v1/projects/-/serviceAccounts/"
+ "metadata-server@iam.gserviceaccount.com:signBlob";
- assertEquals(url, interceptor.getResponse().getRequest().getUrl().toString());
+ HttpRequest request = interceptor.getResponse().getRequest();
+ assertEquals(url, request.getUrl().toString());
+ assertEquals("Bearer test-token", request.getHeaders().getAuthorization());
}
@Test
- public void testExplicitServiceAccountEmail() throws IOException {
+ public void testExplicitServiceAccountEmail() throws Exception {
String signature = BaseEncoding.base64().encode("signed-bytes".getBytes());
String response = Utils.getDefaultJsonFactory().toString(
ImmutableMap.of("signature", signature));
@@ -155,7 +185,7 @@ public void testExplicitServiceAccountEmail() throws IOException {
MockHttpTransport transport = new MultiRequestMockHttpTransport(
ImmutableList.of(
new MockLowLevelHttpResponse().setContent(response)));
- FirebaseOptions options = new FirebaseOptions.Builder()
+ FirebaseOptions options = FirebaseOptions.builder()
.setServiceAccountId("explicit-service-account@iam.gserviceaccount.com")
.setCredentials(new MockGoogleCredentialsWithSigner("test-token"))
.setHttpTransport(transport)
@@ -170,13 +200,15 @@ public void testExplicitServiceAccountEmail() throws IOException {
assertArrayEquals("signed-bytes".getBytes(), data);
final String url = "https://iam.googleapis.com/v1/projects/-/serviceAccounts/"
+ "explicit-service-account@iam.gserviceaccount.com:signBlob";
- assertEquals(url, interceptor.getResponse().getRequest().getUrl().toString());
+ HttpRequest request = interceptor.getResponse().getRequest();
+ assertEquals(url, request.getUrl().toString());
+ assertEquals("Bearer test-token", request.getHeaders().getAuthorization());
}
@Test
- public void testCredentialsWithSigner() throws IOException {
+ public void testCredentialsWithSigner() throws Exception {
// Should fall back to signing-enabled credential
- FirebaseOptions options = new FirebaseOptions.Builder()
+ FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(new MockGoogleCredentialsWithSigner("test-token"))
.build();
FirebaseApp app = FirebaseApp.initializeApp(options, "customApp");
diff --git a/src/test/java/com/google/firebase/auth/internal/FirebaseTokenFactoryTest.java b/src/test/java/com/google/firebase/auth/internal/FirebaseTokenFactoryTest.java
index 1c85ceeee..875c781e9 100644
--- a/src/test/java/com/google/firebase/auth/internal/FirebaseTokenFactoryTest.java
+++ b/src/test/java/com/google/firebase/auth/internal/FirebaseTokenFactoryTest.java
@@ -29,8 +29,9 @@
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.firebase.ErrorCode;
+import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.testing.TestUtils;
-import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
@@ -158,12 +159,12 @@ private static class TestCryptoSigner implements CryptoSigner {
}
@Override
- public byte[] sign(byte[] payload) throws IOException {
+ public byte[] sign(byte[] payload) throws FirebaseAuthException {
try {
return SecurityUtils.sign(SecurityUtils.getSha256WithRsaSignatureAlgorithm(),
privateKey, payload);
} catch (GeneralSecurityException e) {
- throw new IOException(e);
+ throw new FirebaseAuthException(ErrorCode.UNKNOWN, "Failed to sign token", e, null, null);
}
}
diff --git a/src/test/java/com/google/firebase/auth/multitenancy/FirebaseTenantClientTest.java b/src/test/java/com/google/firebase/auth/multitenancy/FirebaseTenantClientTest.java
index 36660dc28..4bc84cb4d 100644
--- a/src/test/java/com/google/firebase/auth/multitenancy/FirebaseTenantClientTest.java
+++ b/src/test/java/com/google/firebase/auth/multitenancy/FirebaseTenantClientTest.java
@@ -18,6 +18,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -26,6 +28,7 @@
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpRequest;
+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.testing.http.MockHttpTransport;
@@ -33,13 +36,14 @@
import com.google.auth.oauth2.GoogleCredentials;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
+import com.google.firebase.ErrorCode;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.TestOnlyImplFirebaseTrampolines;
+import com.google.firebase.auth.AuthErrorCode;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.auth.MockGoogleCredentials;
-import com.google.firebase.auth.internal.AuthHttpClient;
import com.google.firebase.internal.SdkUtils;
import com.google.firebase.testing.MultiRequestMockHttpTransport;
import com.google.firebase.testing.TestResponseInterceptor;
@@ -90,7 +94,12 @@ public void testGetTenantWithNotFoundError() {
FirebaseAuth.getInstance().getTenantManager().getTenant("UNKNOWN");
fail("No error thrown for invalid response");
} catch (FirebaseAuthException e) {
- assertEquals(AuthHttpClient.TENANT_NOT_FOUND_ERROR, e.getErrorCode());
+ assertEquals(ErrorCode.NOT_FOUND, e.getErrorCode());
+ assertEquals(
+ "No tenant found for the given identifier (TENANT_NOT_FOUND).", e.getMessage());
+ assertTrue(e.getCause() instanceof HttpResponseException);
+ assertNotNull(e.getHttpResponse());
+ assertEquals(AuthErrorCode.TENANT_NOT_FOUND, e.getAuthErrorCode());
}
checkUrl(interceptor, "GET", TENANTS_BASE_URL + "/UNKNOWN");
}
@@ -183,16 +192,21 @@ public void testCreateTenantMinimal() throws Exception {
@Test
public void testCreateTenantError() {
- TestResponseInterceptor interceptor =
- initializeAppForTenantManagementWithStatusCode(404,
- "{\"error\": {\"message\": \"INTERNAL_ERROR\"}}");
+ String message = "{\"error\": {\"message\": \"INTERNAL_ERROR\"}}";
+ TenantManager tenantManager = createRetryDisabledTenantManager(new MockLowLevelHttpResponse()
+ .setStatusCode(500)
+ .setContent(message));
+
try {
- FirebaseAuth.getInstance().getTenantManager().createTenant(new Tenant.CreateRequest());
+ tenantManager.createTenant(new Tenant.CreateRequest());
fail("No error thrown for invalid response");
} catch (FirebaseAuthException e) {
- assertEquals(AuthHttpClient.INTERNAL_ERROR, e.getErrorCode());
+ assertEquals(ErrorCode.INTERNAL, e.getErrorCode());
+ assertEquals("Unexpected HTTP response with status: 500\n" + message, e.getMessage());
+ assertTrue(e.getCause() instanceof HttpResponseException);
+ assertNotNull(e.getHttpResponse());
+ assertNull(e.getAuthErrorCode());
}
- checkUrl(interceptor, "POST", TENANTS_BASE_URL);
}
@Test
@@ -252,18 +266,23 @@ public void testUpdateTenantNoValues() throws Exception {
@Test
public void testUpdateTenantError() {
- TestResponseInterceptor interceptor =
- initializeAppForTenantManagementWithStatusCode(404,
- "{\"error\": {\"message\": \"INTERNAL_ERROR\"}}");
+ String message = "{\"error\": {\"message\": \"INTERNAL_ERROR\"}}";
+ TenantManager tenantManager = createRetryDisabledTenantManager(new MockLowLevelHttpResponse()
+ .setStatusCode(500)
+ .setContent(message));
Tenant.UpdateRequest request =
new Tenant.UpdateRequest("TENANT_1").setDisplayName("DISPLAY_NAME");
+
try {
- FirebaseAuth.getInstance().getTenantManager().updateTenant(request);
+ tenantManager.updateTenant(request);
fail("No error thrown for invalid response");
} catch (FirebaseAuthException e) {
- assertEquals(AuthHttpClient.INTERNAL_ERROR, e.getErrorCode());
+ assertEquals(ErrorCode.INTERNAL, e.getErrorCode());
+ assertEquals("Unexpected HTTP response with status: 500\n" + message, e.getMessage());
+ assertTrue(e.getCause() instanceof HttpResponseException);
+ assertNotNull(e.getHttpResponse());
+ assertNull(e.getAuthErrorCode());
}
- checkUrl(interceptor, "PATCH", TENANTS_BASE_URL + "/TENANT_1");
}
@Test
@@ -285,7 +304,10 @@ public void testDeleteTenantWithNotFoundError() {
FirebaseAuth.getInstance().getTenantManager().deleteTenant("UNKNOWN");
fail("No error thrown for invalid response");
} catch (FirebaseAuthException e) {
- assertEquals(AuthHttpClient.TENANT_NOT_FOUND_ERROR, e.getErrorCode());
+ assertEquals(ErrorCode.NOT_FOUND, e.getErrorCode());
+ assertEquals("No tenant found for the given identifier (TENANT_NOT_FOUND).", e.getMessage());
+ assertTrue(e.getCause() instanceof HttpResponseException);
+ assertNotNull(e.getHttpResponse());
}
checkUrl(interceptor, "DELETE", TENANTS_BASE_URL + "/UNKNOWN");
}
@@ -308,18 +330,22 @@ private static void checkRequestHeaders(TestResponseInterceptor interceptor) {
private static void checkUrl(TestResponseInterceptor interceptor, String method, String url) {
HttpRequest request = interceptor.getResponse().getRequest();
- if (method.equals("PATCH")) {
- assertEquals("PATCH",
- request.getHeaders().getFirstHeaderStringValue("X-HTTP-Method-Override"));
- assertEquals("POST", request.getRequestMethod());
- } else {
- assertEquals(method, request.getRequestMethod());
- }
+ assertEquals(method, request.getRequestMethod());
assertEquals(url, request.getUrl().toString().split("\\?")[0]);
}
private static TestResponseInterceptor initializeAppForTenantManagement(String... responses) {
- initializeAppWithResponses(responses);
+ List mocks = new ArrayList<>();
+ for (String response : responses) {
+ mocks.add(new MockLowLevelHttpResponse().setContent(response));
+ }
+ MockHttpTransport transport = new MultiRequestMockHttpTransport(mocks);
+ FirebaseApp.initializeApp(FirebaseOptions.builder()
+ .setCredentials(credentials)
+ .setHttpTransport(transport)
+ .setProjectId("test-project-id")
+ .build());
+
TestResponseInterceptor interceptor = new TestResponseInterceptor();
FirebaseAuth.getInstance().getTenantManager().setInterceptor(interceptor);
return interceptor;
@@ -327,7 +353,7 @@ private static TestResponseInterceptor initializeAppForTenantManagement(String..
private static TestResponseInterceptor initializeAppForTenantManagementWithStatusCode(
int statusCode, String response) {
- FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+ FirebaseApp.initializeApp(FirebaseOptions.builder()
.setCredentials(credentials)
.setHttpTransport(
new MockHttpTransport.Builder()
@@ -341,17 +367,16 @@ private static TestResponseInterceptor initializeAppForTenantManagementWithStatu
return interceptor;
}
- private static void initializeAppWithResponses(String... responses) {
- List mocks = new ArrayList<>();
- for (String response : responses) {
- mocks.add(new MockLowLevelHttpResponse().setContent(response));
- }
- MockHttpTransport transport = new MultiRequestMockHttpTransport(mocks);
- FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+ private static TenantManager createRetryDisabledTenantManager(MockLowLevelHttpResponse response) {
+ MockHttpTransport transport = new MockHttpTransport.Builder()
+ .setLowLevelHttpResponse(response)
+ .build();
+ FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
.setCredentials(credentials)
- .setHttpTransport(transport)
- .setProjectId("test-project-id")
.build());
+ FirebaseTenantClient tenantClient = new FirebaseTenantClient(
+ "test-project-id", Utils.getDefaultJsonFactory(), transport.createRequestFactory());
+ return new TenantManager(app, tenantClient);
}
private static GenericJson parseRequestContent(TestResponseInterceptor interceptor)
diff --git a/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthIT.java b/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthIT.java
index 61a841902..fd8e4af83 100644
--- a/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthIT.java
+++ b/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthIT.java
@@ -39,6 +39,7 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.firebase.FirebaseApp;
+import com.google.firebase.auth.AuthErrorCode;
import com.google.firebase.auth.ExportedUserRecord;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseAuthException;
@@ -257,8 +258,8 @@ public void testVerifyTokenWithWrongTenantAwareClient() throws Exception {
fail("No error thrown for verifying a token with the wrong tenant-aware client");
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof FirebaseAuthException);
- assertEquals("tenant-id-mismatch",
- ((FirebaseAuthException) e.getCause()).getErrorCode());
+ assertEquals(AuthErrorCode.TENANT_ID_MISMATCH,
+ ((FirebaseAuthException) e.getCause()).getAuthErrorCode());
}
}
diff --git a/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthTest.java b/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthTest.java
index df7bcf00d..db0099791 100644
--- a/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthTest.java
+++ b/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthTest.java
@@ -28,6 +28,7 @@
import com.google.api.client.testing.http.MockHttpTransport;
import com.google.api.client.testing.http.MockLowLevelHttpResponse;
import com.google.common.base.Suppliers;
+import com.google.firebase.ErrorCode;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.TestOnlyImplFirebaseTrampolines;
@@ -58,7 +59,7 @@ public class TenantAwareFirebaseAuthTest {
.build();
private static final FirebaseAuthException AUTH_EXCEPTION = new FirebaseAuthException(
- "code", "reason");
+ ErrorCode.INVALID_ARGUMENT, "Test error message", null, null, null);
private static final String CREATE_COOKIE_RESPONSE = TestUtils.loadResource(
"createSessionCookie.json");
@@ -99,7 +100,7 @@ public void testCreateSessionCookieAsyncError() throws Exception {
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof FirebaseAuthException);
FirebaseAuthException cause = (FirebaseAuthException) e.getCause();
- assertEquals("code", cause.getErrorCode());
+ assertEquals(ErrorCode.INVALID_ARGUMENT, cause.getErrorCode());
}
assertNull(interceptor.getResponse());
@@ -133,7 +134,7 @@ public void testCreateSessionCookieError() {
auth.createSessionCookie("testToken", COOKIE_OPTIONS);
fail("No error thrown for invalid ID token");
} catch (FirebaseAuthException e) {
- assertEquals("code", e.getErrorCode());
+ assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode());
}
assertNull(interceptor.getResponse());
@@ -163,7 +164,7 @@ public void testVerifySessionCookieFailure() {
auth.verifySessionCookie("cookie");
fail("No error thrown for invalid token");
} catch (FirebaseAuthException authException) {
- assertEquals("code", authException.getErrorCode());
+ assertEquals(ErrorCode.INVALID_ARGUMENT, authException.getErrorCode());
assertEquals("cookie", tokenVerifier.getLastTokenString());
}
}
@@ -193,7 +194,7 @@ public void testVerifySessionCookieAsyncFailure() throws InterruptedException {
fail("No error thrown for invalid token");
} catch (ExecutionException e) {
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
- assertEquals("code", authException.getErrorCode());
+ assertEquals(ErrorCode.INVALID_ARGUMENT, authException.getErrorCode());
assertEquals("cookie", tokenVerifier.getLastTokenString());
}
}
@@ -224,7 +225,7 @@ public void testVerifySessionCookieWithCheckRevokedFailure() {
auth.verifySessionCookie("cookie", true);
fail("No error thrown for invalid token");
} catch (FirebaseAuthException e) {
- assertEquals("code", e.getErrorCode());
+ assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode());
assertEquals("cookie", tokenVerifier.getLastTokenString());
}
}
@@ -241,7 +242,7 @@ public void testVerifySessionCookieWithCheckRevokedAsyncFailure() throws Interru
fail("No error thrown for invalid token");
} catch (ExecutionException e) {
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
- assertEquals("code", authException.getErrorCode());
+ assertEquals(ErrorCode.INVALID_ARGUMENT, authException.getErrorCode());
assertEquals("cookie", tokenVerifier.getLastTokenString());
}
}
diff --git a/src/test/java/com/google/firebase/auth/multitenancy/TenantManagerIT.java b/src/test/java/com/google/firebase/auth/multitenancy/TenantManagerIT.java
index 086e25aa2..44d75e830 100644
--- a/src/test/java/com/google/firebase/auth/multitenancy/TenantManagerIT.java
+++ b/src/test/java/com/google/firebase/auth/multitenancy/TenantManagerIT.java
@@ -27,9 +27,10 @@
import com.google.api.core.ApiFutureCallback;
import com.google.api.core.ApiFutures;
import com.google.common.util.concurrent.MoreExecutors;
+import com.google.firebase.ErrorCode;
+import com.google.firebase.auth.AuthErrorCode;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseAuthException;
-import com.google.firebase.auth.internal.AuthHttpClient;
import com.google.firebase.testing.IntegrationTestUtils;
import java.util.ArrayList;
import java.util.List;
@@ -81,8 +82,9 @@ public void testTenantLifecycle() throws Exception {
fail("No error thrown for getting a deleted tenant");
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof FirebaseAuthException);
- assertEquals(AuthHttpClient.TENANT_NOT_FOUND_ERROR,
- ((FirebaseAuthException) e.getCause()).getErrorCode());
+ FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
+ assertEquals(ErrorCode.NOT_FOUND, authException.getErrorCode());
+ assertEquals(AuthErrorCode.TENANT_NOT_FOUND, authException.getAuthErrorCode());
}
}
diff --git a/src/test/java/com/google/firebase/cloud/FirestoreClientTest.java b/src/test/java/com/google/firebase/cloud/FirestoreClientTest.java
index 03627f202..08ee49ff0 100644
--- a/src/test/java/com/google/firebase/cloud/FirestoreClientTest.java
+++ b/src/test/java/com/google/firebase/cloud/FirestoreClientTest.java
@@ -12,7 +12,6 @@
import com.google.cloud.firestore.FirestoreOptions;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
-import com.google.firebase.FirebaseOptions.Builder;
import com.google.firebase.ImplFirebaseTrampolines;
import com.google.firebase.TestOnlyImplFirebaseTrampolines;
import com.google.firebase.auth.MockGoogleCredentials;
@@ -23,11 +22,10 @@
public class FirestoreClientTest {
- public static final FirestoreOptions FIRESTORE_OPTIONS = FirestoreOptions.newBuilder()
+ private static final FirestoreOptions FIRESTORE_OPTIONS = FirestoreOptions.newBuilder()
// Setting credentials is not required (they get overridden by Admin SDK), but without
// this Firestore logs an ugly warning during tests.
.setCredentials(new MockGoogleCredentials("test-token"))
- .setTimestampsInSnapshotsEnabled(true)
.build();
@After
@@ -37,7 +35,7 @@ public void tearDown() {
@Test
public void testExplicitProjectId() throws IOException {
- FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+ FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
.setProjectId("explicit-project-id")
.setFirestoreOptions(FIRESTORE_OPTIONS)
@@ -51,7 +49,7 @@ public void testExplicitProjectId() throws IOException {
@Test
public void testServiceAccountProjectId() throws IOException {
- FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+ FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
.setFirestoreOptions(FIRESTORE_OPTIONS)
.build());
@@ -64,7 +62,7 @@ public void testServiceAccountProjectId() throws IOException {
@Test
public void testFirestoreOptions() throws IOException {
- FirebaseApp app = FirebaseApp.initializeApp(new Builder()
+ FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
.setProjectId("explicit-project-id")
.setFirestoreOptions(FIRESTORE_OPTIONS)
@@ -80,11 +78,10 @@ public void testFirestoreOptions() throws IOException {
@Test
public void testFirestoreOptionsOverride() throws IOException {
- FirebaseApp app = FirebaseApp.initializeApp(new Builder()
+ FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
.setProjectId("explicit-project-id")
.setFirestoreOptions(FirestoreOptions.newBuilder()
- .setTimestampsInSnapshotsEnabled(true)
.setProjectId("other-project-id")
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
.build())
@@ -104,7 +101,7 @@ public void testFirestoreOptionsOverride() throws IOException {
@Test
public void testAppDelete() throws IOException {
- FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+ FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
.setProjectId("mock-project-id")
.setFirestoreOptions(FIRESTORE_OPTIONS)
diff --git a/src/test/java/com/google/firebase/cloud/StorageClientTest.java b/src/test/java/com/google/firebase/cloud/StorageClientTest.java
index 41535f3d9..190fad6cc 100644
--- a/src/test/java/com/google/firebase/cloud/StorageClientTest.java
+++ b/src/test/java/com/google/firebase/cloud/StorageClientTest.java
@@ -41,7 +41,7 @@ public void tearDown() {
@Test
public void testInvalidConfiguration() throws IOException {
- FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+ FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
.build());
try {
@@ -54,7 +54,7 @@ public void testInvalidConfiguration() throws IOException {
@Test
public void testInvalidBucketName() throws IOException {
- FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+ FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
.setStorageBucket("mock-bucket-name")
.build());
@@ -75,7 +75,7 @@ public void testInvalidBucketName() throws IOException {
@Test
public void testAppDelete() throws IOException {
- FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+ FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
.setStorageBucket("mock-bucket-name")
.build());
@@ -93,7 +93,7 @@ public void testAppDelete() throws IOException {
@Test
public void testNonExistingBucket() throws IOException {
- FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+ FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
.setStorageBucket("mock-bucket-name")
.build());
@@ -115,7 +115,7 @@ public void testNonExistingBucket() throws IOException {
@Test
public void testBucket() throws IOException {
- FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+ FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
.setStorageBucket("mock-bucket-name")
.build());
diff --git a/src/test/java/com/google/firebase/database/DataSnapshotTest.java b/src/test/java/com/google/firebase/database/DataSnapshotTest.java
index 0764bdbf4..083d8ce09 100644
--- a/src/test/java/com/google/firebase/database/DataSnapshotTest.java
+++ b/src/test/java/com/google/firebase/database/DataSnapshotTest.java
@@ -51,7 +51,7 @@ public class DataSnapshotTest {
@BeforeClass
public static void setUpClass() throws IOException {
testApp = FirebaseApp.initializeApp(
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
.setDatabaseUrl("https://admin-java-sdk.firebaseio.com")
.build());
diff --git a/src/test/java/com/google/firebase/database/DatabaseReferenceTest.java b/src/test/java/com/google/firebase/database/DatabaseReferenceTest.java
index 5e7fc5374..207173f74 100644
--- a/src/test/java/com/google/firebase/database/DatabaseReferenceTest.java
+++ b/src/test/java/com/google/firebase/database/DatabaseReferenceTest.java
@@ -64,7 +64,7 @@ public class DatabaseReferenceTest {
@BeforeClass
public static void setUpClass() throws IOException {
testApp = FirebaseApp.initializeApp(
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
.setDatabaseUrl(DB_URL)
.build());
diff --git a/src/test/java/com/google/firebase/database/FirebaseDatabaseTest.java b/src/test/java/com/google/firebase/database/FirebaseDatabaseTest.java
index ba7816173..2bdc6185e 100644
--- a/src/test/java/com/google/firebase/database/FirebaseDatabaseTest.java
+++ b/src/test/java/com/google/firebase/database/FirebaseDatabaseTest.java
@@ -41,12 +41,12 @@
public class FirebaseDatabaseTest {
private static final FirebaseOptions firebaseOptions =
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setCredentials(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream()))
.setDatabaseUrl("https://firebase-db-test.firebaseio.com")
.build();
private static final FirebaseOptions firebaseOptionsWithoutDatabaseUrl =
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setCredentials(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream()))
.build();
diff --git a/src/test/java/com/google/firebase/database/TestHelpers.java b/src/test/java/com/google/firebase/database/TestHelpers.java
index cfd15034d..00d9a3797 100644
--- a/src/test/java/com/google/firebase/database/TestHelpers.java
+++ b/src/test/java/com/google/firebase/database/TestHelpers.java
@@ -16,7 +16,6 @@
package com.google.firebase.database;
-import static com.cedarsoftware.util.DeepEquals.deepEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -43,6 +42,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Executors;
@@ -241,7 +241,7 @@ public static void setHijackHash(DatabaseReference ref, boolean hijackHash) {
* a true and more effective super set.
*/
public static void assertDeepEquals(Object a, Object b) {
- if (!deepEquals(a, b)) {
+ if (!Objects.deepEquals(a, b)) {
fail("Values different.\nExpected: " + a + "\nActual: " + b);
}
}
diff --git a/src/test/java/com/google/firebase/database/ValueExpectationHelper.java b/src/test/java/com/google/firebase/database/ValueExpectationHelper.java
index f7f15a03d..9732bf73b 100644
--- a/src/test/java/com/google/firebase/database/ValueExpectationHelper.java
+++ b/src/test/java/com/google/firebase/database/ValueExpectationHelper.java
@@ -18,10 +18,10 @@
import static org.junit.Assert.fail;
-import com.cedarsoftware.util.DeepEquals;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.Semaphore;
public class ValueExpectationHelper {
@@ -39,7 +39,7 @@ public void add(final Query query, final Object expected) {
public void onDataChange(DataSnapshot snapshot) {
Object result = snapshot.getValue();
// Hack to handle race condition in initial data
- if (DeepEquals.deepEquals(expected, result)) {
+ if (Objects.deepEquals(expected, result)) {
// We may pass through intermediate states, but we should end up with
// the correct
// state
diff --git a/src/test/java/com/google/firebase/database/collection/RBTreeSortedMapTest.java b/src/test/java/com/google/firebase/database/collection/RBTreeSortedMapTest.java
index 2fbc8ba90..f29ef208a 100644
--- a/src/test/java/com/google/firebase/database/collection/RBTreeSortedMapTest.java
+++ b/src/test/java/com/google/firebase/database/collection/RBTreeSortedMapTest.java
@@ -262,7 +262,7 @@ public void successorKeyIsCorrect() {
lastKey = entry.getKey();
}
if (lastKey != null) {
- assertEquals(null, map.getSuccessorKey(lastKey));
+ assertNull(map.getSuccessorKey(lastKey));
}
}
}
@@ -295,13 +295,13 @@ public int compare(Integer o1, Integer o2) {
arraycopy = arraycopy.insert(key, value);
copyWithDifferentComparator = copyWithDifferentComparator.insert(key, value);
}
- Assert.assertTrue(map.equals(copy));
- Assert.assertTrue(map.equals(arraycopy));
- Assert.assertTrue(arraycopy.equals(map));
-
- Assert.assertFalse(map.equals(copyWithDifferentComparator));
- Assert.assertFalse(map.equals(copy.remove(copy.getMaxKey())));
- Assert.assertFalse(map.equals(copy.insert(copy.getMaxKey() + 1, 1)));
- Assert.assertFalse(map.equals(arraycopy.remove(arraycopy.getMaxKey())));
+ Assert.assertEquals(map, copy);
+ Assert.assertEquals(map, arraycopy);
+ Assert.assertEquals(arraycopy, map);
+
+ Assert.assertNotEquals(map, copyWithDifferentComparator);
+ Assert.assertNotEquals(map, copy.remove(copy.getMaxKey()));
+ Assert.assertNotEquals(map, copy.insert(copy.getMaxKey() + 1, 1));
+ Assert.assertNotEquals(map, arraycopy.remove(arraycopy.getMaxKey()));
}
}
diff --git a/src/test/java/com/google/firebase/database/core/JvmAuthTokenProviderTest.java b/src/test/java/com/google/firebase/database/core/JvmAuthTokenProviderTest.java
index 7b8fe5229..9bc55950b 100644
--- a/src/test/java/com/google/firebase/database/core/JvmAuthTokenProviderTest.java
+++ b/src/test/java/com/google/firebase/database/core/JvmAuthTokenProviderTest.java
@@ -20,7 +20,6 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import com.cedarsoftware.util.DeepEquals;
import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.OAuth2Credentials;
import com.google.common.collect.ImmutableMap;
@@ -38,6 +37,7 @@
import java.util.Date;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -67,7 +67,7 @@ public void testGetToken() throws IOException, InterruptedException {
credentials.refresh();
assertEquals(1, refreshDetector.count);
- FirebaseOptions options = new FirebaseOptions.Builder()
+ FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(credentials)
.build();
FirebaseApp app = FirebaseApp.initializeApp(options);
@@ -87,7 +87,7 @@ public void testGetTokenNoRefresh() throws IOException, InterruptedException {
credentials.refresh();
assertEquals(1, refreshDetector.count);
- FirebaseOptions options = new FirebaseOptions.Builder()
+ FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(credentials)
.build();
FirebaseApp app = FirebaseApp.initializeApp(options);
@@ -103,7 +103,7 @@ public void testGetTokenNoRefresh() throws IOException, InterruptedException {
public void testGetTokenWithAuthOverrides() throws InterruptedException, IOException {
MockGoogleCredentials credentials = new MockGoogleCredentials("mock-token");
Map auth = ImmutableMap.of("uid", "test");
- FirebaseOptions options = new FirebaseOptions.Builder()
+ FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(credentials)
.setDatabaseAuthVariableOverride(auth)
.build();
@@ -119,11 +119,11 @@ public void testGetTokenWithAuthOverrides() throws InterruptedException, IOExcep
public void testGetTokenError() throws InterruptedException {
MockGoogleCredentials credentials = new MockGoogleCredentials("mock-token") {
@Override
- public AccessToken refreshAccessToken() throws IOException {
+ public AccessToken refreshAccessToken() {
throw new RuntimeException("Test error");
}
};
- FirebaseOptions options = new FirebaseOptions.Builder()
+ FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(credentials)
.build();
FirebaseApp app = FirebaseApp.initializeApp(options);
@@ -139,13 +139,13 @@ public void testAddTokenChangeListener() throws IOException {
final AtomicInteger counter = new AtomicInteger(0);
MockGoogleCredentials credentials = new MockGoogleCredentials() {
@Override
- public AccessToken refreshAccessToken() throws IOException {
+ public AccessToken refreshAccessToken() {
Date expiry = new Date(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1));
return new AccessToken("token-" + counter.getAndIncrement(), expiry);
}
};
- FirebaseOptions options = new FirebaseOptions.Builder()
+ FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(credentials)
.build();
FirebaseApp app = FirebaseApp.initializeApp(options);
@@ -172,7 +172,7 @@ public void onTokenChange(String token) {
@Test
public void testTokenChangeListenerThread() throws InterruptedException, IOException {
MockGoogleCredentials credentials = new MockGoogleCredentials();
- FirebaseOptions options = new FirebaseOptions.Builder()
+ FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(credentials)
.build();
FirebaseApp app = FirebaseApp.initializeApp(options);
@@ -210,12 +210,12 @@ public void testTokenAutoRefresh() throws InterruptedException {
final Semaphore semaphore = new Semaphore(0);
credentials.addChangeListener(new OAuth2Credentials.CredentialsChangedListener() {
@Override
- public void onChanged(OAuth2Credentials credentials) throws IOException {
+ public void onChanged(OAuth2Credentials credentials) {
semaphore.release();
}
});
- FirebaseOptions options = new FirebaseOptions.Builder()
+ FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(credentials)
.build();
FirebaseApp app = FirebaseApp.initializeApp(options);
@@ -234,8 +234,8 @@ private void assertToken(String token, String expectedToken, Map
assertEquals(expectedToken, map.get("token"));
- Map auth = (Map)map.get("auth");
- DeepEquals.deepEquals(expectedAuth, auth);
+ Map auth = (Map) map.get("auth");
+ assertTrue(Objects.deepEquals(expectedAuth, auth));
}
private static class TestGetTokenListener
@@ -271,7 +271,7 @@ private static class TokenRefreshDetector
private int count = 0;
@Override
- public void onChanged(OAuth2Credentials credentials) throws IOException {
+ public void onChanged(OAuth2Credentials credentials) {
count++;
}
}
diff --git a/src/test/java/com/google/firebase/database/core/JvmPlatformTest.java b/src/test/java/com/google/firebase/database/core/JvmPlatformTest.java
index c6912916f..044b583b3 100644
--- a/src/test/java/com/google/firebase/database/core/JvmPlatformTest.java
+++ b/src/test/java/com/google/firebase/database/core/JvmPlatformTest.java
@@ -56,7 +56,7 @@ protected ThreadFactory getThreadFactory() {
return Executors.defaultThreadFactory();
}
};
- FirebaseOptions options = new FirebaseOptions.Builder()
+ FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream()))
.setThreadManager(threadManager)
.build();
@@ -80,7 +80,7 @@ protected ThreadFactory getThreadFactory() {
@Test
public void userAgentHasCorrectParts() {
- FirebaseOptions options = new FirebaseOptions.Builder()
+ FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream()))
.build();
FirebaseApp app = FirebaseApp.initializeApp(options, "userAgentApp");
diff --git a/src/test/java/com/google/firebase/database/core/RepoTest.java b/src/test/java/com/google/firebase/database/core/RepoTest.java
index ec9696e55..9c2281d4c 100644
--- a/src/test/java/com/google/firebase/database/core/RepoTest.java
+++ b/src/test/java/com/google/firebase/database/core/RepoTest.java
@@ -61,7 +61,7 @@ public class RepoTest {
@BeforeClass
public static void setUpClass() throws IOException {
FirebaseApp testApp = FirebaseApp.initializeApp(
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
.setDatabaseUrl("https://admin-java-sdk.firebaseio.com")
.build());
diff --git a/src/test/java/com/google/firebase/database/core/SyncPointTest.java b/src/test/java/com/google/firebase/database/core/SyncPointTest.java
index 91be0cce7..158a803e4 100644
--- a/src/test/java/com/google/firebase/database/core/SyncPointTest.java
+++ b/src/test/java/com/google/firebase/database/core/SyncPointTest.java
@@ -68,7 +68,7 @@ public class SyncPointTest {
@BeforeClass
public static void setUpClass() throws IOException {
testApp = FirebaseApp.initializeApp(
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
.setDatabaseUrl("https://admin-java-sdk.firebaseio.com")
.build());
diff --git a/src/test/java/com/google/firebase/database/core/persistence/DefaultPersistenceManagerTest.java b/src/test/java/com/google/firebase/database/core/persistence/DefaultPersistenceManagerTest.java
index 99450944c..342700a3d 100644
--- a/src/test/java/com/google/firebase/database/core/persistence/DefaultPersistenceManagerTest.java
+++ b/src/test/java/com/google/firebase/database/core/persistence/DefaultPersistenceManagerTest.java
@@ -56,7 +56,7 @@ public class DefaultPersistenceManagerTest {
@BeforeClass
public static void setUpClass() throws IOException {
testApp = FirebaseApp.initializeApp(
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
.setDatabaseUrl("https://admin-java-sdk.firebaseio.com")
.build());
diff --git a/src/test/java/com/google/firebase/database/core/persistence/RandomPersistenceTest.java b/src/test/java/com/google/firebase/database/core/persistence/RandomPersistenceTest.java
index 58358a8ff..d55db51c3 100644
--- a/src/test/java/com/google/firebase/database/core/persistence/RandomPersistenceTest.java
+++ b/src/test/java/com/google/firebase/database/core/persistence/RandomPersistenceTest.java
@@ -73,7 +73,7 @@ public class RandomPersistenceTest {
@BeforeClass
public static void setUpClass() throws IOException {
testApp = FirebaseApp.initializeApp(
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()))
.setDatabaseUrl("https://admin-java-sdk.firebaseio.com")
.build());
diff --git a/src/test/java/com/google/firebase/database/integration/DataTestIT.java b/src/test/java/com/google/firebase/database/integration/DataTestIT.java
index 2b2124a14..d832f995d 100644
--- a/src/test/java/com/google/firebase/database/integration/DataTestIT.java
+++ b/src/test/java/com/google/firebase/database/integration/DataTestIT.java
@@ -24,7 +24,6 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import com.cedarsoftware.util.DeepEquals;
import com.google.common.collect.ImmutableList;
import com.google.firebase.FirebaseApp;
import com.google.firebase.database.ChildEventListener;
@@ -56,6 +55,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Semaphore;
@@ -1645,7 +1645,7 @@ public void testUpdateAfterSetLeafNodeWorks() throws InterruptedException {
ref.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
- if (DeepEquals.deepEquals(snapshot.getValue(), expected)) {
+ if (Objects.deepEquals(snapshot.getValue(), expected)) {
semaphore.release();
}
}
diff --git a/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseAuthTestIT.java b/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseAuthTestIT.java
index d210039cc..58e313450 100644
--- a/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseAuthTestIT.java
+++ b/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseAuthTestIT.java
@@ -78,7 +78,7 @@ public void testAuthWithValidCertificateCredential() throws InterruptedException
@Test
public void testAuthWithInvalidCertificateCredential() throws InterruptedException, IOException {
FirebaseOptions options =
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setDatabaseUrl(IntegrationTestUtils.getDatabaseUrl())
.setCredentials(GoogleCredentials.fromStream(ServiceAccount.NONE.asStream()))
.build();
@@ -94,10 +94,9 @@ public void testDatabaseAuthVariablesAuthorization() throws InterruptedException
"uid", "test",
"custom", "secret"
);
- FirebaseOptions options =
- new FirebaseOptions.Builder(masterApp.getOptions())
- .setDatabaseAuthVariableOverride(authVariableOverrides)
- .build();
+ FirebaseOptions options = masterApp.getOptions().toBuilder()
+ .setDatabaseAuthVariableOverride(authVariableOverrides)
+ .build();
FirebaseApp testUidApp = FirebaseApp.initializeApp(options, "testGetAppWithUid");
FirebaseDatabase masterDb = FirebaseDatabase.getInstance(masterApp);
FirebaseDatabase testAuthOverridesDb = FirebaseDatabase.getInstance(testUidApp);
@@ -114,10 +113,9 @@ public void testDatabaseAuthVariablesAuthorization() throws InterruptedException
@Test
public void testDatabaseAuthVariablesNoAuthorization() throws InterruptedException {
- FirebaseOptions options =
- new FirebaseOptions.Builder(masterApp.getOptions())
- .setDatabaseAuthVariableOverride(null)
- .build();
+ FirebaseOptions options = masterApp.getOptions().toBuilder()
+ .setDatabaseAuthVariableOverride(null)
+ .build();
FirebaseApp testUidApp =
FirebaseApp.initializeApp(options, "testServiceAccountDatabaseWithNoAuth");
diff --git a/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseTestIT.java b/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseTestIT.java
index f6451d9c5..84ec321a5 100644
--- a/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseTestIT.java
+++ b/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseTestIT.java
@@ -255,7 +255,7 @@ public void onCancelled(DatabaseError error) {
private static FirebaseApp appWithDbUrl(String dbUrl, String name) {
try {
- FirebaseOptions options = new FirebaseOptions.Builder()
+ FirebaseOptions options = FirebaseOptions.builder()
.setDatabaseUrl(dbUrl)
.setCredentials(GoogleCredentials.fromStream(
IntegrationTestUtils.getServiceAccountCertificate()))
@@ -268,7 +268,7 @@ private static FirebaseApp appWithDbUrl(String dbUrl, String name) {
private static FirebaseApp appWithoutDbUrl(String name) {
try {
- FirebaseOptions options = new FirebaseOptions.Builder()
+ FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(
IntegrationTestUtils.getServiceAccountCertificate()))
.build();
diff --git a/src/test/java/com/google/firebase/database/integration/RulesTestIT.java b/src/test/java/com/google/firebase/database/integration/RulesTestIT.java
index 1745ce220..b46c1287f 100644
--- a/src/test/java/com/google/firebase/database/integration/RulesTestIT.java
+++ b/src/test/java/com/google/firebase/database/integration/RulesTestIT.java
@@ -103,7 +103,7 @@ public class RulesTestIT {
public static void setUpClass() throws IOException {
// Init app with non-admin privileges
Map auth = MapBuilder.of("uid", "my-service-worker");
- FirebaseOptions options = new FirebaseOptions.Builder()
+ FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.fromStream(
IntegrationTestUtils.getServiceAccountCertificate()))
.setDatabaseUrl(IntegrationTestUtils.getDatabaseUrl())
diff --git a/src/test/java/com/google/firebase/database/integration/ShutdownExample.java b/src/test/java/com/google/firebase/database/integration/ShutdownExample.java
index 020e0416e..ded28fe0f 100644
--- a/src/test/java/com/google/firebase/database/integration/ShutdownExample.java
+++ b/src/test/java/com/google/firebase/database/integration/ShutdownExample.java
@@ -32,7 +32,7 @@ public static void main(String[] args) {
FirebaseApp app =
FirebaseApp.initializeApp(
- new FirebaseOptions.Builder()
+ FirebaseOptions.builder()
.setDatabaseUrl("https://admin-java-sdk.firebaseio.com")
.build());
diff --git a/src/test/java/com/google/firebase/database/utilities/ValidationTest.java b/src/test/java/com/google/firebase/database/utilities/ValidationTest.java
index b9e32a851..3293f5d6d 100644
--- a/src/test/java/com/google/firebase/database/utilities/ValidationTest.java
+++ b/src/test/java/com/google/firebase/database/utilities/ValidationTest.java
@@ -18,9 +18,11 @@
import static org.junit.Assert.fail;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.firebase.database.DatabaseException;
import com.google.firebase.database.core.Path;
+import java.util.List;
import java.util.Map;
import org.junit.Test;
@@ -144,32 +146,32 @@ public void testNonWritablePath() {
@Test
public void testUpdate() {
- Map[] updates = new Map[]{
- ImmutableMap.of("foo", "value"),
- ImmutableMap.of("foo", ""),
- ImmutableMap.of("foo", 10D),
- ImmutableMap.of(".foo", "foo"),
- ImmutableMap.of("foo", "value", "bar", "value"),
- };
+ List