From 49c3f580c6d35892190375f4b3d25e0171cc0cf3 Mon Sep 17 00:00:00 2001 From: chong-shao <31256040+chong-shao@users.noreply.github.com> Date: Mon, 28 Jun 2021 10:00:52 -0700 Subject: [PATCH 001/269] Drop 500 from the retriable errorcode list (#576) --- src/main/java/com/google/firebase/internal/ApiClientUtils.java | 2 +- .../java/com/google/firebase/internal/ApiClientUtilsTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/google/firebase/internal/ApiClientUtils.java b/src/main/java/com/google/firebase/internal/ApiClientUtils.java index 723fc6d52..e586bd11f 100644 --- a/src/main/java/com/google/firebase/internal/ApiClientUtils.java +++ b/src/main/java/com/google/firebase/internal/ApiClientUtils.java @@ -34,7 +34,7 @@ public class ApiClientUtils { static final RetryConfig DEFAULT_RETRY_CONFIG = RetryConfig.builder() .setMaxRetries(4) - .setRetryStatusCodes(ImmutableList.of(500, 503)) + .setRetryStatusCodes(ImmutableList.of(503)) .setMaxIntervalMillis(60 * 1000) .build(); diff --git a/src/test/java/com/google/firebase/internal/ApiClientUtilsTest.java b/src/test/java/com/google/firebase/internal/ApiClientUtilsTest.java index c19f5f567..f23c7a34d 100644 --- a/src/test/java/com/google/firebase/internal/ApiClientUtilsTest.java +++ b/src/test/java/com/google/firebase/internal/ApiClientUtilsTest.java @@ -66,7 +66,7 @@ public void testAuthorizedHttpClient() throws IOException { assertEquals(4, retryConfig.getMaxRetries()); assertEquals(60 * 1000, retryConfig.getMaxIntervalMillis()); assertFalse(retryConfig.isRetryOnIOExceptions()); - assertEquals(retryConfig.getRetryStatusCodes(), ImmutableList.of(500, 503)); + assertEquals(retryConfig.getRetryStatusCodes(), ImmutableList.of(503)); } @Test From d7da40ee26fddcc445883b9f6247baf61c5ecc2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Jun 2021 10:31:17 -0700 Subject: [PATCH 002/269] chore(deps): bump google-api-client-bom from 1.31.5 to 1.32.1 (#577) Bumps [google-api-client-bom](https://github.com/googleapis/google-api-java-client) from 1.31.5 to 1.32.1. - [Release notes](https://github.com/googleapis/google-api-java-client/releases) - [Changelog](https://github.com/googleapis/google-api-java-client/blob/master/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-api-java-client/compare/v1.31.5...v1.32.1) --- updated-dependencies: - dependency-name: com.google.api-client:google-api-client-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 98faf9fae..60571a5a4 100644 --- a/pom.xml +++ b/pom.xml @@ -400,7 +400,7 @@ com.google.api-client google-api-client-bom - 1.31.5 + 1.32.1 pom import From 579d1d1ccc1549166dbeaa9486f22dd20f48da1c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Jun 2021 10:34:37 -0700 Subject: [PATCH 003/269] chore(deps): bump libraries-bom from 20.6.0 to 20.7.0 (#578) Bumps [libraries-bom](https://github.com/GoogleCloudPlatform/cloud-opensource-java) from 20.6.0 to 20.7.0. - [Release notes](https://github.com/GoogleCloudPlatform/cloud-opensource-java/releases) - [Changelog](https://github.com/GoogleCloudPlatform/cloud-opensource-java/blob/master/CHANGELOG.md) - [Commits](https://github.com/GoogleCloudPlatform/cloud-opensource-java/commits) --- updated-dependencies: - dependency-name: com.google.cloud:libraries-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 60571a5a4..9985929bb 100644 --- a/pom.xml +++ b/pom.xml @@ -393,7 +393,7 @@ com.google.cloud libraries-bom - 20.6.0 + 20.7.0 pom import From 91f6705dbd7fe2c7a8d5683792b8379b6834a380 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Wed, 30 Jun 2021 12:17:27 -0700 Subject: [PATCH 004/269] [chore] Release 8.0.0 (#579) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9985929bb..2bfa62fdd 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 7.3.0 + 8.0.0 jar firebase-admin From bfc8d95506d229d4e3f3b6cf92918f7bb5cc1c99 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Wed, 7 Jul 2021 15:48:01 -0400 Subject: [PATCH 005/269] Set trimStackTrace property to false (#580) Set `trimStackTrace` to `false` in mvn failsafe plugin. This will print the full stack trace for failed integration tests making it easier to investigate nightly build failures. --- .github/scripts/package_artifacts.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/package_artifacts.sh b/.github/scripts/package_artifacts.sh index 6e993066e..30d998c75 100755 --- a/.github/scripts/package_artifacts.sh +++ b/.github/scripts/package_artifacts.sh @@ -28,7 +28,7 @@ echo "${FIREBASE_API_KEY}" > integration_apikey.txt # 3. Runs the unit tests (test phase) # 4. Packages the artifacts - src, bin, javadocs (package phase) # 5. Runs the integration tests (verify phase) -mvn -B clean verify +mvn -DtrimStackTrace=false -B clean verify # Maven target directory can consist of many files. Just copy the jar artifacts # into a new directory for upload. From 1a5ab1c88c6dbcfa9a84b0db1340ae5045cf8bec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jul 2021 16:29:18 -0700 Subject: [PATCH 006/269] chore(deps): bump libraries-bom from 20.7.0 to 20.8.0 (#581) Bumps [libraries-bom](https://github.com/GoogleCloudPlatform/cloud-opensource-java) from 20.7.0 to 20.8.0. - [Release notes](https://github.com/GoogleCloudPlatform/cloud-opensource-java/releases) - [Changelog](https://github.com/GoogleCloudPlatform/cloud-opensource-java/blob/master/CHANGELOG.md) - [Commits](https://github.com/GoogleCloudPlatform/cloud-opensource-java/commits) --- updated-dependencies: - dependency-name: com.google.cloud:libraries-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2bfa62fdd..57ee68bd0 100644 --- a/pom.xml +++ b/pom.xml @@ -393,7 +393,7 @@ com.google.cloud libraries-bom - 20.7.0 + 20.8.0 pom import From 1568699bc239fc51aae3d6d39332ce187e1880d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jul 2021 16:17:06 -0700 Subject: [PATCH 007/269] chore(deps): bump netty.version from 4.1.65.Final to 4.1.66.Final (#582) Bumps `netty.version` from 4.1.65.Final to 4.1.66.Final. Updates `netty-codec-http` from 4.1.65.Final to 4.1.66.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.65.Final...netty-4.1.66.Final) Updates `netty-handler` from 4.1.65.Final to 4.1.66.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.65.Final...netty-4.1.66.Final) Updates `netty-transport` from 4.1.65.Final to 4.1.66.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.65.Final...netty-4.1.66.Final) --- updated-dependencies: - dependency-name: io.netty:netty-codec-http dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-handler dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-transport dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 57ee68bd0..7753b6e37 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.65.Final + 4.1.66.Final From 0ea84bc7b18a83bfe09d75da050659940962625b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jul 2021 10:44:29 -0700 Subject: [PATCH 008/269] chore(deps): bump libraries-bom from 20.8.0 to 20.9.0 (#583) Bumps [libraries-bom](https://github.com/GoogleCloudPlatform/cloud-opensource-java) from 20.8.0 to 20.9.0. - [Release notes](https://github.com/GoogleCloudPlatform/cloud-opensource-java/releases) - [Changelog](https://github.com/GoogleCloudPlatform/cloud-opensource-java/blob/master/CHANGELOG.md) - [Commits](https://github.com/GoogleCloudPlatform/cloud-opensource-java/commits) --- updated-dependencies: - dependency-name: com.google.cloud:libraries-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7753b6e37..89e5faf38 100644 --- a/pom.xml +++ b/pom.xml @@ -393,7 +393,7 @@ com.google.cloud libraries-bom - 20.8.0 + 20.9.0 pom import From b071b03674ee0694dc3b06438295227853201617 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jul 2021 10:45:07 -0700 Subject: [PATCH 009/269] chore(deps): bump slf4j-api from 1.7.31 to 1.7.32 (#584) Bumps [slf4j-api](https://github.com/qos-ch/slf4j) from 1.7.31 to 1.7.32. - [Release notes](https://github.com/qos-ch/slf4j/releases) - [Commits](https://github.com/qos-ch/slf4j/commits) --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 89e5faf38..73f8e2786 100644 --- a/pom.xml +++ b/pom.xml @@ -446,7 +446,7 @@ org.slf4j slf4j-api - 1.7.31 + 1.7.32 io.netty From 2d50628d5d12f9c8bf582d14ca52d263bae0a534 Mon Sep 17 00:00:00 2001 From: Leo <39062083+lsirac@users.noreply.github.com> Date: Mon, 9 Aug 2021 15:07:13 -0700 Subject: [PATCH 010/269] fix(auth): check if the user is disabled on checkRevoked=true for verifyIdToken and verifySessionCookie (#585) * fix(auth): check if the user is disabled on checkRevoked=true for verifyIdToken and verifySessionCookie * fix: review comments --- .../firebase/auth/AbstractFirebaseAuth.java | 11 ++- .../google/firebase/auth/AuthErrorCode.java | 5 ++ .../auth/RevocationCheckDecorator.java | 24 ++++-- .../google/firebase/auth/FirebaseAuthIT.java | 86 +++++++++++++++++++ .../firebase/auth/FirebaseAuthTest.java | 70 +++++++++++++++ 5 files changed, 184 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java b/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java index e9f1a4320..d1b4a513e 100644 --- a/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java +++ b/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java @@ -256,11 +256,13 @@ public FirebaseToken verifyIdToken(@NonNull String idToken) throws FirebaseAuthE * API call. * * @param idToken A Firebase ID token string to parse and verify. - * @param checkRevoked A boolean denoting whether to check if the tokens were revoked. + * @param checkRevoked A boolean denoting whether to check if the tokens were revoked or if + * the user is disabled. * @return A {@link FirebaseToken} representing the verified and decoded token. * @throws IllegalArgumentException If the token is null, empty, or if the {@link FirebaseApp} * instance does not have a project ID associated with it. - * @throws FirebaseAuthException If an error occurs while parsing or validating the token. + * @throws FirebaseAuthException If an error occurs while parsing or validating the token, or if + * the user is disabled. */ public FirebaseToken verifyIdToken(@NonNull String idToken, boolean checkRevoked) throws FirebaseAuthException { @@ -343,8 +345,11 @@ public FirebaseToken verifySessionCookie(String cookie) throws FirebaseAuthExcep * checkRevoked} is true, throws a {@link FirebaseAuthException}. * * @param cookie A Firebase session cookie string to verify and parse. - * @param checkRevoked A boolean indicating whether to check if the cookie was explicitly revoked. + * @param checkRevoked A boolean indicating whether to check if the cookie was explicitly revoked + * or if the user is disabled. * @return A {@link FirebaseToken} representing the verified and decoded cookie. + * @throws FirebaseAuthException If an error occurs while parsing or validating the token, or if + * the user is disabled. */ public FirebaseToken verifySessionCookie(String cookie, boolean checkRevoked) throws FirebaseAuthException { diff --git a/src/main/java/com/google/firebase/auth/AuthErrorCode.java b/src/main/java/com/google/firebase/auth/AuthErrorCode.java index aa2821475..90b5da1a2 100644 --- a/src/main/java/com/google/firebase/auth/AuthErrorCode.java +++ b/src/main/java/com/google/firebase/auth/AuthErrorCode.java @@ -108,4 +108,9 @@ public enum AuthErrorCode { * No user record found for the given identifier. */ USER_NOT_FOUND, + + /** + * The user record is disabled. + */ + USER_DISABLED, } diff --git a/src/main/java/com/google/firebase/auth/RevocationCheckDecorator.java b/src/main/java/com/google/firebase/auth/RevocationCheckDecorator.java index 74cda69c9..9a99fd5df 100644 --- a/src/main/java/com/google/firebase/auth/RevocationCheckDecorator.java +++ b/src/main/java/com/google/firebase/auth/RevocationCheckDecorator.java @@ -51,7 +51,21 @@ private RevocationCheckDecorator( @Override public FirebaseToken verifyToken(String token) throws FirebaseAuthException { FirebaseToken firebaseToken = tokenVerifier.verifyToken(token); - if (isRevoked(firebaseToken)) { + validateDisabledOrRevoked(firebaseToken); + return firebaseToken; + } + + private void validateDisabledOrRevoked(FirebaseToken firebaseToken) throws FirebaseAuthException { + UserRecord user = userManager.getUserById(firebaseToken.getUid()); + if (user.isDisabled()) { + throw new FirebaseAuthException(ErrorCode.INVALID_ARGUMENT, + "The user record is disabled.", + /* cause= */ null, + /* response= */ null, + AuthErrorCode.USER_DISABLED); + } + long issuedAtInSeconds = (long) firebaseToken.getClaims().get("iat"); + if (user.getTokensValidAfterTimestamp() > issuedAtInSeconds * 1000) { throw new FirebaseAuthException( ErrorCode.INVALID_ARGUMENT, "Firebase " + shortName + " is revoked.", @@ -59,14 +73,6 @@ public FirebaseToken verifyToken(String token) throws FirebaseAuthException { null, errorCode); } - - return firebaseToken; - } - - private boolean isRevoked(FirebaseToken firebaseToken) throws FirebaseAuthException { - UserRecord user = userManager.getUserById(firebaseToken.getUid()); - long issuedAtInSeconds = (long) firebaseToken.getClaims().get("iat"); - return user.getTokensValidAfterTimestamp() > issuedAtInSeconds * 1000; } static RevocationCheckDecorator decorateIdTokenVerifier( diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java index 954850100..5862450ae 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java +++ b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java @@ -625,6 +625,46 @@ public void testVerifyIdToken() throws Exception { auth.deleteUserAsync("user2"); } + @Test + public void testVerifyIdTokenUserDisabled() throws Exception { + RandomUser user = UserTestUtils.generateRandomUserInfo(); + String customToken = auth.createCustomToken(user.getUid()); + String idToken = signInWithCustomToken(customToken); + + temporaryUser.registerUid(user.getUid()); + + // User is not disabled, this should not throw an exception. + FirebaseToken decoded = auth.verifyIdToken(idToken, /* checkRevoked= */true); + assertEquals(user.getUid(), decoded.getUid()); + + // Disable the user record. + auth.updateUser(new UserRecord.UpdateRequest(user.getUid()).setDisabled(true)); + + // Verify the ID token without checking revocation. This should not throw an exception. + decoded = auth.verifyIdToken(idToken); + assertEquals(user.getUid(), decoded.getUid()); + + // Verify the ID token while checking revocation. This should throw an exception. + try { + auth.verifyIdToken(idToken, /* checkRevoked= */true); + fail("Should throw a FirebaseAuthException since the user is disabled."); + } catch (FirebaseAuthException e) { + assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode()); + assertEquals(AuthErrorCode.USER_DISABLED, e.getAuthErrorCode()); + assertEquals("The user record is disabled.", e.getMessage()); + } + + // Revoke the tokens for the user. The USER_DISABLED should take precedence over + // the revocation error. + auth.revokeRefreshTokens(user.getUid()); + try { + auth.verifyIdToken(idToken, /* checkRevoked= */ true); + fail("Should throw an exception as the ID tokens are revoked."); + } catch (FirebaseAuthException e) { + assertEquals(AuthErrorCode.USER_DISABLED, e.getAuthErrorCode()); + } + } + @Test public void testVerifySessionCookie() throws Exception { String customToken = auth.createCustomTokenAsync("user3").get(); @@ -661,6 +701,52 @@ public void testVerifySessionCookie() throws Exception { auth.deleteUserAsync("user3"); } + @Test + public void testVerifySessionCookieUserDisabled() throws Exception { + RandomUser user = UserTestUtils.generateRandomUserInfo(); + String customToken = auth.createCustomToken(user.getUid()); + String idToken = signInWithCustomToken(customToken); + + temporaryUser.registerUid(user.getUid()); + + SessionCookieOptions options = SessionCookieOptions.builder() + .setExpiresIn(TimeUnit.HOURS.toMillis(1)) + .build(); + String sessionCookie = auth.createSessionCookieAsync(idToken, options).get(); + assertFalse(Strings.isNullOrEmpty(sessionCookie)); + + // User is not disabled, this should not throw an exception. + FirebaseToken decoded = auth.verifySessionCookie(sessionCookie, /* checkRevoked= */true); + assertEquals(user.getUid(), decoded.getUid()); + + // Disable the user record. + auth.updateUser(new UserRecord.UpdateRequest(user.getUid()).setDisabled(true)); + + // Verify the session cookie without checking revocation. This should not throw an exception. + decoded = auth.verifySessionCookie(sessionCookie); + assertEquals(user.getUid(), decoded.getUid()); + + // Verify the session cookie while checking revocation. This should throw an exception. + try { + auth.verifySessionCookie(sessionCookie, /* checkRevoked= */true); + fail("Should throw a FirebaseAuthException since the user is disabled."); + } catch (FirebaseAuthException e) { + assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode()); + assertEquals(AuthErrorCode.USER_DISABLED, e.getAuthErrorCode()); + assertEquals("The user record is disabled.", e.getMessage()); + } + + // Revoke the tokens for the user. The USER_DISABLED should take precedence over + // the revocation error. + auth.revokeRefreshTokens(user.getUid()); + try { + auth.verifySessionCookie(sessionCookie, /* checkRevoked= */ true); + fail("Should throw an exception as the tokens are revoked."); + } catch (FirebaseAuthException e) { + assertEquals(AuthErrorCode.USER_DISABLED, e.getAuthErrorCode()); + } + } + @Test public void testCustomTokenWithClaims() throws Exception { Map devClaims = ImmutableMap.of( diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java b/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java index 8784193ac..da46b6322 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java @@ -25,6 +25,8 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import com.google.api.client.json.GenericJson; +import com.google.api.client.json.JsonParser; import com.google.api.client.testing.http.MockHttpTransport; import com.google.api.client.testing.http.MockLowLevelHttpResponse; import com.google.api.core.ApiFuture; @@ -36,10 +38,15 @@ import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; import com.google.firebase.TestOnlyImplFirebaseTrampolines; +import com.google.firebase.internal.ApiClientUtils; import com.google.firebase.internal.FirebaseProcessEnvironment; import com.google.firebase.testing.ServiceAccount; import com.google.firebase.testing.TestResponseInterceptor; import com.google.firebase.testing.TestUtils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -246,6 +253,22 @@ public void testVerifyIdTokenWithRevocationCheck() throws Exception { assertEquals("idtoken", tokenVerifier.getLastTokenString()); } + @Test + public void testVerifyIdTokenWithRevocationCheckAndUserDisabled() throws Exception { + MockTokenVerifier tokenVerifier = MockTokenVerifier.fromResult( + getFirebaseToken(VALID_SINCE + 1000)); + FirebaseAuth auth = + getAuthForIdTokenVerificationWithRevocationCheckWithDisabledUser(tokenVerifier); + try { + auth.verifyIdToken("idtoken", true); + fail("No exception thrown for disabled user."); + } catch (FirebaseAuthException e) { + assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode()); + assertEquals(AuthErrorCode.USER_DISABLED, e.getAuthErrorCode()); + assertEquals("The user record is disabled.", e.getMessage()); + } + } + @Test public void testVerifyIdTokenWithRevocationCheckFailure() { MockTokenVerifier tokenVerifier = MockTokenVerifier.fromResult( @@ -444,6 +467,22 @@ public void testVerifySessionCookieWithRevocationCheck() throws Exception { assertEquals("cookie", tokenVerifier.getLastTokenString()); } + @Test + public void testVerifySessionCookieWithRevocationCheckAndUserDisabled() throws Exception { + MockTokenVerifier tokenVerifier = MockTokenVerifier.fromResult( + getFirebaseToken(VALID_SINCE + 1000)); + FirebaseAuth auth = + getAuthForSessionCookieVerificationWithRevocationCheckAndUserDisabled(tokenVerifier); + try { + auth.verifySessionCookie("cookie", true); + fail("No exception thrown for disabled user."); + } catch (FirebaseAuthException e) { + assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode()); + assertEquals(AuthErrorCode.USER_DISABLED, e.getAuthErrorCode()); + assertEquals("The user record is disabled.", e.getMessage()); + } + } + @Test public void testVerifySessionCookieWithRevocationCheckFailure() { MockTokenVerifier tokenVerifier = MockTokenVerifier.fromResult( @@ -513,6 +552,12 @@ FirebaseAuth getAuthForIdTokenVerificationWithRevocationCheck( return getAuthForIdTokenVerification(app, Suppliers.ofInstance(tokenVerifier)); } + FirebaseAuth getAuthForIdTokenVerificationWithRevocationCheckWithDisabledUser( + FirebaseTokenVerifier tokenVerifier) throws IOException { + FirebaseApp app = getFirebaseAppForDisabledUserRetrieval(); + return getAuthForIdTokenVerification(app, Suppliers.ofInstance(tokenVerifier)); + } + private FirebaseAuth getAuthForIdTokenVerification(FirebaseTokenVerifier tokenVerifier) { return getAuthForIdTokenVerification(Suppliers.ofInstance(tokenVerifier)); } @@ -540,6 +585,12 @@ FirebaseAuth getAuthForSessionCookieVerificationWithRevocationCheck( return getAuthForSessionCookieVerification(app, Suppliers.ofInstance(tokenVerifier)); } + FirebaseAuth getAuthForSessionCookieVerificationWithRevocationCheckAndUserDisabled( + FirebaseTokenVerifier tokenVerifier) throws IOException { + FirebaseApp app = getFirebaseAppForDisabledUserRetrieval(); + return getAuthForSessionCookieVerification(app, Suppliers.ofInstance(tokenVerifier)); + } + private FirebaseAuth getAuthForSessionCookieVerification(FirebaseTokenVerifier tokenVerifier) { return getAuthForSessionCookieVerification(Suppliers.ofInstance(tokenVerifier)); } @@ -573,6 +624,25 @@ private FirebaseApp getFirebaseAppForUserRetrieval() { .build()); } + private FirebaseApp getFirebaseAppForDisabledUserRetrieval() throws IOException { + String getUserResponse = TestUtils.loadResource("getUser.json"); + JsonParser parser = ApiClientUtils.getDefaultJsonFactory().createJsonParser(getUserResponse); + GenericJson json = + parser.parseAndClose(GenericJson.class); + Map users = + ((ArrayList>) json.get("users")).get(0); + users.put("disabled", true); + + MockHttpTransport transport = new MockHttpTransport.Builder() + .setLowLevelHttpResponse(new MockLowLevelHttpResponse().setContent(json.toString())) + .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(); From fe43bd04b7082d7da2faed8628edacef1d93e4bf Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Fri, 13 Aug 2021 14:16:15 -0400 Subject: [PATCH 011/269] [chore] Release 8.0.1 (#586) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 73f8e2786..489510588 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 8.0.0 + 8.0.1 jar firebase-admin From 9f57c9a262761ac116a4f316ea54a06f03be88ca Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Wed, 25 Aug 2021 11:42:29 -0700 Subject: [PATCH 012/269] fix: Using AuthorizedHttpClient for updating rules during tests (#590) --- .../FirebaseDatabaseAuthTestIT.java | 38 +++++----- .../database/integration/OrderByTestIT.java | 5 +- .../database/integration/RulesClient.java | 65 +++++++++++++++++ .../database/integration/RulesTestIT.java | 7 +- .../testing/IntegrationTestUtils.java | 70 ------------------- 5 files changed, 87 insertions(+), 98 deletions(-) create mode 100644 src/test/java/com/google/firebase/database/integration/RulesClient.java 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 58e313450..787e69ae3 100644 --- a/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseAuthTestIT.java +++ b/src/test/java/com/google/firebase/database/integration/FirebaseDatabaseAuthTestIT.java @@ -16,7 +16,6 @@ package com.google.firebase.database.integration; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import com.google.api.core.ApiFutureCallback; @@ -33,8 +32,6 @@ import com.google.firebase.database.TestHelpers; import com.google.firebase.database.ValueEventListener; import com.google.firebase.testing.IntegrationTestUtils; -import com.google.firebase.testing.IntegrationTestUtils.AppHttpClient; -import com.google.firebase.testing.IntegrationTestUtils.ResponseInfo; import com.google.firebase.testing.ServiceAccount; import com.google.firebase.testing.TestUtils; import java.io.IOException; @@ -49,11 +46,11 @@ import org.junit.Test; public class FirebaseDatabaseAuthTestIT { - + private static FirebaseApp masterApp; - + @BeforeClass - public static void setUpClass() throws IOException { + public static void setUpClass() throws IOException { masterApp = IntegrationTestUtils.ensureDefaultApp(); setDatabaseRules(); } @@ -74,7 +71,7 @@ public void testAuthWithValidCertificateCredential() throws InterruptedException assertWriteSucceeds(db.getReference()); assertReadSucceeds(db.getReference()); } - + @Test public void testAuthWithInvalidCertificateCredential() throws InterruptedException, IOException { FirebaseOptions options = @@ -87,7 +84,7 @@ public void testAuthWithInvalidCertificateCredential() throws InterruptedExcepti // TODO: Ideally, we would find a way to verify the correct log output. assertWriteTimeout(db.getReference()); } - + @Test public void testDatabaseAuthVariablesAuthorization() throws InterruptedException { Map authVariableOverrides = ImmutableMap.of( @@ -110,7 +107,7 @@ public void testDatabaseAuthVariablesAuthorization() throws InterruptedException assertWriteSucceeds(testAuthOverridesDb.getReference("test-custom-field-only")); assertReadSucceeds(testAuthOverridesDb.getReference("test-custom-field-only")); } - + @Test public void testDatabaseAuthVariablesNoAuthorization() throws InterruptedException { FirebaseOptions options = masterApp.getOptions().toBuilder() @@ -128,25 +125,25 @@ public void testDatabaseAuthVariablesNoAuthorization() throws InterruptedExcepti assertReadFails(testAuthOverridesDb.getReference("test-uid-only")); assertWriteFails(testAuthOverridesDb.getReference("test-custom-field-only")); assertReadFails(testAuthOverridesDb.getReference("test-custom-field-only")); - assertWriteSucceeds(testAuthOverridesDb.getReference("test-noauth-only")); + assertWriteSucceeds(testAuthOverridesDb.getReference("test-noauth-only")); } - + private static void assertWriteSucceeds(DatabaseReference ref) throws InterruptedException { doWrite(ref, /*shouldSucceed=*/ true, /*shouldTimeout=*/ false); } - + private static void assertWriteFails(DatabaseReference ref) throws InterruptedException { doWrite(ref, /*shouldSucceed=*/ false, /*shouldTimeout=*/ false); } - + private static void assertWriteTimeout(DatabaseReference ref) throws InterruptedException { doWrite(ref, /*shouldSucceed=*/ false, /*shouldTimeout=*/ true); } - + private static void assertReadSucceeds(DatabaseReference ref) throws InterruptedException { doRead(ref, /*shouldSucceed=*/ true, /*shouldTimeout=*/ false); } - + private static void assertReadFails(DatabaseReference ref) throws InterruptedException { doRead(ref, /*shouldSucceed=*/ false, /*shouldTimeout=*/ false); } @@ -180,7 +177,7 @@ public void onSuccess(Void result) { assertTrue("Write successful (expected to fail).", !success.get()); } } - + private static void doRead( DatabaseReference ref, final boolean shouldSucceed, final boolean shouldTimeout) throws InterruptedException { @@ -211,9 +208,9 @@ public void onCancelled(DatabaseError databaseError) { assertTrue("Read successful (expected to fail).", !success.get()); } } - + private static void setDatabaseRules() throws IOException { - // TODO: Use more than uid in rule Set rules so the only allowed operation is writing to + // TODO: Use more than uid in rule Set rules so the only allowed operation is writing to // /test-uid-only by user with uid 'test'. String rules = "{\n" @@ -232,8 +229,7 @@ private static void setDatabaseRules() throws IOException { + " }\n" + "}"; - AppHttpClient client = new AppHttpClient(); - ResponseInfo info = client.put("/.settings/rules.json", rules); - assertEquals(200, info.getStatus()); + RulesClient client = new RulesClient(); + client.updateRules(rules); } } diff --git a/src/test/java/com/google/firebase/database/integration/OrderByTestIT.java b/src/test/java/com/google/firebase/database/integration/OrderByTestIT.java index 2160406eb..416e56ca1 100644 --- a/src/test/java/com/google/firebase/database/integration/OrderByTestIT.java +++ b/src/test/java/com/google/firebase/database/integration/OrderByTestIT.java @@ -87,9 +87,8 @@ private static String formatRules(DatabaseReference ref, String rules) { } private static void uploadRules(FirebaseApp app, String rules) throws IOException { - IntegrationTestUtils.AppHttpClient client = new IntegrationTestUtils.AppHttpClient(app); - IntegrationTestUtils.ResponseInfo response = client.put("/.settings/rules.json", rules); - assertEquals(200, response.getStatus()); + RulesClient client = new RulesClient(app); + client.updateRules(rules); } @Test diff --git a/src/test/java/com/google/firebase/database/integration/RulesClient.java b/src/test/java/com/google/firebase/database/integration/RulesClient.java new file mode 100644 index 000000000..2e88d3a82 --- /dev/null +++ b/src/test/java/com/google/firebase/database/integration/RulesClient.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021 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.database.integration; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.api.client.http.ByteArrayContent; +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.common.io.ByteStreams; +import com.google.firebase.FirebaseApp; +import com.google.firebase.internal.ApiClientUtils; +import java.io.IOException; +import java.io.InputStream; + +/** + * A simple HTTP client wrapper for updating RTDB security rules. + */ +final class RulesClient { + + private final String databaseUrl; + private final HttpRequestFactory requestFactory; + + RulesClient() { + this(FirebaseApp.getInstance()); + } + + RulesClient(FirebaseApp app) { + this.databaseUrl = checkNotNull(app).getOptions().getDatabaseUrl(); + this.requestFactory = ApiClientUtils.newAuthorizedRequestFactory( + app, /*retryConfig*/ null); + } + + public void updateRules(String json) throws IOException { + String url = this.databaseUrl + "/.settings/rules.json"; + HttpRequest request = requestFactory.buildPutRequest( + new GenericUrl(url), + ByteArrayContent.fromString("application/json", json)); + HttpResponse response = request.execute(); + try { + InputStream in = response.getContent(); + if (in != null) { + ByteStreams.exhaust(in); + } + } finally { + ApiClientUtils.disconnectQuietly(response); + } + } +} 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 b46c1287f..b41e2993d 100644 --- a/src/test/java/com/google/firebase/database/integration/RulesTestIT.java +++ b/src/test/java/com/google/firebase/database/integration/RulesTestIT.java @@ -66,7 +66,7 @@ public class RulesTestIT { MapBuilder.of("rules", MapBuilder.of(".read", "auth != null", ".write", "auth != null")); private static final Map testRules; - + static { testRules = new MapBuilder() .put("read_only", MapBuilder.of(".read", true)) @@ -137,9 +137,8 @@ public void checkAndCleanupApp() { } private static void uploadRules(String rules) throws IOException { - IntegrationTestUtils.AppHttpClient client = new IntegrationTestUtils.AppHttpClient(masterApp); - IntegrationTestUtils.ResponseInfo response = client.put("/.settings/rules.json", rules); - assertEquals(200, response.getStatus()); + RulesClient client = new RulesClient(masterApp); + client.updateRules(rules); } @Test diff --git a/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java b/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java index 1c0a24c11..fcfda7460 100644 --- a/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java +++ b/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java @@ -16,21 +16,12 @@ package com.google.firebase.testing; -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.api.client.http.ByteArrayContent; -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.json.GenericJson; import com.google.cloud.firestore.FirestoreOptions; import com.google.common.collect.ImmutableList; -import com.google.common.io.ByteStreams; import com.google.common.io.CharStreams; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; -import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; import com.google.firebase.internal.ApiClientUtils; @@ -148,65 +139,4 @@ public static List getRandomNode(FirebaseApp app, int count) } return builder.build(); } - - public static class AppHttpClient { - - private final FirebaseApp app; - private final FirebaseOptions options; - private final HttpRequestFactory requestFactory; - - public AppHttpClient() { - this(FirebaseApp.getInstance()); - } - - public AppHttpClient(FirebaseApp app) { - this.app = checkNotNull(app); - this.options = app.getOptions(); - this.requestFactory = this.options.getHttpTransport().createRequestFactory(); - } - - public ResponseInfo put(String path, String json) throws IOException { - String url = options.getDatabaseUrl() + path + "?access_token=" + getToken(); - HttpRequest request = requestFactory.buildPutRequest(new GenericUrl(url), - ByteArrayContent.fromString("application/json", json)); - HttpResponse response = null; - try { - response = request.execute(); - return new ResponseInfo(response); - } finally { - if (response != null) { - response.disconnect(); - } - } - } - - private String getToken() { - // TODO: We should consider exposing getToken (or similar) publicly for the - // purpose of servers doing authenticated REST requests like this. - return TestOnlyImplFirebaseTrampolines.getToken(app, false); - } - } - - public static class ResponseInfo { - private final int status; - private final byte[] payload; - - private ResponseInfo(HttpResponse response) throws IOException { - this.status = response.getStatusCode(); - InputStream in = response.getContent(); - if (in != null) { - this.payload = ByteStreams.toByteArray(in); - } else { - this.payload = new byte[0]; - } - } - - public int getStatus() { - return status; - } - - public byte[] getPayload() { - return payload; - } - } } From c9ff22313c32543ddeb81bb32dffd7c86fb94032 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Aug 2021 13:49:29 -0700 Subject: [PATCH 013/269] chore(deps): bump netty.version from 4.1.66.Final to 4.1.67.Final (#588) Bumps `netty.version` from 4.1.66.Final to 4.1.67.Final. Updates `netty-codec-http` from 4.1.66.Final to 4.1.67.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.66.Final...netty-4.1.67.Final) Updates `netty-handler` from 4.1.66.Final to 4.1.67.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.66.Final...netty-4.1.67.Final) Updates `netty-transport` from 4.1.66.Final to 4.1.67.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.66.Final...netty-4.1.67.Final) --- updated-dependencies: - dependency-name: io.netty:netty-codec-http dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-handler dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-transport dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 489510588..340b59ada 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.66.Final + 4.1.67.Final From ec4b5cdcf74f979d6cc5ccd1fd87cf78f0b10ccb Mon Sep 17 00:00:00 2001 From: Alexey Markevich Date: Tue, 31 Aug 2021 20:03:42 +0000 Subject: [PATCH 014/269] fix: FirebaseOptions: move default initialization from Builder (#592) * FirebaseOptions: move default initialization from Builder * Do a checkNotNull() in the corresponding setter methods --- .../com/google/firebase/FirebaseOptions.java | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/google/firebase/FirebaseOptions.java b/src/main/java/com/google/firebase/FirebaseOptions.java index 836f701e9..8e7a29ccb 100644 --- a/src/main/java/com/google/firebase/FirebaseOptions.java +++ b/src/main/java/com/google/firebase/FirebaseOptions.java @@ -102,12 +102,12 @@ private FirebaseOptions(@NonNull final FirebaseOptions.Builder builder) { this.serviceAccountId = null; } this.storageBucket = builder.storageBucket; - this.httpTransport = checkNotNull(builder.httpTransport, - "FirebaseOptions must be initialized with a non-null HttpTransport."); - this.jsonFactory = checkNotNull(builder.jsonFactory, - "FirebaseOptions must be initialized with a non-null JsonFactory."); - this.threadManager = checkNotNull(builder.threadManager, - "FirebaseOptions must be initialized with a non-null ThreadManager."); + this.httpTransport = builder.httpTransport != null ? builder.httpTransport + : ApiClientUtils.getDefaultTransport(); + this.jsonFactory = builder.jsonFactory != null ? builder.jsonFactory + : ApiClientUtils.getDefaultJsonFactory(); + this.threadManager = builder.threadManager != null ? builder.threadManager + : FirebaseThreadManagers.DEFAULT_THREAD_MANAGER; checkArgument(builder.connectTimeout >= 0); this.connectTimeout = builder.connectTimeout; checkArgument(builder.readTimeout >= 0); @@ -255,9 +255,9 @@ public static final class Builder { private String serviceAccountId; private Supplier credentialsSupplier; private FirestoreOptions firestoreOptions; - private HttpTransport httpTransport = ApiClientUtils.getDefaultTransport(); - private JsonFactory jsonFactory = ApiClientUtils.getDefaultJsonFactory(); - private ThreadManager threadManager = FirebaseThreadManagers.DEFAULT_THREAD_MANAGER; + private HttpTransport httpTransport; + private JsonFactory jsonFactory; + private ThreadManager threadManager; private int connectTimeout; private int readTimeout; @@ -421,7 +421,8 @@ public Builder setServiceAccountId(@NonNull String serviceAccountId) { * @return This Builder instance is returned so subsequent calls can be chained. */ public Builder setHttpTransport(HttpTransport httpTransport) { - this.httpTransport = httpTransport; + this.httpTransport = checkNotNull(httpTransport, + "FirebaseOptions must be initialized with a non-null HttpTransport."); return this; } @@ -433,7 +434,8 @@ public Builder setHttpTransport(HttpTransport httpTransport) { * @return This Builder instance is returned so subsequent calls can be chained. */ public Builder setJsonFactory(JsonFactory jsonFactory) { - this.jsonFactory = jsonFactory; + this.jsonFactory = checkNotNull(jsonFactory, + "FirebaseOptions must be initialized with a non-null JsonFactory."); return this; } @@ -445,7 +447,8 @@ public Builder setJsonFactory(JsonFactory jsonFactory) { * @return This Builder instance is returned so subsequent calls can be chained. */ public Builder setThreadManager(ThreadManager threadManager) { - this.threadManager = threadManager; + this.threadManager = checkNotNull(threadManager, + "FirebaseOptions must be initialized with a non-null ThreadManager."); return this; } From 18826db4fb8e239abad4f2ddaaccd416d3ca20be Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Wed, 22 Sep 2021 12:49:04 -0400 Subject: [PATCH 015/269] feat(rc): Add Remote Config Parameter Value Type support (#591) * feat(rc): Add Remote Config Parameter Value Type support * Update integration tests --- .../firebase/remoteconfig/Parameter.java | 35 ++++++++++++++++-- .../remoteconfig/ParameterValueType.java | 37 +++++++++++++++++++ .../internal/TemplateResponse.java | 12 ++++++ .../FirebaseRemoteConfigClientImplTest.java | 5 ++- .../remoteconfig/FirebaseRemoteConfigIT.java | 9 +++-- .../firebase/remoteconfig/ParameterTest.java | 16 ++++++++ .../firebase/remoteconfig/TemplateTest.java | 11 ++++-- src/test/resources/getRemoteConfig.json | 9 +++-- 8 files changed, 120 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/google/firebase/remoteconfig/ParameterValueType.java diff --git a/src/main/java/com/google/firebase/remoteconfig/Parameter.java b/src/main/java/com/google/firebase/remoteconfig/Parameter.java index d61816b43..0b1ee6e9f 100644 --- a/src/main/java/com/google/firebase/remoteconfig/Parameter.java +++ b/src/main/java/com/google/firebase/remoteconfig/Parameter.java @@ -18,6 +18,7 @@ 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; import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterResponse; @@ -37,6 +38,7 @@ public final class Parameter { private ParameterValue defaultValue; private String description; private Map conditionalValues; + private ParameterValueType valueType; /** * Creates a new {@link Parameter}. @@ -59,6 +61,9 @@ public Parameter() { this.defaultValue = (responseDefaultValue == null) ? null : ParameterValue.fromParameterValueResponse(responseDefaultValue); this.description = parameterResponse.getDescription(); + if (!Strings.isNullOrEmpty(parameterResponse.getValueType())) { + this.valueType = ParameterValueType.valueOf(parameterResponse.getValueType()); + } } /** @@ -93,6 +98,16 @@ public Map getConditionalValues() { return conditionalValues; } + /** + * Gets the data type of the parameter value. + * + * @return The data type of the parameter value or null. + */ + @Nullable + public ParameterValueType getValueType() { + return valueType; + } + /** * Sets the default value of the parameter. * This is the value to set the parameter to, when none of the named conditions @@ -133,6 +148,18 @@ public Parameter setConditionalValues( return this; } + /** + * Sets the data type of the parameter value. + * Defaults to `ParameterValueType.STRING` if unspecified. + * + * @param valueType The data type of the parameter value. + * @return This {@link Parameter}. + */ + public Parameter setValueType(@Nullable ParameterValueType valueType) { + this.valueType = valueType; + return this; + } + ParameterResponse toParameterResponse() { Map conditionalResponseValues = new HashMap<>(); for (Map.Entry entry : conditionalValues.entrySet()) { @@ -143,7 +170,8 @@ ParameterResponse toParameterResponse() { return new ParameterResponse() .setDefaultValue(defaultValueResponse) .setDescription(description) - .setConditionalValues(conditionalResponseValues); + .setConditionalValues(conditionalResponseValues) + .setValueType(this.valueType == null ? null : this.valueType.getValueType()); } @Override @@ -157,11 +185,12 @@ public boolean equals(Object o) { Parameter parameter = (Parameter) o; return Objects.equals(defaultValue, parameter.defaultValue) && Objects.equals(description, parameter.description) - && Objects.equals(conditionalValues, parameter.conditionalValues); + && Objects.equals(conditionalValues, parameter.conditionalValues) + && Objects.equals(valueType, parameter.valueType); } @Override public int hashCode() { - return Objects.hash(defaultValue, description, conditionalValues); + return Objects.hash(defaultValue, description, conditionalValues, valueType); } } diff --git a/src/main/java/com/google/firebase/remoteconfig/ParameterValueType.java b/src/main/java/com/google/firebase/remoteconfig/ParameterValueType.java new file mode 100644 index 000000000..e9957cb77 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ParameterValueType.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021 Google LLC + * + * 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.remoteconfig; + +/** + * Data types that are associated with parameter values. + */ +public enum ParameterValueType { + STRING("STRING"), + BOOLEAN("BOOLEAN"), + NUMBER("NUMBER"), + JSON("JSON"); + + private final String valueType; + + ParameterValueType(String valueType) { + this.valueType = valueType; + } + + public String getValueType() { + return valueType; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/internal/TemplateResponse.java b/src/main/java/com/google/firebase/remoteconfig/internal/TemplateResponse.java index 09e71ef84..57a066bf2 100644 --- a/src/main/java/com/google/firebase/remoteconfig/internal/TemplateResponse.java +++ b/src/main/java/com/google/firebase/remoteconfig/internal/TemplateResponse.java @@ -107,6 +107,9 @@ public static final class ParameterResponse { @Key("conditionalValues") private Map conditionalValues; + @Key("valueType") + private String valueType; + public ParameterValueResponse getDefaultValue() { return defaultValue; } @@ -119,6 +122,10 @@ public Map getConditionalValues() { return conditionalValues; } + public String getValueType() { + return valueType; + } + public ParameterResponse setDefaultValue( ParameterValueResponse defaultValue) { this.defaultValue = defaultValue; @@ -135,6 +142,11 @@ public ParameterResponse setConditionalValues( this.conditionalValues = conditionalValues; return this; } + + public ParameterResponse setValueType(String valueType) { + this.valueType = valueType; + return this; + } } /** diff --git a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java index 71e6f81ec..f2d5c8126 100644 --- a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java @@ -85,9 +85,11 @@ public class FirebaseRemoteConfigClientImplTest { .setConditionalValues(ImmutableMap.of( "ios_en", ParameterValue.of("welcome to app en") )) - .setDescription("text for welcome message!"), + .setDescription("text for welcome message!") + .setValueType(ParameterValueType.STRING), "header_text", new Parameter() .setDefaultValue(ParameterValue.inAppDefault()) + .setValueType(ParameterValueType.STRING) ); private static final Map EXPECTED_PARAMETER_GROUPS = ImmutableMap.of( @@ -97,6 +99,7 @@ public class FirebaseRemoteConfigClientImplTest { "pumpkin_spice_season", new Parameter() .setDefaultValue(ParameterValue.of("true")) .setDescription("Whether it's currently pumpkin spice season.") + .setValueType(ParameterValueType.BOOLEAN) ) ) ); diff --git a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigIT.java b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigIT.java index bb90c6cb9..07a3c6c11 100644 --- a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigIT.java +++ b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigIT.java @@ -249,9 +249,11 @@ private Map getParameters() { "ios_en", ParameterValue.of(String.format("welcome to app en %s", timestamp)) )) - .setDescription("text for welcome message!"), + .setDescription("text for welcome message!") + .setValueType(ParameterValueType.STRING), "header_text", new Parameter() - .setDefaultValue(ParameterValue.inAppDefault())); + .setDefaultValue(ParameterValue.inAppDefault()) + .setValueType(ParameterValueType.STRING)); } private Map getParameterGroups() { @@ -262,7 +264,8 @@ private Map getParameterGroups() { .setParameters(ImmutableMap.of( "pumpkin_spice_season", new Parameter() .setDefaultValue(ParameterValue.of("true")) - .setDescription("Whether it's currently pumpkin spice season.")) + .setDescription("Whether it's currently pumpkin spice season.") + .setValueType(ParameterValueType.STRING)) )); } diff --git a/src/test/java/com/google/firebase/remoteconfig/ParameterTest.java b/src/test/java/com/google/firebase/remoteconfig/ParameterTest.java index 952ea8b82..7fc6ee555 100644 --- a/src/test/java/com/google/firebase/remoteconfig/ParameterTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/ParameterTest.java @@ -38,6 +38,7 @@ public void testConstructor() { assertTrue(parameter.getConditionalValues().isEmpty()); assertNull(parameter.getDefaultValue()); assertNull(parameter.getDescription()); + assertNull(parameter.getValueType()); } @Test(expected = NullPointerException.class) @@ -84,8 +85,23 @@ public void testEquality() { .setConditionalValues(conditionalValues); assertEquals(parameterFive, parameterSix); + + final Parameter parameterSeven = new Parameter() + .setDefaultValue(ParameterValue.inAppDefault()) + .setDescription("greeting text") + .setConditionalValues(conditionalValues) + .setValueType(ParameterValueType.STRING); + final Parameter parameterEight = new Parameter() + .setDefaultValue(ParameterValue.inAppDefault()) + .setDescription("greeting text") + .setConditionalValues(conditionalValues) + .setValueType(ParameterValueType.STRING); + + assertEquals(parameterSeven, parameterEight); assertNotEquals(parameterOne, parameterThree); assertNotEquals(parameterOne, parameterFive); + assertNotEquals(parameterOne, parameterSeven); assertNotEquals(parameterThree, parameterFive); + assertNotEquals(parameterThree, parameterSeven); } } diff --git a/src/test/java/com/google/firebase/remoteconfig/TemplateTest.java b/src/test/java/com/google/firebase/remoteconfig/TemplateTest.java index ad4d48f87..a3ea3e878 100644 --- a/src/test/java/com/google/firebase/remoteconfig/TemplateTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/TemplateTest.java @@ -49,7 +49,8 @@ public class TemplateTest { "greeting_header", new Parameter() .setDefaultValue(ParameterValue.inAppDefault()) .setDescription("greeting header text") - .setConditionalValues(CONDITIONAL_VALUES), + .setConditionalValues(CONDITIONAL_VALUES) + .setValueType(ParameterValueType.STRING), "greeting_text", new Parameter() .setDefaultValue(ParameterValue.inAppDefault()) .setDescription("greeting text") @@ -274,15 +275,17 @@ public void testToJSONWithEmptyTemplate() { public void testToJSONWithParameterValues() { Template t = new Template(); t.getParameters() - .put("with_value", new Parameter().setDefaultValue(ParameterValue.of("hello"))); + .put("with_value", new Parameter().setDefaultValue(ParameterValue.of("hello")) + .setValueType(ParameterValueType.NUMBER)); t.getParameters() .put("with_inApp", new Parameter().setDefaultValue(ParameterValue.inAppDefault())); String jsonString = t.toJSON(); assertEquals("{\"conditions\":[],\"parameterGroups\":{}," + "\"parameters\":{\"with_value\":{\"conditionalValues\":{}," - + "\"defaultValue\":{\"value\":\"hello\"}},\"with_inApp\":{\"conditionalValues\":{}," - + "\"defaultValue\":{\"useInAppDefault\":true}}}}", jsonString); + + "\"defaultValue\":{\"value\":\"hello\"},\"valueType\":\"NUMBER\"},\"with_inApp\":{" + + "\"conditionalValues\":{},\"defaultValue\":{\"useInAppDefault\":true}" + + "}}}", jsonString); } @Test diff --git a/src/test/resources/getRemoteConfig.json b/src/test/resources/getRemoteConfig.json index a4969f0d1..a80caa07d 100644 --- a/src/test/resources/getRemoteConfig.json +++ b/src/test/resources/getRemoteConfig.json @@ -20,12 +20,14 @@ "value": "welcome to app en" } }, - "description": "text for welcome message!" + "description": "text for welcome message!", + "valueType": "STRING" }, "header_text": { "defaultValue": { "useInAppDefault": true - } + }, + "valueType": "STRING" } }, "parameterGroups": { @@ -36,7 +38,8 @@ "defaultValue": { "value": "true" }, - "description": "Whether it's currently pumpkin spice season." + "description": "Whether it's currently pumpkin spice season.", + "valueType": "BOOLEAN" } } } From 89ac0d055222ef7a14a9b946f783f085db906d9e Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Wed, 22 Sep 2021 17:27:22 -0400 Subject: [PATCH 016/269] Update custom Doclet configuration for javadoc (#587) * Update custom Doclet configuration for javadoc From version 3.0 and up `javadoc` has updated parameter names used in custom Doclet configurations, which affects our internal devsite scripts. This PR updates the config to use the new parameters names. * Update other param instances * remove java 8 exception * Add -Xdoclint:none option * Added none --- pom.xml | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/pom.xml b/pom.xml index 340b59ada..d9003bdd3 100644 --- a/pom.xml +++ b/pom.xml @@ -77,16 +77,6 @@ - - - disable-java8-doclint - - [1.8,) - - - -Xdoclint:none - - devsite-apidocs @@ -123,18 +113,19 @@ 1.3 - - -hdf book.path /docs/reference/_book.yaml - -hdf project.path /_project.yaml - -hdf devsite.path /docs/reference/admin/java/reference/ - -d ${project.build.directory}/apidocs - -templatedir ${devsite.template} - -toroot /docs/reference/admin/java/reference/ - -yaml _toc.yaml - -warning 101 - + + -hdf book.path /docs/reference/_book.yaml + -hdf project.path /_project.yaml + -hdf devsite.path /docs/reference/admin/java/reference/ + -d ${project.build.directory}/apidocs + -templatedir ${devsite.template} + -toroot /docs/reference/admin/java/reference/ + -yaml _toc.yaml + -warning 101 + false -J-Xmx1024m + none @@ -334,11 +325,12 @@ 1.3 - - -warning 101 - + + -warning 101 + false -J-Xmx1024m + none From e86c80e689990c02c5ac5c0cd3ac372fdbaf091f Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Thu, 23 Sep 2021 16:50:48 -0400 Subject: [PATCH 017/269] [chore] Release 8.1.0 (#599) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d9003bdd3..cef39e156 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 8.0.1 + 8.1.0 jar firebase-admin From f86f6519207b2dccc4cf1c336e7a0702ec4af4cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Oct 2021 09:59:01 -0700 Subject: [PATCH 018/269] chore(deps): bump netty.version from 4.1.67.Final to 4.1.68.Final (#595) Bumps `netty.version` from 4.1.67.Final to 4.1.68.Final. Updates `netty-codec-http` from 4.1.67.Final to 4.1.68.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.67.Final...netty-4.1.68.Final) Updates `netty-handler` from 4.1.67.Final to 4.1.68.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.67.Final...netty-4.1.68.Final) Updates `netty-transport` from 4.1.67.Final to 4.1.68.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.67.Final...netty-4.1.68.Final) --- updated-dependencies: - dependency-name: io.netty:netty-codec-http dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-handler dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-transport dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cef39e156..30d4f6028 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.67.Final + 4.1.68.Final From f6a4bc3c0941b1f80d5cc1f5393cdc878d6996ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Dec 2021 16:16:12 -0500 Subject: [PATCH 019/269] chore(deps): bump netty.version from 4.1.68.Final to 4.1.72.Final (#613) Bumps `netty.version` from 4.1.68.Final to 4.1.72.Final. Updates `netty-codec-http` from 4.1.68.Final to 4.1.72.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.68.Final...netty-4.1.72.Final) Updates `netty-handler` from 4.1.68.Final to 4.1.72.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.68.Final...netty-4.1.72.Final) Updates `netty-transport` from 4.1.68.Final to 4.1.72.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.68.Final...netty-4.1.72.Final) --- updated-dependencies: - dependency-name: io.netty:netty-codec-http dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-handler dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-transport dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 30d4f6028..20afec1fa 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.68.Final + 4.1.72.Final From ede3b41a74903e36167dfc1b396b8da04c076e16 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Thu, 16 Dec 2021 16:16:44 -0500 Subject: [PATCH 020/269] Update issue templates (#617) * Update issue templates * update content --- .../ISSUE_TEMPLATE/bug_report.md | 14 +++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 +++++++++++++++++++ 2 files changed, 34 insertions(+) rename ISSUE_TEMPLATE.md => .github/ISSUE_TEMPLATE/bug_report.md (76%) create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/bug_report.md similarity index 76% rename from ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE/bug_report.md index 5de83b2cc..f7124c050 100644 --- a/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,3 +1,17 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +--- +**Thank you for submitting your issue. We are operating at reduced capacity from Dec 17 2021 to Jan 4 2022. Please expect delayed responses. For more urgent requests please reach us via our support channels https://firebase.google.com/support** + +--- + ### [READ] Step 1: Are you in the right place? * For issues or feature requests related to __the code in this repository__ diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..3408a0aa1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '[FR]' +labels: 'type: feature request' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 1c9429487945bf85f940b4352639fd78e7e698fc Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Thu, 16 Dec 2021 16:26:25 -0500 Subject: [PATCH 021/269] Fix the delayed response message Removing the table from markdown --- .github/ISSUE_TEMPLATE/bug_report.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index f7124c050..d5792111b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,11 +7,8 @@ assignees: '' --- ---- **Thank you for submitting your issue. We are operating at reduced capacity from Dec 17 2021 to Jan 4 2022. Please expect delayed responses. For more urgent requests please reach us via our support channels https://firebase.google.com/support** ---- - ### [READ] Step 1: Are you in the right place? * For issues or feature requests related to __the code in this repository__ From 53b9380c2eec94f1fed704dd0ae8a8bcb097ebb2 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Wed, 5 Jan 2022 14:42:08 -0500 Subject: [PATCH 022/269] Remove delayed response message for holidays (#621) Remove delayed response message for holidays --- .github/ISSUE_TEMPLATE/bug_report.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d5792111b..36eea6edb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,8 +7,6 @@ assignees: '' --- -**Thank you for submitting your issue. We are operating at reduced capacity from Dec 17 2021 to Jan 4 2022. Please expect delayed responses. For more urgent requests please reach us via our support channels https://firebase.google.com/support** - ### [READ] Step 1: Are you in the right place? * For issues or feature requests related to __the code in this repository__ From a71942ce8ddfaf7d1c769aea378d426b9fed9533 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jan 2022 17:15:38 -0500 Subject: [PATCH 023/269] chore(deps): bump maven-javadoc-plugin from 3.3.0 to 3.3.1 (#596) Bumps [maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.3.0 to 3.3.1. - [Release notes](https://github.com/apache/maven-javadoc-plugin/releases) - [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.3.0...maven-javadoc-plugin-3.3.1) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-javadoc-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 20afec1fa..539f3efed 100644 --- a/pom.xml +++ b/pom.xml @@ -89,7 +89,7 @@ maven-javadoc-plugin - 3.3.0 + 3.3.1 site @@ -301,7 +301,7 @@ maven-javadoc-plugin - 3.3.0 + 3.3.1 attach-javadocs From 21533c5d4828a7cf1fd559f1322a67260c164d2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jan 2022 18:03:41 -0500 Subject: [PATCH 024/269] chore(deps): bump google-api-client-bom from 1.32.1 to 1.33.0 (#623) Bumps [google-api-client-bom](https://github.com/googleapis/google-api-java-client) from 1.32.1 to 1.33.0. - [Release notes](https://github.com/googleapis/google-api-java-client/releases) - [Changelog](https://github.com/googleapis/google-api-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-api-java-client/compare/v1.32.1...v1.33.0) --- updated-dependencies: - dependency-name: com.google.api-client:google-api-client-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 539f3efed..b967ee665 100644 --- a/pom.xml +++ b/pom.xml @@ -392,7 +392,7 @@ com.google.api-client google-api-client-bom - 1.32.1 + 1.33.0 pom import From 23321cf17c568184b78f1371ee4cc9860b9e4296 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Jan 2022 13:15:49 -0500 Subject: [PATCH 025/269] chore(deps): bump slf4j-api from 1.7.32 to 1.7.33 (#626) Bumps [slf4j-api](https://github.com/qos-ch/slf4j) from 1.7.32 to 1.7.33. - [Release notes](https://github.com/qos-ch/slf4j/releases) - [Commits](https://github.com/qos-ch/slf4j/compare/v_1.7.32...v_1.7.33) --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b967ee665..014d8c738 100644 --- a/pom.xml +++ b/pom.xml @@ -438,7 +438,7 @@ org.slf4j slf4j-api - 1.7.32 + 1.7.33 io.netty From 8506ebbf91792ccaa67cf4eea69f529bf65867ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Jan 2022 18:53:12 -0500 Subject: [PATCH 026/269] chore(deps): bump google-api-client-bom from 1.33.0 to 1.33.1 (#632) Bumps [google-api-client-bom](https://github.com/googleapis/google-api-java-client) from 1.33.0 to 1.33.1. - [Release notes](https://github.com/googleapis/google-api-java-client/releases) - [Changelog](https://github.com/googleapis/google-api-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-api-java-client/compare/v1.33.0...v1.33.1) --- updated-dependencies: - dependency-name: com.google.api-client:google-api-client-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 014d8c738..f13af91ce 100644 --- a/pom.xml +++ b/pom.xml @@ -392,7 +392,7 @@ com.google.api-client google-api-client-bom - 1.33.0 + 1.33.1 pom import From 8cadc0d48d5ce024dfaf8deedf06fbbc5633a6ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Feb 2022 14:57:51 -0500 Subject: [PATCH 027/269] chore(deps): bump slf4j-api from 1.7.33 to 1.7.35 (#635) Bumps [slf4j-api](https://github.com/qos-ch/slf4j) from 1.7.33 to 1.7.35. - [Release notes](https://github.com/qos-ch/slf4j/releases) - [Commits](https://github.com/qos-ch/slf4j/compare/v_1.7.33...v_1.7.35) --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f13af91ce..1eff55489 100644 --- a/pom.xml +++ b/pom.xml @@ -438,7 +438,7 @@ org.slf4j slf4j-api - 1.7.33 + 1.7.35 io.netty From 0a17730035b3eaf937cc0c7fca87e491006f032a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Feb 2022 15:01:32 -0500 Subject: [PATCH 028/269] chore(deps): bump netty.version from 4.1.72.Final to 4.1.74.Final (#638) Bumps `netty.version` from 4.1.72.Final to 4.1.74.Final. Updates `netty-codec-http` from 4.1.72.Final to 4.1.74.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.72.Final...netty-4.1.74.Final) Updates `netty-handler` from 4.1.72.Final to 4.1.74.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.72.Final...netty-4.1.74.Final) Updates `netty-transport` from 4.1.72.Final to 4.1.74.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.72.Final...netty-4.1.74.Final) --- updated-dependencies: - dependency-name: io.netty:netty-codec-http dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-handler dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-transport dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1eff55489..8bf295f47 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.72.Final + 4.1.74.Final From c213726b6bb63c208c102583c1ddeea773bda15f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Feb 2022 15:14:48 -0500 Subject: [PATCH 029/269] chore(deps): bump slf4j-api from 1.7.35 to 1.7.36 (#640) Bumps [slf4j-api](https://github.com/qos-ch/slf4j) from 1.7.35 to 1.7.36. - [Release notes](https://github.com/qos-ch/slf4j/releases) - [Commits](https://github.com/qos-ch/slf4j/compare/v_1.7.35...v_1.7.36) --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8bf295f47..516821b64 100644 --- a/pom.xml +++ b/pom.xml @@ -438,7 +438,7 @@ org.slf4j slf4j-api - 1.7.35 + 1.7.36 io.netty From 72bfe0b209bdf17234df58b216769d99abe60e09 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Apr 2022 12:46:21 -0400 Subject: [PATCH 030/269] chore(deps): bump google-api-client-bom from 1.33.1 to 1.33.4 (#653) Bumps [google-api-client-bom](https://github.com/googleapis/google-api-java-client) from 1.33.1 to 1.33.4. - [Release notes](https://github.com/googleapis/google-api-java-client/releases) - [Changelog](https://github.com/googleapis/google-api-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-api-java-client/compare/v1.33.1...v1.33.4) --- updated-dependencies: - dependency-name: com.google.api-client:google-api-client-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 516821b64..a383d855f 100644 --- a/pom.xml +++ b/pom.xml @@ -392,7 +392,7 @@ com.google.api-client google-api-client-bom - 1.33.1 + 1.33.4 pom import From bc99272e27e6a26ceca0e60f23d009d5ce0ff5d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Apr 2022 12:57:37 -0400 Subject: [PATCH 031/269] chore(deps): bump maven-project-info-reports-plugin from 3.1.2 to 3.2.2 (#656) Bumps [maven-project-info-reports-plugin](https://github.com/apache/maven-project-info-reports-plugin) from 3.1.2 to 3.2.2. - [Release notes](https://github.com/apache/maven-project-info-reports-plugin/releases) - [Commits](https://github.com/apache/maven-project-info-reports-plugin/compare/maven-project-info-reports-plugin-3.1.2...maven-project-info-reports-plugin-3.2.2) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-project-info-reports-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a383d855f..8d752573e 100644 --- a/pom.xml +++ b/pom.xml @@ -367,7 +367,7 @@ maven-project-info-reports-plugin - 3.1.2 + 3.2.2 From fad6471243e9f39be00352849d47150a49b51214 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Apr 2022 13:03:38 -0400 Subject: [PATCH 032/269] chore(deps): bump netty.version from 4.1.74.Final to 4.1.75.Final (#657) Bumps `netty.version` from 4.1.74.Final to 4.1.75.Final. Updates `netty-codec-http` from 4.1.74.Final to 4.1.75.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.74.Final...netty-4.1.75.Final) Updates `netty-handler` from 4.1.74.Final to 4.1.75.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.74.Final...netty-4.1.75.Final) Updates `netty-transport` from 4.1.74.Final to 4.1.75.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.74.Final...netty-4.1.75.Final) --- updated-dependencies: - dependency-name: io.netty:netty-codec-http dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-handler dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-transport dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8d752573e..54909dab1 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.74.Final + 4.1.75.Final From 8168b17944ad281fdc18d3d42a8065b1407ac0e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Apr 2022 14:18:30 -0400 Subject: [PATCH 033/269] chore(deps): bump jacoco-maven-plugin from 0.8.7 to 0.8.8 (#658) Bumps [jacoco-maven-plugin](https://github.com/jacoco/jacoco) from 0.8.7 to 0.8.8. - [Release notes](https://github.com/jacoco/jacoco/releases) - [Commits](https://github.com/jacoco/jacoco/compare/v0.8.7...v0.8.8) --- updated-dependencies: - dependency-name: org.jacoco:jacoco-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 54909dab1..ea826866d 100644 --- a/pom.xml +++ b/pom.xml @@ -189,7 +189,7 @@ org.jacoco jacoco-maven-plugin - 0.8.7 + 0.8.8 pre-unit-test From 33b903305ff3f61de49dfd73665dd8eca9372e59 Mon Sep 17 00:00:00 2001 From: ssbushi <66321939+ssbushi@users.noreply.github.com> Date: Mon, 25 Apr 2022 09:51:40 -0400 Subject: [PATCH 034/269] Add Argon2 Hashing Algorithm support (#637) * Add Argon2 * More checks for associatedData * Use String for associated data, add another test * Removed default handling for Version in Argon2 * lint fix * more tests * Address comments. Use consts for param limits * Add javadoc for the Builder methods * Fix the mapping for Argon2. All parameters must be inside the `argon2Parameters` field --- .../com/google/firebase/auth/hash/Argon2.java | 217 ++++++++++++++++++ .../firebase/auth/UserImportHashTest.java | 99 ++++++++ .../firebase/auth/hash/InvalidHashTest.java | 68 ++++++ 3 files changed, 384 insertions(+) create mode 100644 src/main/java/com/google/firebase/auth/hash/Argon2.java diff --git a/src/main/java/com/google/firebase/auth/hash/Argon2.java b/src/main/java/com/google/firebase/auth/hash/Argon2.java new file mode 100644 index 000000000..8edcf11b7 --- /dev/null +++ b/src/main/java/com/google/firebase/auth/hash/Argon2.java @@ -0,0 +1,217 @@ +/* + * Copyright 2022 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.hash; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.collect.ImmutableMap; +import com.google.common.io.BaseEncoding; +import com.google.firebase.auth.UserImportHash; +import java.util.Map; + +/** + * Represents the Argon2 password hashing algorithm. Can be used as an instance of {@link + * com.google.firebase.auth.UserImportHash} when importing users. + */ +public final class Argon2 extends UserImportHash { + + private static final int MIN_HASH_LENGTH_BYTES = 4; + private static final int MAX_HASH_LENGTH_BYTES = 1024; + private static final int MIN_PARALLELISM = 1; + private static final int MAX_PARALLELISM = 16; + private static final int MIN_ITERATIONS = 1; + private static final int MAX_ITERATIONS = 16; + private static final int MIN_MEMORY_COST_KIB = 1; + private static final int MAX_MEMORY_COST_KIB = 32768; + + private final int hashLengthBytes; + private final Argon2HashType hashType; + private final int parallelism; + private final int iterations; + private final int memoryCostKib; + private final Argon2Version version; + private final String associatedData; + + private Argon2(Builder builder) { + super("ARGON2"); + checkArgument(intShouldBeBetweenLimitsInclusive(builder.hashLengthBytes, MIN_HASH_LENGTH_BYTES, + MAX_HASH_LENGTH_BYTES), + "hashLengthBytes is required for Argon2 and must be between %s and %s", + MIN_HASH_LENGTH_BYTES, MAX_HASH_LENGTH_BYTES); + checkArgument(builder.hashType != null, + "A hashType is required for Argon2"); + checkArgument( + intShouldBeBetweenLimitsInclusive(builder.parallelism, MIN_PARALLELISM, MAX_PARALLELISM), + "parallelism is required for Argon2 and must be between %s and %s", MIN_PARALLELISM, + MAX_PARALLELISM); + checkArgument( + intShouldBeBetweenLimitsInclusive(builder.iterations, MIN_ITERATIONS, MAX_ITERATIONS), + "iterations is required for Argon2 and must be between %s and %s", MIN_ITERATIONS, + MAX_ITERATIONS); + checkArgument(intShouldBeBetweenLimitsInclusive(builder.memoryCostKib, MIN_MEMORY_COST_KIB, + MAX_MEMORY_COST_KIB), + "memoryCostKib is required for Argon2 and must be less than or equal to %s", + MAX_MEMORY_COST_KIB); + this.hashLengthBytes = builder.hashLengthBytes; + this.hashType = builder.hashType; + this.parallelism = builder.parallelism; + this.iterations = builder.iterations; + this.memoryCostKib = builder.memoryCostKib; + if (builder.version != null) { + this.version = builder.version; + } else { + this.version = null; + } + if (builder.associatedData != null) { + this.associatedData = BaseEncoding.base64Url().encode(builder.associatedData); + } else { + this.associatedData = null; + } + } + + private static boolean intShouldBeBetweenLimitsInclusive(int property, int fromInclusive, + int toInclusive) { + return property >= fromInclusive && property <= toInclusive; + } + + @Override + protected Map getOptions() { + ImmutableMap.Builder argon2Parameters = ImmutableMap.builder() + .put("hashLengthBytes", hashLengthBytes) + .put("hashType", hashType.toString()) + .put("parallelism", parallelism) + .put("iterations", iterations) + .put("memoryCostKib", memoryCostKib); + if (this.associatedData != null) { + argon2Parameters.put("associatedData", associatedData); + } + if (this.version != null) { + argon2Parameters.put("version", version.toString()); + } + return ImmutableMap.of("argon2Parameters", argon2Parameters.build()); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private int hashLengthBytes; + private Argon2HashType hashType; + private int parallelism; + private int iterations; + private int memoryCostKib; + private Argon2Version version; + private byte[] associatedData; + + private Builder() {} + + /** + * Sets the hash length in bytes. Required field. + * + * @param hashLengthBytes an integer between 4 and 1024 (inclusive). + * @return This builder. + */ + public Builder setHashLengthBytes(int hashLengthBytes) { + this.hashLengthBytes = hashLengthBytes; + return this; + } + + /** + * Sets the Argon2 hash type. Required field. + * + * @param hashType a value from the {@link Argon2HashType} enum. + * @return This builder. + */ + public Builder setHashType(Argon2HashType hashType) { + this.hashType = hashType; + return this; + } + + /** + * Sets the degree of parallelism, also called threads or lanes. Required field. + * + * @param parallelism an integer between 1 and 16 (inclusive). + * @return This builder. + */ + public Builder setParallelism(int parallelism) { + this.parallelism = parallelism; + return this; + } + + /** + * Sets the number of iterations to perform. Required field. + * + * @param iterations an integer between 1 and 16 (inclusive). + * @return This builder. + */ + public Builder setIterations(int iterations) { + this.iterations = iterations; + return this; + } + + /** + * Sets the memory cost in kibibytes. Required field. + * + * @param memoryCostKib an integer between 1 and 32768 (inclusive). + * @return This builder. + */ + public Builder setMemoryCostKib(int memoryCostKib) { + this.memoryCostKib = memoryCostKib; + return this; + } + + /** + * Sets the version of the Argon2 algorithm. + * + * @param version a value from the {@link Argon2Version} enum. + * @return This builder. + */ + public Builder setVersion(Argon2Version version) { + this.version = version; + return this; + } + + /** + * Sets additional associated data, if provided, to append to the hash value for additional + * security. This data is base64 encoded before it is sent to the API. + * + * @param associatedData Associated data as a byte array. + * @return This builder. + */ + public Builder setAssociatedData(byte[] associatedData) { + this.associatedData = associatedData; + return this; + } + + public Argon2 build() { + return new Argon2(this); + } + } + + public enum Argon2HashType { + ARGON2_D, + ARGON2_ID, + ARGON2_I + } + + public enum Argon2Version { + VERSION_10, + VERSION_13 + } +} diff --git a/src/test/java/com/google/firebase/auth/UserImportHashTest.java b/src/test/java/com/google/firebase/auth/UserImportHashTest.java index e4a3af14a..95d85088a 100644 --- a/src/test/java/com/google/firebase/auth/UserImportHashTest.java +++ b/src/test/java/com/google/firebase/auth/UserImportHashTest.java @@ -20,6 +20,9 @@ import com.google.common.collect.ImmutableMap; import com.google.common.io.BaseEncoding; +import com.google.firebase.auth.hash.Argon2; +import com.google.firebase.auth.hash.Argon2.Argon2HashType; +import com.google.firebase.auth.hash.Argon2.Argon2Version; import com.google.firebase.auth.hash.Bcrypt; import com.google.firebase.auth.hash.HmacMd5; import com.google.firebase.auth.hash.HmacSha1; @@ -40,6 +43,7 @@ public class UserImportHashTest { private static final byte[] SIGNER_KEY = "key".getBytes(); private static final byte[] SALT_SEPARATOR = "separator".getBytes(); + private static final byte[] ARGON2_ASSOCIATED_DATA = "associatedData".getBytes(); private static class MockHash extends UserImportHash { MockHash() { @@ -109,6 +113,94 @@ public void testStandardScryptHash() { assertEquals(properties, scrypt.getProperties()); } + @Test + public void testArgon2Hash_withoutAssociatedDataWithVersion() { + UserImportHash argon2 = Argon2.builder() + .setHashLengthBytes(512) + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(8) + .setIterations(16) + .setMemoryCostKib(512) + .setVersion(Argon2Version.VERSION_10) + .build(); + + Map argon2Parameters = ImmutableMap.builder() + .put("hashLengthBytes", 512) + .put("hashType", "ARGON2_ID") + .put("parallelism", 8) + .put("iterations", 16) + .put("memoryCostKib", 512) + .put("version", "VERSION_10") + .build(); + assertEquals(getArgon2ParametersMap(argon2Parameters), argon2.getProperties()); + } + + @Test + public void testArgon2Hash_withAssociatedDataWithoutVersion() { + UserImportHash argon2 = Argon2.builder() + .setHashLengthBytes(512) + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(8) + .setIterations(16) + .setMemoryCostKib(512) + .setAssociatedData(ARGON2_ASSOCIATED_DATA) + .build(); + + Map argon2Parameters = ImmutableMap.builder() + .put("hashLengthBytes", 512) + .put("hashType", "ARGON2_ID") + .put("parallelism", 8) + .put("iterations", 16) + .put("memoryCostKib", 512) + .put("associatedData", BaseEncoding.base64Url().encode(ARGON2_ASSOCIATED_DATA)) + .build(); + assertEquals(getArgon2ParametersMap(argon2Parameters), argon2.getProperties()); + } + + @Test + public void testArgon2Hash_withAssociatedDataAndVersion() { + UserImportHash argon2 = Argon2.builder() + .setHashLengthBytes(512) + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(8) + .setIterations(16) + .setMemoryCostKib(512) + .setAssociatedData(ARGON2_ASSOCIATED_DATA) + .setVersion(Argon2Version.VERSION_10) + .build(); + + Map argon2Parameters = ImmutableMap.builder() + .put("hashLengthBytes", 512) + .put("hashType", "ARGON2_ID") + .put("parallelism", 8) + .put("iterations", 16) + .put("memoryCostKib", 512) + .put("associatedData", BaseEncoding.base64Url().encode(ARGON2_ASSOCIATED_DATA)) + .put("version", "VERSION_10") + .build(); + assertEquals(getArgon2ParametersMap(argon2Parameters), argon2.getProperties()); + } + + @Test + public void testArgon2Hash_withoutAssociatedDataAndVersion() { + UserImportHash argon2 = Argon2.builder() + .setHashLengthBytes(512) + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(8) + .setIterations(16) + .setMemoryCostKib(2048) + .build(); + + Map argon2Parameters = ImmutableMap.builder() + .put("hashLengthBytes", 512) + .put("hashType", "ARGON2_ID") + .put("parallelism", 8) + .put("iterations", 16) + .put("memoryCostKib", 2048) + .build(); + assertEquals(getArgon2ParametersMap(argon2Parameters), argon2.getProperties()); + } + @Test public void testHmacHash() { Map hashes = ImmutableMap.of( @@ -151,4 +243,11 @@ public void testBcryptHash() { Map properties = ImmutableMap.of("hashAlgorithm", "BCRYPT"); assertEquals(properties, bcrypt.getProperties()); } + + private static ImmutableMap getArgon2ParametersMap( + Map argon2Parameters) { + return ImmutableMap.of( + "hashAlgorithm", "ARGON2", + "argon2Parameters", argon2Parameters); + } } diff --git a/src/test/java/com/google/firebase/auth/hash/InvalidHashTest.java b/src/test/java/com/google/firebase/auth/hash/InvalidHashTest.java index 5186a443a..8087b8583 100644 --- a/src/test/java/com/google/firebase/auth/hash/InvalidHashTest.java +++ b/src/test/java/com/google/firebase/auth/hash/InvalidHashTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; +import com.google.firebase.auth.hash.Argon2.Argon2HashType; import java.util.List; import org.junit.Test; @@ -124,4 +125,71 @@ public void testInvalidScrypt() { } } } + + @Test + public void testInvalidArgon2() { + List builders = ImmutableList.of( + Argon2.builder() // hashLengthBytes < 4 + .setHashLengthBytes(2) + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(8) + .setIterations(16) + .setMemoryCostKib(512), + Argon2.builder() // hashLengthBytes > 1024 + .setHashLengthBytes(2048) + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(8) + .setIterations(16) + .setMemoryCostKib(512), + Argon2.builder() // missing hashType + .setHashLengthBytes(32) + .setParallelism(8) + .setIterations(16) + .setMemoryCostKib(512), + Argon2.builder() // parallelism < 1 + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(0) + .setHashLengthBytes(32) + .setIterations(16) + .setMemoryCostKib(512), + Argon2.builder() // parallelism > 16 + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(32) + .setHashLengthBytes(32) + .setIterations(16) + .setMemoryCostKib(512), + Argon2.builder() // iterations < 1 + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(16) + .setHashLengthBytes(32) + .setIterations(0) + .setMemoryCostKib(512), + Argon2.builder() // iterations > 16 + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(16) + .setHashLengthBytes(32) + .setIterations(32) + .setMemoryCostKib(512), + Argon2.builder() // memoryCostKib < 1 + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(16) + .setHashLengthBytes(32) + .setIterations(8) + .setMemoryCostKib(0), + Argon2.builder() // memoryCostKib > 32768 + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(16) + .setHashLengthBytes(32) + .setIterations(8) + .setMemoryCostKib(32769) + ); + for (Argon2.Builder builder : builders) { + try { + builder.build(); + fail("No error thrown for invalid configuration"); + } catch (IllegalArgumentException expected) { + // expected + } + } + } } From ddcbe667696f10230cd9476bee1e7805b045b04e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Apr 2022 10:05:29 -0400 Subject: [PATCH 035/269] chore(deps): bump maven-javadoc-plugin from 3.3.1 to 3.3.2 (#659) Bumps [maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.3.1 to 3.3.2. - [Release notes](https://github.com/apache/maven-javadoc-plugin/releases) - [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.3.1...maven-javadoc-plugin-3.3.2) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-javadoc-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index ea826866d..11b6cd228 100644 --- a/pom.xml +++ b/pom.xml @@ -89,7 +89,7 @@ maven-javadoc-plugin - 3.3.1 + 3.3.2 site @@ -301,7 +301,7 @@ maven-javadoc-plugin - 3.3.1 + 3.3.2 attach-javadocs From 9c71576a2d6ebd886a05575fdafc1516514b09b1 Mon Sep 17 00:00:00 2001 From: ssbushi <66321939+ssbushi@users.noreply.github.com> Date: Mon, 25 Apr 2022 15:07:27 -0400 Subject: [PATCH 036/269] Add Argon2 Java snippet (#664) --- .../snippets/FirebaseAuthSnippets.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/test/java/com/google/firebase/snippets/FirebaseAuthSnippets.java b/src/test/java/com/google/firebase/snippets/FirebaseAuthSnippets.java index e96170cf9..cd73908da 100644 --- a/src/test/java/com/google/firebase/snippets/FirebaseAuthSnippets.java +++ b/src/test/java/com/google/firebase/snippets/FirebaseAuthSnippets.java @@ -37,6 +37,9 @@ import com.google.firebase.auth.UserRecord; import com.google.firebase.auth.UserRecord.CreateRequest; import com.google.firebase.auth.UserRecord.UpdateRequest; +import com.google.firebase.auth.hash.Argon2; +import com.google.firebase.auth.hash.Argon2.Argon2HashType; +import com.google.firebase.auth.hash.Argon2.Argon2Version; import com.google.firebase.auth.hash.Bcrypt; import com.google.firebase.auth.hash.HmacSha256; import com.google.firebase.auth.hash.Pbkdf2Sha256; @@ -588,6 +591,35 @@ public void importWithScrypt() { // [END import_with_scrypt] } + public void importWithArgon2() { + // [START import_with_argon2] + try { + List users = Collections.singletonList(ImportUserRecord.builder() + .setUid("some-uid") + .setEmail("user@example.com") + .setPasswordHash("password-hash".getBytes()) + .setPasswordSalt("salt".getBytes()) + .build()); + UserImportOptions options = UserImportOptions.withHash( + Argon2.builder() + .setHashLengthBytes(512) + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(8) + .setIterations(16) + .setMemoryCostKib(2048) + .setVersion(Argon2Version.VERSION_10) + .setAssociatedData("associated-data".getBytes()) + .build()); + UserImportResult result = FirebaseAuth.getInstance().importUsers(users, options); + for (ErrorInfo indexedError : result.getErrors()) { + System.out.println("Failed to import user: " + indexedError.getReason()); + } + } catch (FirebaseAuthException e) { + System.out.println("Error importing users: " + e.getMessage()); + } + // [END import_with_argon2] + } + public void importWithoutPassword() { // [START import_without_password] try { From 9102585a8704e1cbad554db436efdef208b807b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Apr 2022 13:50:46 -0400 Subject: [PATCH 037/269] chore(deps): bump netty.version from 4.1.75.Final to 4.1.76.Final (#661) Bumps `netty.version` from 4.1.75.Final to 4.1.76.Final. Updates `netty-codec-http` from 4.1.75.Final to 4.1.76.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.75.Final...netty-4.1.76.Final) Updates `netty-handler` from 4.1.75.Final to 4.1.76.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.75.Final...netty-4.1.76.Final) Updates `netty-transport` from 4.1.75.Final to 4.1.76.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.75.Final...netty-4.1.76.Final) --- updated-dependencies: - dependency-name: io.netty:netty-codec-http dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-handler dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-transport dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 11b6cd228..eed44f1f0 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.75.Final + 4.1.76.Final From b2687f760e8ecde1861d9c4f3dfde46dc3d51c11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Apr 2022 13:58:28 -0400 Subject: [PATCH 038/269] chore(deps): bump maven-antrun-plugin from 3.0.0 to 3.1.0 (#665) Bumps [maven-antrun-plugin](https://github.com/apache/maven-antrun-plugin) from 3.0.0 to 3.1.0. - [Release notes](https://github.com/apache/maven-antrun-plugin/releases) - [Commits](https://github.com/apache/maven-antrun-plugin/compare/maven-antrun-plugin-3.0.0...maven-antrun-plugin-3.1.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-antrun-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index eed44f1f0..6de53572a 100644 --- a/pom.xml +++ b/pom.xml @@ -131,7 +131,7 @@ maven-antrun-plugin - 3.0.0 + 3.1.0 site From 198f0b47861da7b2e6914bed5dce2b2a99e6523f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Apr 2022 14:05:21 -0400 Subject: [PATCH 039/269] chore(deps): bump google-api-client-bom from 1.33.4 to 1.34.0 (#667) Bumps [google-api-client-bom](https://github.com/googleapis/google-api-java-client) from 1.33.4 to 1.34.0. - [Release notes](https://github.com/googleapis/google-api-java-client/releases) - [Changelog](https://github.com/googleapis/google-api-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-api-java-client/compare/v1.33.4...v1.34.0) --- updated-dependencies: - dependency-name: com.google.api-client:google-api-client-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6de53572a..dc58b6b2c 100644 --- a/pom.xml +++ b/pom.xml @@ -392,7 +392,7 @@ com.google.api-client google-api-client-bom - 1.33.4 + 1.34.0 pom import From af05f8c6cc95da103a69b2a8b9eaade56b80b434 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 May 2022 15:12:59 -0400 Subject: [PATCH 040/269] chore(deps): bump maven-project-info-reports-plugin from 3.2.2 to 3.3.0 (#668) Bumps [maven-project-info-reports-plugin](https://github.com/apache/maven-project-info-reports-plugin) from 3.2.2 to 3.3.0. - [Release notes](https://github.com/apache/maven-project-info-reports-plugin/releases) - [Commits](https://github.com/apache/maven-project-info-reports-plugin/compare/maven-project-info-reports-plugin-3.2.2...maven-project-info-reports-plugin-3.3.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-project-info-reports-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dc58b6b2c..71a817152 100644 --- a/pom.xml +++ b/pom.xml @@ -367,7 +367,7 @@ maven-project-info-reports-plugin - 3.2.2 + 3.3.0 From e0165d65dd6eb9140de09c84296f2c80ca2c4ace Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 May 2022 15:27:40 -0400 Subject: [PATCH 041/269] chore(deps): bump netty.version from 4.1.76.Final to 4.1.77.Final (#674) Bumps `netty.version` from 4.1.76.Final to 4.1.77.Final. Updates `netty-codec-http` from 4.1.76.Final to 4.1.77.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.76.Final...netty-4.1.77.Final) Updates `netty-handler` from 4.1.76.Final to 4.1.77.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.76.Final...netty-4.1.77.Final) Updates `netty-transport` from 4.1.76.Final to 4.1.77.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.76.Final...netty-4.1.77.Final) --- updated-dependencies: - dependency-name: io.netty:netty-codec-http dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-handler dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-transport dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 71a817152..857f7d0fb 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.76.Final + 4.1.77.Final From 14c7ade015fb98b18bf51637c718b86ed162b3af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 May 2022 15:38:03 -0400 Subject: [PATCH 042/269] chore(deps): bump netty-codec-http from 4.1.76.Final to 4.1.77.Final (#673) Bumps [netty-codec-http](https://github.com/netty/netty) from 4.1.76.Final to 4.1.77.Final. - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.76.Final...netty-4.1.77.Final) --- updated-dependencies: - dependency-name: io.netty:netty-codec-http dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From d589482873aad52ebc1003f3278c308a5f0c1438 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 May 2022 13:50:31 -0400 Subject: [PATCH 043/269] chore(deps): bump maven-javadoc-plugin from 3.3.2 to 3.4.0 (#677) Bumps [maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.3.2 to 3.4.0. - [Release notes](https://github.com/apache/maven-javadoc-plugin/releases) - [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.3.2...maven-javadoc-plugin-3.4.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-javadoc-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 857f7d0fb..72ac372f7 100644 --- a/pom.xml +++ b/pom.xml @@ -89,7 +89,7 @@ maven-javadoc-plugin - 3.3.2 + 3.4.0 site @@ -301,7 +301,7 @@ maven-javadoc-plugin - 3.3.2 + 3.4.0 attach-javadocs From d55d05e7ef4e1f94c39bf6660343f1e79c7e0c9f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 May 2022 19:54:38 +0000 Subject: [PATCH 044/269] chore(deps): bump google-api-client-bom from 1.34.0 to 1.34.1 (#680) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 72ac372f7..560aca681 100644 --- a/pom.xml +++ b/pom.xml @@ -392,7 +392,7 @@ com.google.api-client google-api-client-bom - 1.34.0 + 1.34.1 pom import From f1b003f605454813f2b1b9df593750f689603a5f Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Thu, 2 Jun 2022 12:59:55 -0400 Subject: [PATCH 045/269] change: Deprecated support for Java 7 (#682) RELEASE NOTE: Deprecated support for Java 7. Developers are advised to use Java 8 or higher when deploying the Admin SDK. Java 7 support will be dropped in the next major version. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 406223d06..c190e7713 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,9 @@ requests, code review feedback, and also pull requests. ## Supported Java Versions -We support Java 7 and higher. Firebase Admin Java SDK also runs on [Google App +We currently support Java 7 and higher. However, Java 7 support is deprecated. +We strongly encourage you to use Java 8 or higher as we will drop support for +Java 7 in the next major version. Firebase Admin Java SDK also runs on [Google App Engine](https://cloud.google.com/appengine/). From 80e9640cea5ec4fa726250cb0b42627cec671c4f Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Thu, 2 Jun 2022 14:08:48 -0400 Subject: [PATCH 046/269] [chore] Release 8.2.0 (#683) - Deprecated support for Java 7. Instead use Java 8 or higher when deploying the Admin SDK. Java 7 support will be dropped in the next major version. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 560aca681..aac0d4203 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 8.1.0 + 8.2.0 jar firebase-admin From 45243455591967b83610e3bde12e320b8d1a42ea Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Thu, 2 Jun 2022 15:00:21 -0400 Subject: [PATCH 047/269] Revert "[chore] Release 8.2.0 (#683)" (#684) This reverts commit 80e9640cea5ec4fa726250cb0b42627cec671c4f. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index aac0d4203..560aca681 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 8.2.0 + 8.1.0 jar firebase-admin From 723692ac9008ffa1161d78397eef80ead99860f9 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Thu, 2 Jun 2022 16:13:20 -0400 Subject: [PATCH 048/269] [chore] Release 8.2.0 Take 2 (#685) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 560aca681..aac0d4203 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 8.1.0 + 8.2.0 jar firebase-admin From 2a168dbfe5d458c951917e4ee2779837a87f0cf5 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Thu, 2 Jun 2022 16:36:15 -0400 Subject: [PATCH 049/269] [chore] Release 8.2.0 Take 3 (#686) --- .github/resources/settings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/resources/settings.xml b/.github/resources/settings.xml index 4afbaca26..8da474829 100644 --- a/.github/resources/settings.xml +++ b/.github/resources/settings.xml @@ -21,7 +21,7 @@ gpg - A9B90B41060565F56F348F948B6B459CFD695DE8 + 8B6B459CFD695DE8 ${env.GPG_PASSPHRASE} From 078ec13d7253f54882cd9abc545931d44544d587 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Thu, 2 Jun 2022 18:19:37 -0400 Subject: [PATCH 050/269] [chore] Release 8.2.0 (#687) * chore: Update the release process * Trigger CI --- .github/resources/firebase.asc.gpg | Bin 4061 -> 5213 bytes .github/resources/settings.xml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/resources/firebase.asc.gpg b/.github/resources/firebase.asc.gpg index 8fd7d0769b6056896ebff240b9955656f7c98a60..dae950b79907fe1755832d9d37ad34cef5e2e284 100644 GIT binary patch literal 5213 zcmV-j6r$^l4Fm}T0y~cD1mMJ_9{NSJ|6_^aOlzDF;9a=bJ=4>+~FpqQRBCwHSU;vuJq+^UYpVdggTmZa@nZ zh%<#5tD=T>i3_X%SbV<2CTS7D4c#%aWv)d#b*tw~8+%O{skDSn|13iD2v)_1xzdfx zd|N-H=t9%Fl}2giyw5{Ko-Yi%h2^JwFp>_YEU}vRh3*8E<>KjRhm_>N0-Et3d9jru zcglr?n~c_YSsz6F^M5CZ6c^N|c={`W@y04~XVu+29IoBZf0&sBDqggsVeMcuCFjyRczh;{iahd*t}*fNh7Y&8{hAouJ*d`L zLWIR{NJw-U5mx&0MsxLXLFTW`mXC%Yb($@IP4aQam*rc}o$jj){;`BWb%*GXoGrmbShPt=lzBr%B!Z8QZ~0WaMV}5 z)Ae`!7C-WsW+n~8cqYdF>dEhUtr6di!U zg7DWt`tl}u%-FV28=Ut>XZJ|DyQ!&3#$7xT*App?cawN9el^octlJhGCwb657Kg`# z%Dda_0-j;vIiqfx{j?n5`RLiMNYN-huKS2B$jqTJTzH>of45!+F1`Kces;fvdQgcM z0*YsDI+0s}Q?Ww*c?U7qrYZd&b{{Mj=j>`J$x9@Eoz_Dn=KSALFFqi75s|JdIy^fx7vxN&M2He#y3zgZvkp@I1BuSMQIV5Pyq z+pH4*kP2>ps;;66BZUFe5puUt5>AjvdI;hy&~{^b>m2j0Pm}=15s7u}qWeSvAN_xO zFTK-J=GM4upv;@lMs%7e72iDQiDFEMJaNYnUZ=T^^WsxFu!l_UD5lOU;tXtlzL+(~HMS;Gx2aLeKTdj%Gx;V*D=(3db+ zwWSCh?d(vsBKxj?e1qQt>2JWs;I@5A7#gCKoLx>9V=&A!ZS;~}+&z!IkJaVGAQ%Qg zb^L8PX7bvMx(&hjf1eQwM?yS+@&p4Q0_xsFo&OJ>4I1d;LIF8NcJVMdB~U{Ffw^MQXx z&9vf}0n)!`L>n9ML{yOYR{F|iiLdsROq1h+1{}PLJa2>Q-8#}eEbO_$OQfNs$;x3~ zR02wC;HAo3NmHasI7207%XyLYV&Z72U|2(MEwO0Uh&eNGr8%v&p`X%1DaYxgl}$rz z@v7eUU1St}cN9s_o%N@VgGGt57^^1vtF|0eoNSDdwTzP?)?68|xeP%QqW=l1!|9^uwEf|YAhU4T{bn8+~W$5>maxkg4Hd6%FsoN7c zO~I^o*L2nTZR&zNl07Oi)y{oN^DtcvK(SOI*Y0>Ybuar6xrG(XK^dfK`&C4MJ-e=K zZnKxHf%qwe;50O8;3k)4bdoBjyTAqs(!tnp$Buf68tvWUlz7z$6xk$i)kD zX=0YAOOh$u#R6V$%F?*l&$EHHVA{gVEh(NNv|bKo^$yxT4}#pL0Blp9spkEst&Z!g zqbMUyFXg+eZjeT+^RTaV&Jm)hd21Y%AaW3^Lqblg)2sER6*#gei94~!<(M{b_3{HF zwd3`)aIxZMI3J&c#~0b?w01xqb{S)|QK83#L%(IKo!QdckrvC!zIHb6tu1CQIt%XL ziK;-F57I`VaR3d{o^SW0z`Tlg6r|7(u?SW;R-=R$T0cuCo_SIyidv+e-Z zlm<`xPuT1mGQbfoUQC!VzP^{#%8U?tCE?zvh{b+;_VNmb%-x*u{R5Qtc9U?x7fC|;Hfrw7oO#%MOJ--mnj1<&fOU)~#i76#MW=V27AZO-$K(LeD zETl~4j=1ql&j~rVzN$L)%@D+U1M2)Dj{*{6)I}&UPGq0OE^K zpXR(!Z|F2*D(P&67eq-9X5&(LiN1Y}@htX#z}jOO8|N9rLrve76IAB00?FUVz0e(Y zx;0bf^@>O=lvjkR#9q*1!B%*NO9^PH$S)rn)%%g+1nKM?F!pRRxBRQ0VeCplGS7_| zsh|S;`1XAL_9f*Sss)a)YzWY^Z_o(#^%mc2^A2fbC~Vtls4maI_>Dr{FFRZ0&Pqq) z`wdZhsh<38I16GIm{0ZTEr__!fP+3G{$(2JR{mhg@V4G?kX>M zQ~JTq;1ey%npIir>qza~5oHsK(oV>Ht+t}HZ0@=_D?KHEST}Je-cZ-AI~>5&{t21a zn42zwbYG7}h>#bw608CQV-Q-v1!o~0b|hj9_R@_qhLF%pA$G8kc^ zC_tXksu8YLCz4CM;(D3%-ryzJ7)na?G`Ml(Q`3Ns`!Yvg9dUfe-LRSy9?uho8Y2r# zM+W8ftj=T-A6`83-Xog@nS1Wz#5}I^TmQ1c&F4zGWb80K5TFXTsEZ<?|9v;S*RR1OTUf9YJcxZ zCVh$uA#0s=$pB?Dz3<|t4T;3!kdqw9;y%G-dIkz~W+??tSn046hCZtL!2l`r%7nu~X-p-g zktkXLj~Qu04H$CKb!C);=}pCVe>6jf&Xof@Or$Z7(Kf?UYgVrljDij~n!5pdrAX*h zmag^pefSf4E_boQ1JV~T-BZt6QcNhOMdy7;2tEX@P@j19q&SzqEZQC|Eug08?UUjl zSLZzcX*LTzr(@20JJNi%7)Pt+HFLev{Jc;GkdI0RgeFOpQ41(4`R$v0~7Iy_KR%wT#&H(nTV`|6XCIt6*=d_M&7iVrtPCYl6I$ z9Xs6+BtcYY)vh;lIxle`)@Phfw@xRvS#Z?(0~b)V5@nYemY-UWvYxmX?XD_-zm#Q* zq~Aup`}dRl?Jndj|E|r_2QH(1@9GvUh=&agz{ z`BFGi6qXUV=jAw_E2ioD$`41a<7fJ*4Im*iZLve}sTPEq+^Hidp!awyGOS~tL>8rV zwiPNSKve@m-*Fd7I(HOE6r!Dk=Cyr#q*X;A+%W9`_0AI~oE=fOUGk%~%+@)L*A^z) zD_$-y@5AMaYx_AH#6VQY?C98DY4_$FWfuaA7J@a6DnsBZ*J=D%x-Rtx+|gQAUx0|5 z9tVWKXZ>u-{_&b6w7vKIK~p>czIL@JkKw6Z6o}66Xw~IAKx7{_B$4nMFB51}N~-$3 zs`8z!S_12b>iz?Plj|e~1LinXR|$vjG-G-CIvJql&leGNU09xUm?v3@St*^kOJM6a z(4<3o_%=)S0@n@~wkwOE9j5+x(UQT$6p6WlLnC>19D6LpujoCJ|0sY>nK|e3hb+>A zExOpMWTv00f7|i*)loysK-wxGfEB>ooR(4ojRgc=yGev{W0vNW`fAwDh%rsXv>HTR zWOCt;b;`sZcvh+#WB@ue2$3I*uAfZh;DaSOkR zX+V4KL+PwD^t6m5} z$`%AFpYhO1>Ta-;>Sd6lxwR96;*2EyJH7!1vVA`N(a7?{cNKBSXj>E>=gU6G(xPI#O>NY`-DvZK@}Q`%7k7 zTJ28;wOmL6mfQKFM(M%0O>wr;kI?ZjI+i`b6V?w;MBd00#Tp1YEL|TUqdOf=wwzcG zUd6{L_b&v{yZTZn59HISeoLE4h|1KN!HYI!(Kdp!v0%XIH3ve-gKX)Y^#D4!uk*KE zxY8Q2VoNnL6^^z;ADGg|()E-#?PFSI{ATHo(^BZ9T<~|F%WZWr&|JVm7~?I?_zG1 z20U2JtZuP{KO?}g(Wtp4GrUVNBAZ_KA<(*`0ZFUxIEwkfsSxO0jFYqR$ar+WvNh+3 z#?`SS1=E4Y@Lc`3`^jkg*>%cv`&7%o+f3HpZXIqZnE3{)6;!mFr4`Ww`{2Rc?QxFb zYVp_h)YqFpcd;0iAHE=ex$EYy0eUIpc1X(5wGi17HWTpM$~gm#k-y*2jdSyJSl`de$Jd^wjLMoL$Ax@B5lGf> z4FxvLE9j7m%wTH=^1STMjE`t6XSeW%0?}voD8f2ePtYkM(d1JeS~V~<6jfqvzeieJJ3oPP|?Ss_L#q1s z!upQ5m~;!NlZ3WqJ&nAA9iz5q6o<$yy}nO;WU%go8bT@5`if&rsX%C) XrIgL-1=1;}YjbYhxg363z=nJD3>TRDh12twaxxgrB=~nu4Ie_l{B4=u%Z!ZxI0Yr=F9w=iT{b&g_ zw-c$@r|V{b{y-ay=@PtDT7)9C*C09hepY{5sNuZs*%)_x^`u}LsM4fZ+W>w_1huO( z%@c>7hIE>+#}dDaru{9lKFrrwJ~m)gys#I`B@&VNwDtH@F5_E$O!~J06yi;j2f+Lr=e)3IkT*mj|4P^3n6MwY;}sTk0@P%l&sf zVK}afzDH%-Ga~R6e&tt7&eFqLx~Kt9?Ch$wFpKravh*+vc|l1uZmRT zQg6pu(L&zpsvS1Fb0qpUAjJEAsZi&$WXyKuG&`;?AOE@od`tm&azIt-fvFjs$A77A zfwiwhOi#eI35UZx0i*oi8-BQlmhFg1W38YzFUkDZSkQT$cL`$mecv+p`NPne#*Q-` z^a}Xh-T%r6-fU1CT|MxK0}z0VwojB5_C35grZ}bVI<37u$q9V-snzMXRL`QH_s;pd z+JH9TQ3Tw1_n<7(glRHMhS2AYVoU$#GSlj$XkS710%aSznI4joqCgvZ8J;d2{xi-G zK#|Pk6-9oXY5<5P6d%XxOaui|Bvt2T5dgXZ-Z%fG=6C|0j14qIKw&}K-pT;f7#{&9 zrBvU-Hdib)$r$$lx#w4fIXS;V_9N4S`0CHoS!gc*q<#Weu{g2yOR0_ zLbV|rO2){j48X0>)z~!Wk735N1OfNmiePSpB~vNurH6r%&9R^AwdTu!-=RB@6$tOu zFD=1luY?>%-(4`F8+^R6DLU_c{Nf$L_S8=~nt^V~jg>_`a?(rdv4M}co;tcE84tR_ zbAFf;!T+q!6?VThSH2VesUu>vHl-E^v@#LXC~kLnjP}6%{CCYDQ(3wh5V?QDqf2px zUmyq|`daYnw_i~B8m^)fz5@b!?m`tNh|z^!xxV8Xw6G+gqxC}?hW67f{z~FZq59W6 z^^@;&xL)PZHT$aB%p%!D9HP&qJscOSi=^~T{BaEaVnLFx(e3PN$e%KKcYSXCR11LpRq)QJWnyvh&58kSVK<%e=hI% zdOEP>StfE5yeD99e2B+Q=BP+~X2_;q4*+0!vY}E5Iq4$uVb&`1b)u6Ib4Z>3DYBbD zydh-S5Km;^20iFd-8#w>=r_PDxMpR5Z)Sv}fjB8ED2G=fD)CevCX&$?%kMrRGRQKr zlhjT$1oB1@bC+L!g5=3!P#@>zWm#0DBHi3d>V)*~&=xo02z@S9xX$~fLIgt337n=y zWC&~j_SM?|$JU{?Hj-B4eYqcEE6HK`ewdgRj{E``^)!rAcn&Y(E(Su6x2*k5QqFkdLqoSylqxE(D&K1sj+ zq{)|j&VLF{*+I${cFiX^m6e|Mg>OOhKdY3Ug+;t{+hO9BqxN+zT`;vAaz9gKi5&t;6N9hQC8Jh~Lb&mxdoDzRfgu=JkqV@QlhVO z#DG|EbK$Bl5m4~9n;t{`A=*xPn+VP{ZOPTF%Rix5qI?tm5~H)4L(9AOa*FcC#!4h$ zay6GGE+!(A@34Nh-_9@+-IAn}t^q~`fp6Ypgk?7&5IM6cdlo=oo<2Y)evnV$5N0Q9 z(d^pc&3h9aPo{Ri`3mGV-(Z*yre1c7xmhKmCIWF2fAy~B62OYXzIh7q)+rjmx2d%r zG&BL~G9NN%Wdu;P6A-}l7mB3#;};>bLJu%Zf5*LD!SQxit_ydd%RCSOG)5T2ZN++v znv=;S8`vSB0@e|sS#z#20S`Z-L?o`0z3Tj})7$;JX=*RR)KqnjZ9!=mpbJt8hpsRR zS98jB&cJm}8r>@$rK}rb`>AihF?l#SJOC>LChFmA3>=2detiMok)NLr>PR|A>_hZ+ zIx>&#L=O4sapFtit zwi+1b%q~hcr{1P-_W3FUYfdlArUU_HKeOSBakbl zGsPs&JSxcOA7$L&B!TI@HwL}Ed9#lEQ4Ph+M3!tREoCx>p|;=tr`)5uLr1<4!4)$M#KvhVNWK&n4m4$pu};hZKI;2? zRD}0kq_+arS4lQ0J zyV!~G@};dT53c&SFB-gJ%KFJyvgKTO?69z~7}SrHXCB-|pU>@Jx71J3ZEv_<5Q8q) zkt`{Wh#aZ~1)7|o*dGQktcJ+0_NYL3UxK6pUUWVikKFc-=fzqrniwP!K7%dNV`Rvm zwI5o@g$isY*jX&K{GZ?i=+SmL_Vul(ROv#oMj{eqRioz26fx7Tpdov3mdKzW)}r0r z2!CL#4s6j<&CH~ls&lZaPn7>%%o}NlnG%5DO;d#Dwt&;6NvEy>sN=`7db^O^)i)$~ z4@&yo4XW4S|t{h`jM_)%krF4g_bJcZ6>dk`_WNZ4nHcF@2(5IuCgy$!3U2@`Z2TnKK~3Xl0JTzVNg}$Z(RpH)to$6lf|FFF zGS{o&YE@nLc^U^}fNxAWgl|JrZIY|$^(|wjvFS$3Cf0W)kFuCTQrCDLnnWY=8a7Q6 zQM`YT4>_ktr+<{7=q5A^xn=4^tSmyIs;)BcW9>{eoHtbTN*p%9WltA)oxjT&D3Mb-E!8h6)>= ztCx0Ml9{PAY|aDh>Ldw^9al>|o?eW><8J`aY0>~&&=b=Q0(KrCb{}LpX!bg7v_HDx z`KM|v-ie?zh?24vxs2;fPVS`gVas!_;C29sG6Osdq%p?@nZ!ON-vpZz-g z>58&!*nw1zH-!A5`bI2^`O_r)zq#;3@xFVy#*S+OlTR@21g0ARQSD&UkE1uzyd8E3 zgLcJ9*X57fRX4uTJni0buamcXX37vnR&BL8zFMO}Lgc9qO`c)oY5=%a$4*RKMQ3F? zSRs(o+f_N`CSh2lO5sA9kHbsK*Yr%>yFADTS^~|}A*AATR6O^!#Kvd!cbY963!(|l z^nkJ+FJe?ski7dpkYt)sNZzA#RKqd0rr{vfoQYI}1U|pGDt^;=g;sC4L$8gUwrT3; zEUDvmKIN@72!otR4vr0dMaHq-)oQ}~a{lHfV#nKZ+-QC7>!iWG;-OA}N3XFq=`RG| z$LQHwMAu@sDw}|UE-b*9ZJS0^SCdD0_Qlkf_%uAQ(U{diEIPV4@j z<<`PYX%@`#0!a0tdWO*|3C1FrKnAiL!1SerD<}n gpg - 8B6B459CFD695DE8 + 77A3CB7D41F06A4512BA8EA5AA4E6ADF6A05C58E ${env.GPG_PASSPHRASE} From 998372527d2356e3c7676bfea37092a95079be2d Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Fri, 10 Jun 2022 14:45:12 -0400 Subject: [PATCH 051/269] change: Drop support for Java 7 (#690) Updated the minimum support platform version to Java 8 Updated the CIs to run on Java 8, 11, and 17 Upgraded the dependencies that were blocked on Java 7, including libraries-bom --- .github/workflows/ci.yml | 2 +- README.md | 16 +++++++++++++--- pom.xml | 10 +++++----- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e765291f6..eb09f08c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: strategy: matrix: - java-version: [7, 8, 11] + java-version: [8, 11, 17] steps: - uses: actions/checkout@v1 diff --git a/README.md b/README.md index c190e7713..1ad5352e5 100644 --- a/README.md +++ b/README.md @@ -46,11 +46,21 @@ requests, code review feedback, and also pull requests. ## Supported Java Versions -We currently support Java 7 and higher. However, Java 7 support is deprecated. -We strongly encourage you to use Java 8 or higher as we will drop support for -Java 7 in the next major version. Firebase Admin Java SDK also runs on [Google App +We currently support Java 8 and higher. The Firebase Admin Java SDK also runs on [Google App Engine](https://cloud.google.com/appengine/). +The Firebase Admin Java SDK follows the [Oracle Java SE +support roadmap](https://www.oracle.com/java/technologies/java-se-support-roadmap.html) +(see the Oracle Java SE Product Releases section). + +### For new development + +In general, new feature development occurs with support for the lowest Java LTS version +covered by Oracle's Premier Support (which typically lasts 5 years from initial General +Availability). If the minimum required JVM for a given library is changed, it is +accompanied by a [semver](https://semver.org/) major release. + +Java 11 and Java 17 are the best choices for new development. ## Documentation diff --git a/pom.xml b/pom.xml index aac0d4203..ffc9d1cd5 100644 --- a/pom.xml +++ b/pom.xml @@ -270,7 +270,7 @@ maven-compiler-plugin - 3.8.1 + 3.10.1 1.7 1.7 @@ -352,7 +352,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.8 + 1.6.13 true ossrh @@ -385,14 +385,14 @@ com.google.cloud libraries-bom - 20.9.0 + 25.4.0 pom import com.google.api-client google-api-client-bom - 1.34.1 + 1.35.0 pom import @@ -460,7 +460,7 @@ org.mockito mockito-core - 2.28.2 + 4.6.1 test From dd7cfa0ea3a26c2b35ff1cafb67a37e373680e85 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Jun 2022 14:55:15 -0400 Subject: [PATCH 052/269] chore(deps): bump google-api-client-bom from 1.35.0 to 1.35.1 (#691) Bumps [google-api-client-bom](https://github.com/googleapis/google-api-java-client) from 1.35.0 to 1.35.1. - [Release notes](https://github.com/googleapis/google-api-java-client/releases) - [Changelog](https://github.com/googleapis/google-api-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-api-java-client/compare/v1.35.0...v1.35.1) --- updated-dependencies: - dependency-name: com.google.api-client:google-api-client-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ffc9d1cd5..c8dfa23a4 100644 --- a/pom.xml +++ b/pom.xml @@ -392,7 +392,7 @@ com.google.api-client google-api-client-bom - 1.35.0 + 1.35.1 pom import From a73cf72ee57fc4acfe8a6ca371d27e28bf0d4dd4 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Thu, 16 Jun 2022 16:09:39 -0400 Subject: [PATCH 053/269] [chore] Release 9.0.0 (#693) Breaking change: Dropped support for Java 7. Developers should use Java 8 or higher when deploying the Admin SDK. Breaking change: Upgraded the dependency on libraries-bom to v25 and higher. This in turns upgrades the dependencies on google-cloud-firestore and google-cloud-storage to the latest versions. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c8dfa23a4..1db31664a 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 8.2.0 + 9.0.0 jar firebase-admin From d96376c6da5b1bafde2d61af3946de5ef71318d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jun 2022 13:21:06 -0400 Subject: [PATCH 054/269] chore(deps): bump netty.version from 4.1.77.Final to 4.1.78.Final (#694) Bumps `netty.version` from 4.1.77.Final to 4.1.78.Final. Updates `netty-codec-http` from 4.1.77.Final to 4.1.78.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.77.Final...netty-4.1.78.Final) Updates `netty-handler` from 4.1.77.Final to 4.1.78.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.77.Final...netty-4.1.78.Final) Updates `netty-transport` from 4.1.77.Final to 4.1.78.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.77.Final...netty-4.1.78.Final) --- updated-dependencies: - dependency-name: io.netty:netty-codec-http dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-handler dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-transport dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1db31664a..ab8c12867 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.77.Final + 4.1.78.Final From 9afdda930ef4636c050df8a0c42f5e6e2090fa7c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Jul 2022 12:48:56 -0400 Subject: [PATCH 055/269] chore(deps): bump google-api-client-bom from 1.35.1 to 1.35.2 (#696) Bumps [google-api-client-bom](https://github.com/googleapis/google-api-java-client) from 1.35.1 to 1.35.2. - [Release notes](https://github.com/googleapis/google-api-java-client/releases) - [Changelog](https://github.com/googleapis/google-api-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-api-java-client/compare/v1.35.1...v1.35.2) --- updated-dependencies: - dependency-name: com.google.api-client:google-api-client-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ab8c12867..10eec2462 100644 --- a/pom.xml +++ b/pom.xml @@ -392,7 +392,7 @@ com.google.api-client google-api-client-bom - 1.35.1 + 1.35.2 pom import From ae3ea83590150f4457bd8d61bb58ff6d83df6607 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Jul 2022 13:42:16 -0400 Subject: [PATCH 056/269] chore(deps): bump libraries-bom from 25.4.0 to 26.0.0 (#697) Bumps [libraries-bom](https://github.com/googleapis/java-cloud-bom) from 25.4.0 to 26.0.0. - [Release notes](https://github.com/googleapis/java-cloud-bom/releases) - [Changelog](https://github.com/googleapis/java-cloud-bom/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/java-cloud-bom/compare/libraries-bom-v25.4.0...libraries-bom-v26.0.0) --- updated-dependencies: - dependency-name: com.google.cloud:libraries-bom dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 10eec2462..5d1312561 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 25.4.0 + 26.0.0 pom import From 5b1a60197da4221a15464fd7f4023b80c9a4e258 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Jul 2022 17:45:03 +0000 Subject: [PATCH 057/269] chore(deps): bump netty.version from 4.1.78.Final to 4.1.79.Final (#698) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5d1312561..c69f52e91 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.78.Final + 4.1.79.Final From faead80b8b5acfbeea62c31b691b024c68d517f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Aug 2022 20:01:10 -0400 Subject: [PATCH 058/269] chore(deps): bump exec-maven-plugin from 3.0.0 to 3.1.0 (#701) Bumps [exec-maven-plugin](https://github.com/mojohaus/exec-maven-plugin) from 3.0.0 to 3.1.0. - [Release notes](https://github.com/mojohaus/exec-maven-plugin/releases) - [Commits](https://github.com/mojohaus/exec-maven-plugin/compare/exec-maven-plugin-3.0.0...exec-maven-plugin-3.1.0) --- updated-dependencies: - dependency-name: org.codehaus.mojo:exec-maven-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c69f52e91..d6c995492 100644 --- a/pom.xml +++ b/pom.xml @@ -215,7 +215,7 @@ org.codehaus.mojo exec-maven-plugin - 3.0.0 + 3.1.0 test From d61e019fe7fe50493a2dfa1f72aaf7803c833267 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Sep 2022 00:04:34 +0000 Subject: [PATCH 059/269] chore(deps-dev): bump mockito-core from 4.6.1 to 4.7.0 (#706) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d6c995492..c0871ccc4 100644 --- a/pom.xml +++ b/pom.xml @@ -460,7 +460,7 @@ org.mockito mockito-core - 4.6.1 + 4.7.0 test From 89ee249f9614cc4996c8f6ec421ada56d69143f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Sep 2022 00:07:11 +0000 Subject: [PATCH 060/269] chore(deps): bump maven-project-info-reports-plugin from 3.3.0 to 3.4.1 (#708) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c0871ccc4..e751f19dc 100644 --- a/pom.xml +++ b/pom.xml @@ -367,7 +367,7 @@ maven-project-info-reports-plugin - 3.3.0 + 3.4.1 From 46339ef11b336b5f76a0444a236acefe8b0669ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Sep 2022 00:13:12 +0000 Subject: [PATCH 061/269] chore(deps): bump slf4j-api from 1.7.36 to 2.0.0 (#710) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e751f19dc..bc010f7fa 100644 --- a/pom.xml +++ b/pom.xml @@ -438,7 +438,7 @@ org.slf4j slf4j-api - 1.7.36 + 2.0.0 io.netty From 8f27e2245dd7837428ec2201253dc73fb22e9d88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Sep 2022 00:26:05 +0000 Subject: [PATCH 062/269] chore(deps): bump libraries-bom from 26.0.0 to 26.1.1 (#711) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bc010f7fa..fc4687743 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.0.0 + 26.1.1 pom import From 834001f740053ec8a4331c1ef8ae116994e974c7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Sep 2022 00:48:51 +0000 Subject: [PATCH 063/269] chore(deps): bump netty.version from 4.1.79.Final to 4.1.80.Final (#712) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fc4687743..628518eba 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.79.Final + 4.1.80.Final From 7f2ca26096fb84bcff60c3b3ab49c9ca3e316364 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Sep 2022 17:46:13 -0400 Subject: [PATCH 064/269] chore(deps): bump google-api-client-bom from 1.35.2 to 2.0.0 (#700) Bumps [google-api-client-bom](https://github.com/googleapis/google-api-java-client) from 1.35.2 to 2.0.0. - [Release notes](https://github.com/googleapis/google-api-java-client/releases) - [Changelog](https://github.com/googleapis/google-api-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-api-java-client/compare/v1.35.2...v2.0.0) --- updated-dependencies: - dependency-name: com.google.api-client:google-api-client-bom dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 628518eba..9413ad8c9 100644 --- a/pom.xml +++ b/pom.xml @@ -392,7 +392,7 @@ com.google.api-client google-api-client-bom - 1.35.2 + 2.0.0 pom import From eed0b7846adda8d8bf5e8bb40f2f2c354f64b067 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Sep 2022 22:03:23 +0000 Subject: [PATCH 065/269] chore(deps): bump maven-javadoc-plugin from 3.4.0 to 3.4.1 (#707) --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 9413ad8c9..e76b2dc10 100644 --- a/pom.xml +++ b/pom.xml @@ -89,7 +89,7 @@ maven-javadoc-plugin - 3.4.0 + 3.4.1 site @@ -301,7 +301,7 @@ maven-javadoc-plugin - 3.4.0 + 3.4.1 attach-javadocs From 31b8044e4f5ee5a5c51061a57017b7ac03495ef1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Sep 2022 17:02:11 -0400 Subject: [PATCH 066/269] chore(deps): bump netty.version from 4.1.80.Final to 4.1.82.Final (#717) Bumps `netty.version` from 4.1.80.Final to 4.1.82.Final. Updates `netty-codec-http` from 4.1.80.Final to 4.1.82.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.80.Final...netty-4.1.82.Final) Updates `netty-handler` from 4.1.80.Final to 4.1.82.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.80.Final...netty-4.1.82.Final) Updates `netty-transport` from 4.1.80.Final to 4.1.82.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.80.Final...netty-4.1.82.Final) --- updated-dependencies: - dependency-name: io.netty:netty-codec-http dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-handler dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-transport dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e76b2dc10..972967519 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.80.Final + 4.1.82.Final From 0cd2859e00e3bc244df3b6eb437d4f5889f9f498 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Sep 2022 21:04:59 +0000 Subject: [PATCH 067/269] chore(deps-dev): bump mockito-core from 4.7.0 to 4.8.0 (#719) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 972967519..64bb7302e 100644 --- a/pom.xml +++ b/pom.xml @@ -460,7 +460,7 @@ org.mockito mockito-core - 4.7.0 + 4.8.0 test From 58820a17dd3425cc7ee01039e13ef4c14bf09d47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Sep 2022 21:08:07 +0000 Subject: [PATCH 068/269] chore(deps): bump slf4j-api from 2.0.0 to 2.0.2 (#721) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 64bb7302e..828fdeb2e 100644 --- a/pom.xml +++ b/pom.xml @@ -438,7 +438,7 @@ org.slf4j slf4j-api - 2.0.0 + 2.0.2 io.netty From d164dca69cb4f600ed13c389fadfea0ad098e51e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Sep 2022 21:11:03 +0000 Subject: [PATCH 069/269] chore(deps): bump libraries-bom from 26.1.1 to 26.1.2 (#720) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 828fdeb2e..5c1647931 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.1.1 + 26.1.2 pom import From dc3835f29dae8ffaaa9be2a5d8186b1bb0f0251d Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Wed, 28 Sep 2022 15:09:59 -0400 Subject: [PATCH 070/269] chore: Update source target to 1.8 (#724) Updated maven source config to 1.8 Updated the CONTRIBUTING.md to reflect Java 8+ support. --- CONTRIBUTING.md | 4 +--- pom.xml | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c346c5b9d..a3b65e101 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,9 +85,7 @@ information on using pull requests. ### Initial Setup -Install Java 7 or higher. You can also use Java 8, but please note that the Firebase Admin SDK must -maintain full Java 7 compatibility. Therefore make sure that you do not use any Java 8 features -(e.g. lambdas) when writing code for the Admin Java SDK. +Install Java 8 or higher. We use [Apache Maven](http://maven.apache.org/) for building, testing and releasing the Admin Java SDK code. Follow the [installation guide](http://maven.apache.org/install.html), and install Maven diff --git a/pom.xml b/pom.xml index 5c1647931..74ab77b67 100644 --- a/pom.xml +++ b/pom.xml @@ -272,8 +272,8 @@ maven-compiler-plugin 3.10.1 - 1.7 - 1.7 + 1.8 + 1.8 From 9b4fff75d933a83ffe183ecc01d5bb76d421445c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Oct 2022 16:26:08 -0400 Subject: [PATCH 071/269] chore(deps): bump slf4j-api from 2.0.2 to 2.0.3 (#726) Bumps [slf4j-api](https://github.com/qos-ch/slf4j) from 2.0.2 to 2.0.3. - [Release notes](https://github.com/qos-ch/slf4j/releases) - [Commits](https://github.com/qos-ch/slf4j/compare/v_2.0.2...v_2.0.3) --- updated-dependencies: - dependency-name: org.slf4j:slf4j-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 74ab77b67..94f8c025e 100644 --- a/pom.xml +++ b/pom.xml @@ -438,7 +438,7 @@ org.slf4j slf4j-api - 2.0.2 + 2.0.3 io.netty From 19cc8fc31c7c268b5c6b49242f642629e8beaa2f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Oct 2022 13:10:15 -0400 Subject: [PATCH 072/269] chore(deps): bump libraries-bom from 26.1.2 to 26.1.3 (#727) Bumps [libraries-bom](https://github.com/googleapis/java-cloud-bom) from 26.1.2 to 26.1.3. - [Release notes](https://github.com/googleapis/java-cloud-bom/releases) - [Changelog](https://github.com/googleapis/java-cloud-bom/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/java-cloud-bom/compare/libraries-bom-v26.1.2...libraries-bom-v26.1.3) --- updated-dependencies: - dependency-name: com.google.cloud:libraries-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 94f8c025e..8ee1b6919 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.1.2 + 26.1.3 pom import From b0be4b0646c778ebf66c30cddb6ec13f8d255e99 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Mon, 17 Oct 2022 15:19:16 -0400 Subject: [PATCH 073/269] [chore] Release 9.1.0 (#728) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8ee1b6919..ded322471 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 9.0.0 + 9.1.0 jar firebase-admin From b0aff5200b9cbbdff1a28891e532f8c24f57ba1c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Oct 2022 11:25:36 -0400 Subject: [PATCH 074/269] chore(deps): bump netty.version from 4.1.82.Final to 4.1.84.Final (#729) Bumps `netty.version` from 4.1.82.Final to 4.1.84.Final. Updates `netty-codec-http` from 4.1.82.Final to 4.1.84.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.82.Final...netty-4.1.84.Final) Updates `netty-handler` from 4.1.82.Final to 4.1.84.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.82.Final...netty-4.1.84.Final) Updates `netty-transport` from 4.1.82.Final to 4.1.84.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.82.Final...netty-4.1.84.Final) --- updated-dependencies: - dependency-name: io.netty:netty-codec-http dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-handler dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-transport dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ded322471..e9172a4ce 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.82.Final + 4.1.84.Final From 1eec932b27276ddf1d6ab491a08edb68a3036170 Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Fri, 21 Oct 2022 16:03:51 -0400 Subject: [PATCH 075/269] Implement MultiDB Support (#723) * Implement MultiDB Support * Fix Checkstyle * Revert accidental commit * Remove usage of Java 8 features (lambdas) * Revert "Remove usage of Java 8 features (lambdas)" This reverts commit 24813d0e21b31cd67d4128c2aecfda02049bee80. * Target Java 8 * Fix API description --- .../firebase/cloud/FirestoreClient.java | 88 ++++++++++--- .../firebase/cloud/FirestoreClientTest.java | 118 +++++++++++------- 2 files changed, 148 insertions(+), 58 deletions(-) diff --git a/src/main/java/com/google/firebase/cloud/FirestoreClient.java b/src/main/java/com/google/firebase/cloud/FirestoreClient.java index fddf3320d..101201781 100644 --- a/src/main/java/com/google/firebase/cloud/FirestoreClient.java +++ b/src/main/java/com/google/firebase/cloud/FirestoreClient.java @@ -11,6 +11,9 @@ import com.google.firebase.ImplFirebaseTrampolines; import com.google.firebase.internal.FirebaseService; import com.google.firebase.internal.NonNull; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,7 +35,7 @@ public class FirestoreClient { private final Firestore firestore; - private FirestoreClient(FirebaseApp app) { + private FirestoreClient(FirebaseApp app, String databaseId) { checkNotNull(app, "FirebaseApp must not be null"); String projectId = ImplFirebaseTrampolines.getProjectId(app); checkArgument(!Strings.isNullOrEmpty(projectId), @@ -47,13 +50,14 @@ private FirestoreClient(FirebaseApp app) { .setCredentialsProvider( FixedCredentialsProvider.create(ImplFirebaseTrampolines.getCredentials(app))) .setProjectId(projectId) + .setDatabaseId(databaseId) .build() .getService(); } /** - * Returns the Firestore instance associated with the default Firebase app. Returns the same - * instance for all invocations. The Firestore instance and all references obtained from it + * Returns the default Firestore instance associated with the default Firebase app. Returns the + * same instance for all invocations. The Firestore instance and all references obtained from it * becomes unusable, once the default app is deleted. * * @return A non-null {@code Firestore} @@ -65,9 +69,9 @@ public static Firestore getFirestore() { } /** - * Returns the Firestore instance associated with the specified Firebase app. For a given app, - * always returns the same instance. The Firestore instance and all references obtained from it - * becomes unusable, once the specified app is deleted. + * Returns the default Firestore instance associated with the specified Firebase app. For a given + * app, invocation always returns the same instance. The Firestore instance and all references + * obtained from it becomes unusable, once the specified app is deleted. * * @param app A non-null {@link FirebaseApp}. * @return A non-null {@code Firestore} @@ -75,32 +79,86 @@ public static Firestore getFirestore() { */ @NonNull public static Firestore getFirestore(FirebaseApp app) { - return getInstance(app).firestore; + return getFirestore(app, ImplFirebaseTrampolines.getFirestoreOptions(app).getDatabaseId()); + } + + /** + * Returns the Firestore instance associated with the specified Firebase app. Returns the same + * instance for all invocations given the same app and database parameter. The Firestore instance + * and all references obtained from it becomes unusable, once the specified app is deleted. + * + * @param app A non-null {@link FirebaseApp}. + * @param database - The name of database. + * @return A non-null {@code Firestore} + * instance. + */ + @NonNull + static Firestore getFirestore(FirebaseApp app, String database) { + return getInstance(app, database).firestore; + } + + /** + * Returns the Firestore instance associated with the default Firebase app. Returns the same + * instance for all invocations given the same database parameter. The Firestore instance and all + * references obtained from it becomes unusable, once the default app is deleted. + * + * @param database - The name of database. + * @return A non-null {@code Firestore} + * instance. + */ + @NonNull + static Firestore getFirestore(String database) { + return getFirestore(FirebaseApp.getInstance(), database); } - private static synchronized FirestoreClient getInstance(FirebaseApp app) { + private static synchronized FirestoreClient getInstance(FirebaseApp app, String database) { FirestoreClientService service = ImplFirebaseTrampolines.getService(app, SERVICE_ID, FirestoreClientService.class); if (service == null) { service = ImplFirebaseTrampolines.addService(app, new FirestoreClientService(app)); } - return service.getInstance(); + return service.getInstance().get(database); } private static final String SERVICE_ID = FirestoreClient.class.getName(); - private static class FirestoreClientService extends FirebaseService { + private static class FirestoreClientService extends FirebaseService { FirestoreClientService(FirebaseApp app) { - super(SERVICE_ID, new FirestoreClient(app)); + super(SERVICE_ID, new FirestoreInstances(app)); } @Override public void destroy() { - try { - instance.firestore.close(); - } catch (Exception e) { - logger.warn("Error while closing the Firestore instance", e); + instance.destroy(); + } + } + + private static class FirestoreInstances { + + private final FirebaseApp app; + + private final Map clients = + Collections.synchronizedMap(new HashMap<>()); + + private FirestoreInstances(FirebaseApp app) { + this.app = app; + } + + FirestoreClient get(String databaseId) { + return clients.computeIfAbsent(databaseId, id -> new FirestoreClient(app, id)); + } + + void destroy() { + synchronized (clients) { + for (FirestoreClient client : clients.values()) { + try { + client.firestore.close(); + } catch (Exception e) { + logger.warn("Error while closing the Firestore instance", e); + } + } + clients.clear(); } } } diff --git a/src/test/java/com/google/firebase/cloud/FirestoreClientTest.java b/src/test/java/com/google/firebase/cloud/FirestoreClientTest.java index 018d0e45b..a5f617ce6 100644 --- a/src/test/java/com/google/firebase/cloud/FirestoreClientTest.java +++ b/src/test/java/com/google/firebase/cloud/FirestoreClientTest.java @@ -2,9 +2,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertThrows; import com.google.auth.oauth2.GoogleCredentials; import com.google.cloud.firestore.DocumentReference; @@ -26,6 +26,7 @@ public class FirestoreClientTest { // 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")) + .setDatabaseId("differedDefaultDatabaseId") .build(); @After @@ -35,47 +36,75 @@ public void tearDown() { @Test public void testExplicitProjectId() throws IOException { + final String databaseId = "databaseIdInTestExplicitProjectId"; FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder() .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream())) .setProjectId("explicit-project-id") .setFirestoreOptions(FIRESTORE_OPTIONS) .build()); - Firestore firestore = FirestoreClient.getFirestore(app); - assertEquals("explicit-project-id", firestore.getOptions().getProjectId()); + Firestore firestore1 = FirestoreClient.getFirestore(app); + assertEquals("explicit-project-id", firestore1.getOptions().getProjectId()); + assertEquals(FIRESTORE_OPTIONS.getDatabaseId(), firestore1.getOptions().getDatabaseId()); - firestore = FirestoreClient.getFirestore(); - assertEquals("explicit-project-id", firestore.getOptions().getProjectId()); + assertSame(firestore1, FirestoreClient.getFirestore()); + + Firestore firestore2 = FirestoreClient.getFirestore(app, databaseId); + assertEquals("explicit-project-id", firestore2.getOptions().getProjectId()); + assertEquals(databaseId, firestore2.getOptions().getDatabaseId()); + + assertSame(firestore2, FirestoreClient.getFirestore(databaseId)); + + assertNotSame(firestore1, firestore2); } @Test public void testServiceAccountProjectId() throws IOException { + final String databaseId = "databaseIdInTestServiceAccountProjectId"; FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder() .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream())) .setFirestoreOptions(FIRESTORE_OPTIONS) .build()); - Firestore firestore = FirestoreClient.getFirestore(app); - assertEquals("mock-project-id", firestore.getOptions().getProjectId()); + Firestore firestore1 = FirestoreClient.getFirestore(app); + assertEquals("mock-project-id", firestore1.getOptions().getProjectId()); + assertEquals(FIRESTORE_OPTIONS.getDatabaseId(), firestore1.getOptions().getDatabaseId()); + + assertSame(firestore1, FirestoreClient.getFirestore()); - firestore = FirestoreClient.getFirestore(); - assertEquals("mock-project-id", firestore.getOptions().getProjectId()); + Firestore firestore2 = FirestoreClient.getFirestore(app, databaseId); + assertEquals("mock-project-id", firestore2.getOptions().getProjectId()); + assertEquals(databaseId, firestore2.getOptions().getDatabaseId()); + + assertSame(firestore2, FirestoreClient.getFirestore(databaseId)); + + assertNotSame(firestore1, firestore2); } @Test public void testFirestoreOptions() throws IOException { + final String databaseId = "databaseIdInTestFirestoreOptions"; FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder() .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream())) .setProjectId("explicit-project-id") .setFirestoreOptions(FIRESTORE_OPTIONS) .build()); - Firestore firestore = FirestoreClient.getFirestore(app); - assertEquals("explicit-project-id", firestore.getOptions().getProjectId()); + Firestore firestore1 = FirestoreClient.getFirestore(app); + assertEquals("explicit-project-id", firestore1.getOptions().getProjectId()); + assertEquals(FIRESTORE_OPTIONS.getDatabaseId(), firestore1.getOptions().getDatabaseId()); + + assertSame(firestore1, FirestoreClient.getFirestore()); + + Firestore firestore2 = FirestoreClient.getFirestore(app, databaseId); + assertEquals("explicit-project-id", firestore2.getOptions().getProjectId()); + assertEquals(databaseId, firestore2.getOptions().getDatabaseId()); - firestore = FirestoreClient.getFirestore(); - assertEquals("explicit-project-id", firestore.getOptions().getProjectId()); + assertSame(firestore2, FirestoreClient.getFirestore(databaseId)); + + assertNotSame(firestore1, firestore2); } @Test public void testFirestoreOptionsOverride() throws IOException { + final String databaseId = "databaseIdInTestFirestoreOptions"; FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder() .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream())) .setProjectId("explicit-project-id") @@ -84,48 +113,51 @@ public void testFirestoreOptionsOverride() throws IOException { .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream())) .build()) .build()); - Firestore firestore = FirestoreClient.getFirestore(app); - assertEquals("explicit-project-id", firestore.getOptions().getProjectId()); + Firestore firestore1 = FirestoreClient.getFirestore(app); + assertEquals("explicit-project-id", firestore1.getOptions().getProjectId()); assertSame(ImplFirebaseTrampolines.getCredentials(app), - firestore.getOptions().getCredentialsProvider().getCredentials()); + firestore1.getOptions().getCredentialsProvider().getCredentials()); + assertEquals("(default)", firestore1.getOptions().getDatabaseId()); + + assertSame(firestore1, FirestoreClient.getFirestore()); - firestore = FirestoreClient.getFirestore(); - assertEquals("explicit-project-id", firestore.getOptions().getProjectId()); + Firestore firestore2 = FirestoreClient.getFirestore(app, databaseId); + assertEquals("explicit-project-id", firestore2.getOptions().getProjectId()); assertSame(ImplFirebaseTrampolines.getCredentials(app), - firestore.getOptions().getCredentialsProvider().getCredentials()); + firestore2.getOptions().getCredentialsProvider().getCredentials()); + assertEquals(databaseId, firestore2.getOptions().getDatabaseId()); + + assertSame(firestore2, FirestoreClient.getFirestore(databaseId)); + + assertNotSame(firestore1, firestore2); } @Test public void testAppDelete() throws IOException { + final String databaseId = "databaseIdInTestAppDelete"; FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder() .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream())) .setProjectId("mock-project-id") .setFirestoreOptions(FIRESTORE_OPTIONS) .build()); - Firestore firestore = FirestoreClient.getFirestore(app); - assertNotNull(firestore); - DocumentReference document = firestore.collection("collection").document("doc"); + Firestore firestore1 = FirestoreClient.getFirestore(app); + assertNotNull(firestore1); + assertSame(firestore1, FirestoreClient.getFirestore()); + + Firestore firestore2 = FirestoreClient.getFirestore(app, databaseId); + assertNotNull(firestore2); + assertSame(firestore2, FirestoreClient.getFirestore(databaseId)); + + assertNotSame(firestore1, firestore2); + + DocumentReference document = firestore1.collection("collection").document("doc"); app.delete(); - try { - FirestoreClient.getFirestore(app); - fail("No error thrown for deleted app"); - } catch (IllegalStateException expected) { - // ignore - } - - try { - document.get(); - fail("No error thrown for deleted app"); - } catch (IllegalStateException expected) { - // ignore - } - - try { - FirestoreClient.getFirestore(); - fail("No error thrown for deleted app"); - } catch (IllegalStateException expected) { - // ignore - } + + assertThrows(IllegalStateException.class, () -> FirestoreClient.getFirestore(app)); + assertThrows(IllegalStateException.class, () -> document.get()); + assertThrows(IllegalStateException.class, () -> FirestoreClient.getFirestore()); + assertThrows(IllegalStateException.class, () -> FirestoreClient.getFirestore(app, databaseId)); + assertThrows(IllegalStateException.class, () -> FirestoreClient.getFirestore(databaseId)); } } From e28ca2e6123cfdec14198f65a2e3af86dccfa588 Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Mon, 7 Nov 2022 14:55:45 -0500 Subject: [PATCH 076/269] Fix MultiDB Regression (#735) --- .../com/google/firebase/cloud/FirestoreClient.java | 3 ++- .../google/firebase/cloud/FirestoreClientTest.java | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/google/firebase/cloud/FirestoreClient.java b/src/main/java/com/google/firebase/cloud/FirestoreClient.java index 101201781..e1672ec34 100644 --- a/src/main/java/com/google/firebase/cloud/FirestoreClient.java +++ b/src/main/java/com/google/firebase/cloud/FirestoreClient.java @@ -79,7 +79,8 @@ public static Firestore getFirestore() { */ @NonNull public static Firestore getFirestore(FirebaseApp app) { - return getFirestore(app, ImplFirebaseTrampolines.getFirestoreOptions(app).getDatabaseId()); + final FirestoreOptions firestoreOptions = ImplFirebaseTrampolines.getFirestoreOptions(app); + return getFirestore(app, firestoreOptions == null ? null : firestoreOptions.getDatabaseId()); } /** diff --git a/src/test/java/com/google/firebase/cloud/FirestoreClientTest.java b/src/test/java/com/google/firebase/cloud/FirestoreClientTest.java index a5f617ce6..31ea33618 100644 --- a/src/test/java/com/google/firebase/cloud/FirestoreClientTest.java +++ b/src/test/java/com/google/firebase/cloud/FirestoreClientTest.java @@ -79,6 +79,20 @@ public void testServiceAccountProjectId() throws IOException { assertNotSame(firestore1, firestore2); } + @Test + public void testAbsentFirestoreOptions() throws Exception { + String projectId = "test-proj"; + FirebaseOptions options = FirebaseOptions.builder() + .setCredentials(GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream())) + .setProjectId(projectId) + .build(); + + FirebaseApp.initializeApp(options); + Firestore firestore = FirestoreClient.getFirestore(); + assertEquals(projectId, firestore.getOptions().getProjectId()); + assertEquals("(default)", firestore.getOptions().getDatabaseId()); + } + @Test public void testFirestoreOptions() throws IOException { final String databaseId = "databaseIdInTestFirestoreOptions"; From 6278b5f0f9f1677621c681bbee3256534a5bca6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Nov 2022 21:13:22 +0000 Subject: [PATCH 077/269] chore(deps-dev): bump mockito-core from 4.8.0 to 4.8.1 (#730) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e9172a4ce..42f848150 100644 --- a/pom.xml +++ b/pom.xml @@ -460,7 +460,7 @@ org.mockito mockito-core - 4.8.0 + 4.8.1 test From b36c7615aa8598fbe5697cb486e97f15cacb5899 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Nov 2022 21:49:56 +0000 Subject: [PATCH 078/269] chore(deps): bump libraries-bom from 26.1.3 to 26.1.4 (#732) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 42f848150..973d0f2c4 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.1.3 + 26.1.4 pom import From 51a0f272ce88d1b50e79db11c0f0e4a5967d2948 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Nov 2022 17:10:54 -0500 Subject: [PATCH 079/269] chore(deps): bump google-api-client-bom from 2.0.0 to 2.0.1 (#736) Bumps [google-api-client-bom](https://github.com/googleapis/google-api-java-client) from 2.0.0 to 2.0.1. - [Release notes](https://github.com/googleapis/google-api-java-client/releases) - [Changelog](https://github.com/googleapis/google-api-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-api-java-client/compare/v2.0.0...v2.0.1) --- updated-dependencies: - dependency-name: com.google.api-client:google-api-client-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 973d0f2c4..45274ef21 100644 --- a/pom.xml +++ b/pom.xml @@ -392,7 +392,7 @@ com.google.api-client google-api-client-bom - 2.0.0 + 2.0.1 pom import From 9f1583851a433c8f73b8d97ccb38315f99735108 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Tue, 8 Nov 2022 13:56:27 -0500 Subject: [PATCH 080/269] fix(auth): Set emulator credentials when using the Auth emulator (#734) If `FIREBASE_AUTH_EMULATOR_HOST` env var is set the SDK shouldn't connect to Auth servers for OAuth2 handshake. This PR sets the emulator credentials when emulator mode is set. --- .../firebase/ImplFirebaseTrampolines.java | 5 ++ .../firebase/database/FirebaseDatabase.java | 23 +------- .../internal/EmulatorCredentials.java | 52 +++++++++++++++++++ .../auth/FirebaseUserManagerTest.java | 15 +++++- .../FirebaseTenantClientTest.java | 16 +++++- 5 files changed, 85 insertions(+), 26 deletions(-) create mode 100644 src/main/java/com/google/firebase/internal/EmulatorCredentials.java diff --git a/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java b/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java index 3a7f6499a..0d3008b18 100644 --- a/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java +++ b/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java @@ -22,6 +22,8 @@ import com.google.api.core.ApiFutures; import com.google.auth.oauth2.GoogleCredentials; import com.google.cloud.firestore.FirestoreOptions; +import com.google.firebase.auth.internal.Utils; +import com.google.firebase.internal.EmulatorCredentials; import com.google.firebase.internal.FirebaseService; import com.google.firebase.internal.NonNull; @@ -41,6 +43,9 @@ public final class ImplFirebaseTrampolines { private ImplFirebaseTrampolines() {} public static GoogleCredentials getCredentials(@NonNull FirebaseApp app) { + if (Utils.isEmulatorMode()) { + return new EmulatorCredentials(); + } return app.getOptions().getCredentials(); } diff --git a/src/main/java/com/google/firebase/database/FirebaseDatabase.java b/src/main/java/com/google/firebase/database/FirebaseDatabase.java index 306ce9274..e59cd38fa 100644 --- a/src/main/java/com/google/firebase/database/FirebaseDatabase.java +++ b/src/main/java/com/google/firebase/database/FirebaseDatabase.java @@ -35,6 +35,7 @@ import com.google.firebase.database.utilities.ParsedUrl; import com.google.firebase.database.utilities.Utilities; import com.google.firebase.database.utilities.Validation; +import com.google.firebase.internal.EmulatorCredentials; import com.google.firebase.internal.FirebaseService; import com.google.firebase.internal.SdkUtils; @@ -406,26 +407,4 @@ public void destroy() { instance.destroy(); } } - - private static class EmulatorCredentials extends GoogleCredentials { - - EmulatorCredentials() { - super(newToken()); - } - - private static AccessToken newToken() { - return new AccessToken("owner", - new Date(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1))); - } - - @Override - public AccessToken refreshAccessToken() { - return newToken(); - } - - @Override - public Map> getRequestMetadata() throws IOException { - return ImmutableMap.of(); - } - } } diff --git a/src/main/java/com/google/firebase/internal/EmulatorCredentials.java b/src/main/java/com/google/firebase/internal/EmulatorCredentials.java new file mode 100644 index 000000000..b13e971d0 --- /dev/null +++ b/src/main/java/com/google/firebase/internal/EmulatorCredentials.java @@ -0,0 +1,52 @@ +/* + * Copyright 2022 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.auth.oauth2.AccessToken; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.common.collect.ImmutableMap; + +import java.io.IOException; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * A Mock Credentials implementation that can be used with the Emulator Suite + */ +public final class EmulatorCredentials extends GoogleCredentials { + + public EmulatorCredentials() { + super(newToken()); + } + + private static AccessToken newToken() { + return new AccessToken("owner", + new Date(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1))); + } + + @Override + public AccessToken refreshAccessToken() { + return newToken(); + } + + @Override + public Map> getRequestMetadata() throws IOException { + return ImmutableMap.of(); + } +} diff --git a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java index cd43c0147..f83c5f56f 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java @@ -71,8 +71,13 @@ public class FirebaseUserManagerTest { private static final String TEST_TOKEN = "token"; + private static final String TEST_EMULATOR_TOKEN = "owner"; + private static final GoogleCredentials credentials = new MockGoogleCredentials(TEST_TOKEN); + private static final GoogleCredentials emulator_credentials = + new MockGoogleCredentials(TEST_EMULATOR_TOKEN); + private static final ActionCodeSettings ACTION_CODE_SETTINGS = ActionCodeSettings.builder() .setUrl("https://example.dynamic.link") .setHandleCodeInApp(true) @@ -2916,7 +2921,7 @@ private static void initializeAppWithResponses(String... responses) { } MockHttpTransport transport = new MultiRequestMockHttpTransport(mocks); FirebaseApp.initializeApp(FirebaseOptions.builder() - .setCredentials(credentials) + .setCredentials(isEmulatorMode() ? emulator_credentials : credentials) .setHttpTransport(transport) .setProjectId("test-project-id") .build()); @@ -3014,7 +3019,7 @@ private static void checkSamlProviderConfig(SamlProviderConfig config, String pr private static void checkRequestHeaders(TestResponseInterceptor interceptor) { HttpHeaders headers = interceptor.getResponse().getRequest().getHeaders(); - String auth = "Bearer " + TEST_TOKEN; + String auth = "Bearer " + (isEmulatorMode() ? TEST_EMULATOR_TOKEN : TEST_TOKEN); assertEquals(auth, headers.getFirstHeaderStringValue("Authorization")); String clientVersion = "Java/Admin/" + SdkUtils.getVersion(); @@ -3027,6 +3032,12 @@ private static void checkUrl(TestResponseInterceptor interceptor, String method, assertEquals(url, request.getUrl().toString().split("\\?")[0]); } + private static boolean isEmulatorMode() { + return !Strings.isNullOrEmpty( + FirebaseProcessEnvironment.getenv("FIREBASE_AUTH_EMULATOR_HOST") + ); + } + private interface UserManagerOp { void call(FirebaseAuth auth) throws Exception; } 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 3e57dc3a1..eca55ee66 100644 --- a/src/test/java/com/google/firebase/auth/multitenancy/FirebaseTenantClientTest.java +++ b/src/test/java/com/google/firebase/auth/multitenancy/FirebaseTenantClientTest.java @@ -33,6 +33,7 @@ import com.google.api.client.testing.http.MockHttpTransport; import com.google.api.client.testing.http.MockLowLevelHttpResponse; import com.google.auth.oauth2.GoogleCredentials; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.firebase.ErrorCode; @@ -63,8 +64,13 @@ public class FirebaseTenantClientTest { private static final String TEST_TOKEN = "token"; + private static final String TEST_EMULATOR_TOKEN = "owner"; + private static final GoogleCredentials credentials = new MockGoogleCredentials(TEST_TOKEN); + private static final GoogleCredentials emulator_credentials = + new MockGoogleCredentials(TEST_EMULATOR_TOKEN); + private static final String PROJECT_BASE_URL = "https://identitytoolkit.googleapis.com/v2/projects/test-project-id"; @@ -342,7 +348,7 @@ private static void checkTenant(Tenant tenant, String tenantId) { private static void checkRequestHeaders(TestResponseInterceptor interceptor) { HttpHeaders headers = interceptor.getResponse().getRequest().getHeaders(); - String auth = "Bearer " + TEST_TOKEN; + String auth = "Bearer " + (isEmulatorMode() ? TEST_EMULATOR_TOKEN : TEST_TOKEN); assertEquals(auth, headers.getFirstHeaderStringValue("Authorization")); String clientVersion = "Java/Admin/" + SdkUtils.getVersion(); @@ -362,7 +368,7 @@ private static TestResponseInterceptor initializeAppForTenantManagement(String.. } MockHttpTransport transport = new MultiRequestMockHttpTransport(mocks); FirebaseApp.initializeApp(FirebaseOptions.builder() - .setCredentials(credentials) + .setCredentials(isEmulatorMode() ? emulator_credentials : credentials) .setHttpTransport(transport) .setProjectId("test-project-id") .build()); @@ -408,4 +414,10 @@ private static GenericJson parseRequestContent(TestResponseInterceptor intercept interceptor.getResponse().getRequest().getContent().writeTo(out); return JSON_FACTORY.fromString(new String(out.toByteArray()), GenericJson.class); } + + private static boolean isEmulatorMode() { + return !Strings.isNullOrEmpty( + FirebaseProcessEnvironment.getenv("FIREBASE_AUTH_EMULATOR_HOST") + ); + } } From d24289a6663625436ff2f19cf0ac86c258458370 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Thu, 10 Nov 2022 14:26:22 -0500 Subject: [PATCH 081/269] [chore] Release 9.1.1 (#737) - Release v9.1.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 45274ef21..ffd516561 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 9.1.0 + 9.1.1 jar firebase-admin From b819554db507f71409b230d0b4615de3101f882a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Nov 2022 17:02:10 -0500 Subject: [PATCH 082/269] chore(deps): bump netty.version from 4.1.84.Final to 4.1.85.Final (#739) Bumps `netty.version` from 4.1.84.Final to 4.1.85.Final. Updates `netty-codec-http` from 4.1.84.Final to 4.1.85.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.84.Final...netty-4.1.85.Final) Updates `netty-handler` from 4.1.84.Final to 4.1.85.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.84.Final...netty-4.1.85.Final) Updates `netty-transport` from 4.1.84.Final to 4.1.85.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.84.Final...netty-4.1.85.Final) --- updated-dependencies: - dependency-name: io.netty:netty-codec-http dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-handler dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-transport dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ffd516561..31026463b 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.84.Final + 4.1.85.Final From c6b4144b40fb81db7234bae881b7c3ede053e2e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Nov 2022 22:05:18 +0000 Subject: [PATCH 083/269] chore(deps-dev): bump mockito-core from 4.8.1 to 4.9.0 (#740) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 31026463b..20b4bfa95 100644 --- a/pom.xml +++ b/pom.xml @@ -460,7 +460,7 @@ org.mockito mockito-core - 4.8.1 + 4.9.0 test From 8cbb5d2e6999ab7952ec4d8e4c0ee6eb09f03935 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Nov 2022 17:26:38 -0500 Subject: [PATCH 084/269] chore(deps): bump libraries-bom from 26.1.4 to 26.1.5 (#741) Bumps [libraries-bom](https://github.com/googleapis/java-cloud-bom) from 26.1.4 to 26.1.5. - [Release notes](https://github.com/googleapis/java-cloud-bom/releases) - [Changelog](https://github.com/googleapis/java-cloud-bom/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/java-cloud-bom/compare/libraries-bom-v26.1.4...libraries-bom-v26.1.5) --- updated-dependencies: - dependency-name: com.google.cloud:libraries-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 20b4bfa95..bfeb2b329 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.1.4 + 26.1.5 pom import From 61454914dedefb847e60a218db2c9d059f2f47e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Nov 2022 22:29:32 +0000 Subject: [PATCH 085/269] chore(deps): bump slf4j-api from 2.0.3 to 2.0.4 (#742) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bfeb2b329..3763023f4 100644 --- a/pom.xml +++ b/pom.xml @@ -438,7 +438,7 @@ org.slf4j slf4j-api - 2.0.3 + 2.0.4 io.netty From f0d6f499b306f9a6959749d31c4a8c18e8c8fe52 Mon Sep 17 00:00:00 2001 From: "Nhien (Ricky) Lam" <62775270+NhienLam@users.noreply.github.com> Date: Tue, 13 Dec 2022 10:29:06 -0800 Subject: [PATCH 086/269] Fix a small bug in setCustomUserClaims() auth snippet (#747) * fix a small bug in setCustomUserClaims() auth snippet * Revert changes in setCustomUserClaims and create new function setCustomUserClaimsTenant --- .../snippets/FirebaseAuthSnippets.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/test/java/com/google/firebase/snippets/FirebaseAuthSnippets.java b/src/test/java/com/google/firebase/snippets/FirebaseAuthSnippets.java index cd73908da..500bb2197 100644 --- a/src/test/java/com/google/firebase/snippets/FirebaseAuthSnippets.java +++ b/src/test/java/com/google/firebase/snippets/FirebaseAuthSnippets.java @@ -1150,6 +1150,33 @@ public void customClaimsVerifyTenant( // [END verify_custom_claims_tenant] } + public static void setCustomUserClaimsTenant(TenantAwareFirebaseAuth tenantAuth, + String uid) throws FirebaseAuthException { + // [START set_custom_user_claims_tenant] + // Set admin privilege on the user corresponding to uid in a specific tenant. + Map claims = new HashMap<>(); + claims.put("admin", true); + tenantAuth.setCustomUserClaims(uid, claims); + // The new custom claims will propagate to the user's ID token the + // next time a new one is issued. + // [END set_custom_user_claims_tenant] + + String idToken = "id_token"; + // [START verify_custom_claims_tenant] + // Verify the ID token first. + FirebaseToken decoded = tenantAuth.verifyIdToken(idToken); + if (Boolean.TRUE.equals(decoded.getClaims().get("admin"))) { + // Allow access to requested admin resource. + } + // [END verify_custom_claims_tenant] + + // [START read_custom_user_claims_tenant] + // Lookup the user associated with the specified uid in a specific tenant. + UserRecord user = tenantAuth.getUser(uid); + System.out.println(user.getCustomClaims().get("admin")); + // [END read_custom_user_claims_tenant] + } + public void generateEmailVerificationLinkTenant( TenantAwareFirebaseAuth tenantAuth, String email, From b842b04c9a5996f2b978d43b3a41cbd4f5908d50 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Dec 2022 18:33:36 +0000 Subject: [PATCH 087/269] chore(deps): bump slf4j-api from 2.0.4 to 2.0.6 (#749) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3763023f4..11aa32772 100644 --- a/pom.xml +++ b/pom.xml @@ -438,7 +438,7 @@ org.slf4j slf4j-api - 2.0.4 + 2.0.6 io.netty From ebfda3c03346b7c6b5b4ad7fd578458d2ae8d95a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Dec 2022 18:35:58 +0000 Subject: [PATCH 088/269] chore(deps): bump google-api-client-bom from 2.0.1 to 2.1.1 (#746) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 11aa32772..8d22ca780 100644 --- a/pom.xml +++ b/pom.xml @@ -392,7 +392,7 @@ com.google.api-client google-api-client-bom - 2.0.1 + 2.1.1 pom import From 01cc4ceeabefdfbd3601169abd0f4602afe565b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Dec 2022 18:39:15 +0000 Subject: [PATCH 089/269] chore(deps): bump netty-codec-http from 4.1.85.Final to 4.1.86.Final (#748) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8d22ca780..a9c06e20c 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.85.Final + 4.1.86.Final From 239b86475e8c1f3f8ace920cf7bdaa3528b21a6a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Jan 2023 23:36:13 +0000 Subject: [PATCH 090/269] chore(deps): bump google-api-client-bom from 2.1.1 to 2.1.2 (#756) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a9c06e20c..e6e02c282 100644 --- a/pom.xml +++ b/pom.xml @@ -392,7 +392,7 @@ com.google.api-client google-api-client-bom - 2.1.1 + 2.1.2 pom import From 4ca33c8b2984a8fb5061265441667906e890039d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Jan 2023 23:38:57 +0000 Subject: [PATCH 091/269] chore(deps-dev): bump mockito-core from 4.9.0 to 4.11.0 (#754) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e6e02c282..c5e2a0677 100644 --- a/pom.xml +++ b/pom.xml @@ -460,7 +460,7 @@ org.mockito mockito-core - 4.9.0 + 4.11.0 test From c19e812ae77edcc0a0571495f280516d808841dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Jan 2023 23:42:21 +0000 Subject: [PATCH 092/269] chore(deps): bump libraries-bom from 26.1.5 to 26.3.0 (#755) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c5e2a0677..401cf6e07 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.1.5 + 26.3.0 pom import From dc40455ba22f3b269ba72af0da65570c5ed735e1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Jan 2023 17:40:57 -0500 Subject: [PATCH 093/269] chore(deps): bump netty.version from 4.1.86.Final to 4.1.87.Final (#757) Bumps `netty.version` from 4.1.86.Final to 4.1.87.Final. Updates `netty-codec-http` from 4.1.86.Final to 4.1.87.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.86.Final...netty-4.1.87.Final) Updates `netty-handler` from 4.1.86.Final to 4.1.87.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.86.Final...netty-4.1.87.Final) Updates `netty-transport` from 4.1.86.Final to 4.1.87.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.86.Final...netty-4.1.87.Final) --- updated-dependencies: - dependency-name: io.netty:netty-codec-http dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-handler dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-transport dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 401cf6e07..0cf873d4d 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.86.Final + 4.1.87.Final From aa27dc00a24f84c8ec163c1598f0e1ad2c800169 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Jan 2023 22:44:13 +0000 Subject: [PATCH 094/269] chore(deps): bump maven-project-info-reports-plugin from 3.4.1 to 3.4.2 (#758) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0cf873d4d..e064b6ab9 100644 --- a/pom.xml +++ b/pom.xml @@ -367,7 +367,7 @@ maven-project-info-reports-plugin - 3.4.1 + 3.4.2 From 1ceb40b4b6bfc890386e384ba81f214e500e8db7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Jan 2023 15:59:32 -0500 Subject: [PATCH 095/269] chore(deps): bump libraries-bom from 26.3.0 to 26.4.0 (#760) Bumps [libraries-bom](https://github.com/googleapis/java-cloud-bom) from 26.3.0 to 26.4.0. - [Release notes](https://github.com/googleapis/java-cloud-bom/releases) - [Changelog](https://github.com/googleapis/java-cloud-bom/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/java-cloud-bom/compare/libraries-bom-v26.3.0...v26.4.0) --- updated-dependencies: - dependency-name: com.google.cloud:libraries-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e064b6ab9..27201152f 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.3.0 + 26.4.0 pom import From e8ec68c97929972acdbe05b79b5ea18f077a08a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Jan 2023 21:02:41 +0000 Subject: [PATCH 096/269] chore(deps): bump google-api-client-bom from 2.1.2 to 2.2.0 (#762) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 27201152f..6209ecaf5 100644 --- a/pom.xml +++ b/pom.xml @@ -392,7 +392,7 @@ com.google.api-client google-api-client-bom - 2.1.2 + 2.2.0 pom import From 95e542790e1b22a26258a10f7edff3a5a29c86d4 Mon Sep 17 00:00:00 2001 From: "Nhien (Ricky) Lam" <62775270+NhienLam@users.noreply.github.com> Date: Tue, 28 Feb 2023 10:24:02 -0800 Subject: [PATCH 097/269] Update passwordHash and passwordSalt to be base64 decoded before user import (#775) --- .../com/google/firebase/snippets/FirebaseAuthSnippets.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/google/firebase/snippets/FirebaseAuthSnippets.java b/src/test/java/com/google/firebase/snippets/FirebaseAuthSnippets.java index 500bb2197..f40c00da5 100644 --- a/src/test/java/com/google/firebase/snippets/FirebaseAuthSnippets.java +++ b/src/test/java/com/google/firebase/snippets/FirebaseAuthSnippets.java @@ -569,8 +569,8 @@ public void importWithScrypt() { List users = Collections.singletonList(ImportUserRecord.builder() .setUid("some-uid") .setEmail("user@example.com") - .setPasswordHash("password-hash".getBytes()) - .setPasswordSalt("salt".getBytes()) + .setPasswordHash(BaseEncoding.base64().decode("password-hash")) + .setPasswordSalt(BaseEncoding.base64().decode("salt")) .build()); UserImportOptions options = UserImportOptions.withHash( Scrypt.builder() From dd9ee43ac857721c2f29c537b011b968b21078c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Mar 2023 17:37:03 +0000 Subject: [PATCH 098/269] chore(deps): bump libraries-bom from 26.4.0 to 26.10.0 (#782) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6209ecaf5..62531dd44 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.4.0 + 26.10.0 pom import From cbc1a7af434e27e4cb06155e4ba72b9de1f768ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Mar 2023 17:39:40 +0000 Subject: [PATCH 099/269] chore(deps): bump maven-javadoc-plugin from 3.4.1 to 3.5.0 (#774) --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 62531dd44..0931f5147 100644 --- a/pom.xml +++ b/pom.xml @@ -89,7 +89,7 @@ maven-javadoc-plugin - 3.4.1 + 3.5.0 site @@ -301,7 +301,7 @@ maven-javadoc-plugin - 3.4.1 + 3.5.0 attach-javadocs From 374bc11f20106922bb39b74216c2b4f47ba3cc8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Mar 2023 17:42:40 +0000 Subject: [PATCH 100/269] chore(deps): bump maven-compiler-plugin from 3.10.1 to 3.11.0 (#777) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0931f5147..9a0d4ad46 100644 --- a/pom.xml +++ b/pom.xml @@ -270,7 +270,7 @@ maven-compiler-plugin - 3.10.1 + 3.11.0 1.8 1.8 From e16d317bd9ed1be600f2c2c54dfc5137e62c599d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Mar 2023 17:45:07 +0000 Subject: [PATCH 101/269] chore(deps): bump netty.version from 4.1.87.Final to 4.1.90.Final (#787) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9a0d4ad46..29bf60cd7 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.87.Final + 4.1.90.Final From 89c4c739a0d1f8c925e1269ee208b2b2c90e8864 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Apr 2023 14:24:21 -0400 Subject: [PATCH 102/269] chore(deps): bump libraries-bom from 26.10.0 to 26.12.0 (#796) Bumps [libraries-bom](https://github.com/googleapis/java-cloud-bom) from 26.10.0 to 26.12.0. - [Release notes](https://github.com/googleapis/java-cloud-bom/releases) - [Changelog](https://github.com/googleapis/java-cloud-bom/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/java-cloud-bom/compare/v26.10.0...v26.12.0) --- updated-dependencies: - dependency-name: com.google.cloud:libraries-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 29bf60cd7..d85d97378 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.10.0 + 26.12.0 pom import From 3d260badec7e7e55b1e6defd8b66dd031c42201f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Apr 2023 18:26:55 +0000 Subject: [PATCH 103/269] chore(deps): bump maven-failsafe-plugin from 2.22.2 to 3.0.0 (#788) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d85d97378..ecb45926d 100644 --- a/pom.xml +++ b/pom.xml @@ -337,7 +337,7 @@ maven-failsafe-plugin - 2.22.2 + 3.0.0 From de51a7fe6599a72a68cdf135b1bc432763956366 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Apr 2023 18:29:25 +0000 Subject: [PATCH 104/269] chore(deps): bump slf4j-api from 2.0.6 to 2.0.7 (#790) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ecb45926d..77d20c702 100644 --- a/pom.xml +++ b/pom.xml @@ -438,7 +438,7 @@ org.slf4j slf4j-api - 2.0.6 + 2.0.7 io.netty From 3179f82fdd82481f3fdde58da7fe4a26a4b4cff7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Apr 2023 18:32:25 +0000 Subject: [PATCH 105/269] chore(deps): bump netty.version from 4.1.90.Final to 4.1.91.Final (#793) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 77d20c702..8b1aca116 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.90.Final + 4.1.91.Final From 7957d316b33994cef898784be2130ff0b9066f8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Apr 2023 18:35:36 +0000 Subject: [PATCH 106/269] chore(deps): bump maven-surefire-plugin from 2.22.2 to 3.0.0 (#789) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8b1aca116..463228c56 100644 --- a/pom.xml +++ b/pom.xml @@ -280,7 +280,7 @@ maven-surefire-plugin - 2.22.2 + 3.0.0 ${skipUTs} From 22aadefb3e90f019f1172f55cfedf73c4881245b Mon Sep 17 00:00:00 2001 From: Tom Andersen Date: Fri, 2 Jun 2023 12:22:44 -0400 Subject: [PATCH 107/269] Expose MultiDb API (#814) --- src/main/java/com/google/firebase/cloud/FirestoreClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/google/firebase/cloud/FirestoreClient.java b/src/main/java/com/google/firebase/cloud/FirestoreClient.java index e1672ec34..e55d5c7e9 100644 --- a/src/main/java/com/google/firebase/cloud/FirestoreClient.java +++ b/src/main/java/com/google/firebase/cloud/FirestoreClient.java @@ -94,7 +94,7 @@ public static Firestore getFirestore(FirebaseApp app) { * instance. */ @NonNull - static Firestore getFirestore(FirebaseApp app, String database) { + public static Firestore getFirestore(FirebaseApp app, String database) { return getInstance(app, database).firestore; } @@ -108,7 +108,7 @@ static Firestore getFirestore(FirebaseApp app, String database) { * instance. */ @NonNull - static Firestore getFirestore(String database) { + public static Firestore getFirestore(String database) { return getFirestore(FirebaseApp.getInstance(), database); } From 60f97c582d975eade0f21b72e2ace24f97ff0b59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 19:09:25 +0000 Subject: [PATCH 108/269] chore(deps): bump jacoco-maven-plugin from 0.8.8 to 0.8.10 (#809) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 463228c56..1e66dcb68 100644 --- a/pom.xml +++ b/pom.xml @@ -189,7 +189,7 @@ org.jacoco jacoco-maven-plugin - 0.8.8 + 0.8.10 pre-unit-test From abd0a8fb2ac4ede1c2a2ef3e3acc5bb4b49287f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 19:11:29 +0000 Subject: [PATCH 109/269] chore(deps): bump maven-project-info-reports-plugin from 3.4.2 to 3.4.4 (#813) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1e66dcb68..6accbfcab 100644 --- a/pom.xml +++ b/pom.xml @@ -367,7 +367,7 @@ maven-project-info-reports-plugin - 3.4.2 + 3.4.4 From 06ebf346ba42cdb1574a447304cab49f382e0ec8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jun 2023 13:58:39 -0400 Subject: [PATCH 110/269] chore(deps): bump libraries-bom from 26.12.0 to 26.16.0 (#817) Bumps [libraries-bom](https://github.com/googleapis/java-cloud-bom) from 26.12.0 to 26.16.0. - [Release notes](https://github.com/googleapis/java-cloud-bom/releases) - [Changelog](https://github.com/googleapis/java-cloud-bom/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/java-cloud-bom/compare/v26.12.0...v26.16.0) --- updated-dependencies: - dependency-name: com.google.cloud:libraries-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6accbfcab..1da771e36 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.12.0 + 26.16.0 pom import From 167f16be4d4b8b0ab861b11e52bc7d4318382318 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jun 2023 18:05:27 +0000 Subject: [PATCH 111/269] chore(deps): bump netty.version from 4.1.91.Final to 4.1.93.Final (#816) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1da771e36..10eacc245 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.91.Final + 4.1.93.Final From 17fff8374ccb1a40a7835df534cc6655d81afc34 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jun 2023 13:01:00 -0400 Subject: [PATCH 112/269] chore(deps): bump maven-failsafe-plugin from 3.0.0 to 3.1.0 (#820) Bumps [maven-failsafe-plugin](https://github.com/apache/maven-surefire) from 3.0.0 to 3.1.0. - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.0.0...surefire-3.1.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-failsafe-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 10eacc245..260dbfc2c 100644 --- a/pom.xml +++ b/pom.xml @@ -337,7 +337,7 @@ maven-failsafe-plugin - 3.0.0 + 3.1.0 From 27ab2916720c56e1badfe147e38233197ec4ffe5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jun 2023 17:04:37 +0000 Subject: [PATCH 113/269] chore(deps): bump maven-gpg-plugin from 3.0.1 to 3.1.0 (#823) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 260dbfc2c..048856fca 100644 --- a/pom.xml +++ b/pom.xml @@ -157,7 +157,7 @@ maven-gpg-plugin - 3.0.1 + 3.1.0 sign-artifacts From 090dd3afd1597c06a2b9b257d5e223f523579b1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jun 2023 17:08:13 +0000 Subject: [PATCH 114/269] chore(deps): bump maven-source-plugin from 3.2.1 to 3.3.0 (#821) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 048856fca..a21a568fe 100644 --- a/pom.xml +++ b/pom.xml @@ -289,7 +289,7 @@ maven-source-plugin - 3.2.1 + 3.3.0 attach-sources From 25dfab675a4cda94e37ada0a4cc8998ca23523e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jun 2023 17:10:41 +0000 Subject: [PATCH 115/269] chore(deps): bump maven-surefire-plugin from 3.0.0 to 3.1.0 (#822) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a21a568fe..dfd425ef0 100644 --- a/pom.xml +++ b/pom.xml @@ -280,7 +280,7 @@ maven-surefire-plugin - 3.0.0 + 3.1.0 ${skipUTs} From d1baee366cebf121aa6d5a62593c08cc26ce97a8 Mon Sep 17 00:00:00 2001 From: Doris-Ge Date: Thu, 8 Jun 2023 13:10:31 -0700 Subject: [PATCH 116/269] feat(fcm): Implement `sendEach`, `sendEachAsync`, `sendEachForMulticast` and `sendEachForMulticastAsync` (#785) (#815) * feat(fcm): Implement `sendEach`, `sendEachAsync`, `sendEachForMulticast` and `sendEachForMulticastAsync` (#785) * Add org.hamcrest as dependency for unit tests * Implement sendEach, sendEachAsync, sendEachForMulticast and sendEachForMulticastAsync `sendEach` vs `sendAll` 1. `sendEach` sends one HTTP request to V1 Send endpoint for each message in the array. `sendAll` sends only one HTTP request to V1 Batch Send endpoint to send all messages in the array. 2. `sendEach` calls `messagingClient.send` to send each message and constructs a `SendResponse` with the returned `messageId`. If `messagingClient.send` throws out an exception, `sendEach` will catch the exception and also turn it into a `SendResponse` with the exception in it. `sendEach` calls `ApiFutures.allAsList().get()` to execute all `messagingClient.send` calls asynchronously and wait for all of them to complete and construct a `BatchResponse` with all `SendResponse`s. Therefore, unlike `sendAll`, `sendEach` does not always throw an error for a total failure. It can also return a `BatchResponse` with only errors in it. `sendEachForMulticast` calls `sendEach` under the hood. `sendEachAsync` is the async version of `sendEach`. `sendEachForMulticastAsync` is the async version of `sendEachForMulticast`. * Add integration tests for batch-send re-implementation: testSendEach(), testSendFiveHundredWithSendEach(), testSendEachForMulticast() * Replace all "-- i.e." in the comments with "meaning that" * Fix the build errors caused by "Line is longer than 100 characters" * Fix the build errors caused by "package does not exist" * Address doc review comments --- pom.xml | 6 + .../firebase/messaging/FirebaseMessaging.java | 221 ++++++++++++- .../messaging/FirebaseMessagingIT.java | 97 ++++++ .../messaging/FirebaseMessagingTest.java | 306 +++++++++++++++++- 4 files changed, 618 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index dfd425ef0..0a67f6c14 100644 --- a/pom.xml +++ b/pom.xml @@ -482,5 +482,11 @@ 2.1.1 test + + org.hamcrest + hamcrest + 2.2 + test + diff --git a/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java b/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java index 95366eaa7..882fec8fc 100644 --- a/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java +++ b/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java @@ -20,18 +20,22 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutures; import com.google.common.annotations.VisibleForTesting; 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.firebase.ErrorCode; import com.google.firebase.FirebaseApp; import com.google.firebase.ImplFirebaseTrampolines; import com.google.firebase.internal.CallableOperation; import com.google.firebase.internal.FirebaseService; import com.google.firebase.internal.NonNull; +import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ExecutionException; /** * This class is the entry point for all server-side Firebase Cloud Messaging actions. @@ -91,7 +95,7 @@ public String send(@NonNull Message message) throws FirebaseMessagingException { * *

If the {@code dryRun} option is set to true, the message will not be actually sent. Instead * FCM performs all the necessary validations and emulates the send operation. The {@code dryRun} - * option is useful for determining whether an FCM registration has been deleted. However, it + * option is useful for determining whether an FCM registration has been deleted. However, it * cannot be used to validate APNs tokens. * * @param message A non-null {@link Message} to be sent. @@ -139,6 +143,191 @@ protected String execute() throws FirebaseMessagingException { }; } + /** + * Sends each message in the given list via Firebase Cloud Messaging. + * Unlike {@link #sendAll(List)}, this method makes an HTTP call for each message in the + * given array. + * + *

The list of responses obtained by calling {@link BatchResponse#getResponses()} on the return + * value is in the same order as the input list. + * + * @param messages A non-null, non-empty list containing up to 500 messages. + * @return A {@link BatchResponse} indicating the result of the operation. + * @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for + * delivery. An exception here or a {@link BatchResponse} with all failures indicates a total + * failure, meaning that none of the messages in the list could be sent. Partial failures or + * no failures are only indicated by a {@link BatchResponse}. + */ + public BatchResponse sendEach(@NonNull List messages) throws FirebaseMessagingException { + return sendEachOp(messages, false).call(); + } + + + /** + * Sends each message in the given list via Firebase Cloud Messaging. + * Unlike {@link #sendAll(List)}, this method makes an HTTP call for each message in the + * given array. + * + *

If the {@code dryRun} option is set to true, the message will not be actually sent. Instead + * FCM performs all the necessary validations, and emulates the send operation. The {@code dryRun} + * option is useful for determining whether an FCM registration has been deleted. But it cannot be + * used to validate APNs tokens. + * + *

The list of responses obtained by calling {@link BatchResponse#getResponses()} on the return + * value is in the same order as the input list. + * + * @param messages A non-null, non-empty list containing up to 500 messages. + * @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send. + * @return A {@link BatchResponse} indicating the result of the operation. + * @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for + * delivery. An exception here or a {@link BatchResponse} with all failures indicates a total + * failure, meaning that none of the messages in the list could be sent. Partial failures or + * no failures are only indicated by a {@link BatchResponse}. + */ + public BatchResponse sendEach( + @NonNull List messages, boolean dryRun) throws FirebaseMessagingException { + return sendEachOp(messages, dryRun).call(); + } + + /** + * Similar to {@link #sendEach(List)} but performs the operation asynchronously. + * + * @param messages A non-null, non-empty list containing up to 500 messages. + * @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when + * the messages have been sent. + */ + public ApiFuture sendEachAsync(@NonNull List messages) { + return sendEachOp(messages, false).callAsync(app); + } + + /** + * Similar to {@link #sendEach(List, boolean)} but performs the operation asynchronously. + * + * @param messages A non-null, non-empty list containing up to 500 messages. + * @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send. + * @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when + * the messages have been sent. + */ + public ApiFuture sendEachAsync(@NonNull List messages, boolean dryRun) { + return sendEachOp(messages, dryRun).callAsync(app); + } + + private CallableOperation sendEachOp( + final List messages, final boolean dryRun) { + final List immutableMessages = ImmutableList.copyOf(messages); + checkArgument(!immutableMessages.isEmpty(), "messages list must not be empty"); + checkArgument(immutableMessages.size() <= 500, + "messages list must not contain more than 500 elements"); + + return new CallableOperation() { + @Override + protected BatchResponse execute() throws FirebaseMessagingException { + List> list = new ArrayList<>(); + for (Message message : immutableMessages) { + ApiFuture messageId = sendOpForSendResponse(message, dryRun).callAsync(app); + list.add(messageId); + } + try { + List responses = ApiFutures.allAsList(list).get(); + return new BatchResponseImpl(responses); + } catch (InterruptedException | ExecutionException e) { + throw new FirebaseMessagingException(ErrorCode.CANCELLED, SERVICE_ID); + } + } + }; + } + + private CallableOperation sendOpForSendResponse( + final Message message, final boolean dryRun) { + checkNotNull(message, "message must not be null"); + final FirebaseMessagingClient messagingClient = getMessagingClient(); + return new CallableOperation() { + @Override + protected SendResponse execute() { + try { + String messageId = messagingClient.send(message, dryRun); + return SendResponse.fromMessageId(messageId); + } catch (FirebaseMessagingException e) { + return SendResponse.fromException(e); + } + } + }; + } + + /** + * Sends the given multicast message to all the FCM registration tokens specified in it. + * + *

This method uses the {@link #sendEach(List)} API under the hood to send the given + * message to all the target recipients. The list of responses obtained by calling + * {@link BatchResponse#getResponses()} on the return value is in the same order as the + * tokens in the {@link MulticastMessage}. + * + * @param message A non-null {@link MulticastMessage} + * @return A {@link BatchResponse} indicating the result of the operation. + * @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for + * delivery. An exception here or a {@link BatchResponse} with all failures indicates a total + * failure, meaning that none of the messages in the list could be sent. Partial failures or + * no failures are only indicated by a {@link BatchResponse}. + */ + public BatchResponse sendEachForMulticast( + @NonNull MulticastMessage message) throws FirebaseMessagingException { + return sendEachForMulticast(message, false); + } + + /** + * Sends the given multicast message to all the FCM registration tokens specified in it. + * + *

If the {@code dryRun} option is set to true, the message will not be actually sent. Instead + * FCM performs all the necessary validations, and emulates the send operation. The {@code dryRun} + * option is useful for determining whether an FCM registration has been deleted. But it cannot be + * used to validate APNs tokens. + * + *

This method uses the {@link #sendEach(List)} API under the hood to send the given + * message to all the target recipients. The list of responses obtained by calling + * {@link BatchResponse#getResponses()} on the return value is in the same order as the + * tokens in the {@link MulticastMessage}. + * + * @param message A non-null {@link MulticastMessage}. + * @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send. + * @return A {@link BatchResponse} indicating the result of the operation. + * @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for + * delivery. An exception here or a {@link BatchResponse} with all failures indicates a total + * failure, meaning that none of the messages in the list could be sent. Partial failures or + * no failures are only indicated by a {@link BatchResponse}. + */ + public BatchResponse sendEachForMulticast(@NonNull MulticastMessage message, boolean dryRun) + throws FirebaseMessagingException { + checkNotNull(message, "multicast message must not be null"); + return sendEach(message.getMessageList(), dryRun); + } + + /** + * Similar to {@link #sendEachForMulticast(MulticastMessage)} but performs the operation + * asynchronously. + * + * @param message A non-null {@link MulticastMessage}. + * @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when + * the messages have been sent. + */ + public ApiFuture sendEachForMulticastAsync(@NonNull MulticastMessage message) { + return sendEachForMulticastAsync(message, false); + } + + /** + * Similar to {@link #sendEachForMulticast(MulticastMessage, boolean)} but performs the operation + * asynchronously. + * + * @param message A non-null {@link MulticastMessage}. + * @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send. + * @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when + * the messages have been sent. + */ + public ApiFuture sendEachForMulticastAsync( + @NonNull MulticastMessage message, boolean dryRun) { + checkNotNull(message, "multicast message must not be null"); + return sendEachAsync(message.getMessageList(), dryRun); + } + /** * Sends all the messages in the given list via Firebase Cloud Messaging. Employs batching to * send the entire list as a single RPC call. Compared to the {@link #send(Message)} method, this @@ -150,8 +339,10 @@ protected String execute() throws FirebaseMessagingException { * @param messages A non-null, non-empty list containing up to 500 messages. * @return A {@link BatchResponse} indicating the result of the operation. * @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for - * delivery. An exception here indicates a total failure -- i.e. none of the messages in the - * list could be sent. Partial failures are indicated by a {@link BatchResponse} return value. + * delivery. An exception here indicates a total failure, meaning that none of the messages in + * the list could be sent. Partial failures are indicated by a {@link BatchResponse} return + * value. + * @deprecated Use {@link #sendEach(List)} instead. */ public BatchResponse sendAll( @NonNull List messages) throws FirebaseMessagingException { @@ -175,8 +366,10 @@ public BatchResponse sendAll( * @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send. * @return A {@link BatchResponse} indicating the result of the operation. * @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for - * delivery. An exception here indicates a total failure -- i.e. none of the messages in the - * list could be sent. Partial failures are indicated by a {@link BatchResponse} return value. + * delivery. An exception here indicates a total failure, meaning that none of the messages in + * the list could be sent. Partial failures are indicated by a {@link BatchResponse} return + * value. + * @deprecated Use {@link #sendEach(List, boolean)} instead. */ public BatchResponse sendAll( @NonNull List messages, boolean dryRun) throws FirebaseMessagingException { @@ -187,8 +380,9 @@ public BatchResponse sendAll( * Similar to {@link #sendAll(List)} but performs the operation asynchronously. * * @param messages A non-null, non-empty list containing up to 500 messages. - * @return @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when + * @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when * the messages have been sent. + * @deprecated Use {@link #sendEachAsync(List)} instead. */ public ApiFuture sendAllAsync(@NonNull List messages) { return sendAllAsync(messages, false); @@ -199,8 +393,9 @@ public ApiFuture sendAllAsync(@NonNull List messages) { * * @param messages A non-null, non-empty list containing up to 500 messages. * @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send. - * @return @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when + * @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when * the messages have been sent, or when the emulation has finished. + * @deprecated Use {@link #sendEachAsync(List, boolean)} instead. */ public ApiFuture sendAllAsync( @NonNull List messages, boolean dryRun) { @@ -218,9 +413,10 @@ public ApiFuture sendAllAsync( * @param message A non-null {@link MulticastMessage} * @return A {@link BatchResponse} indicating the result of the operation. * @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for - * delivery. An exception here indicates a total failure -- i.e. the messages could not be - * delivered to any recipient. Partial failures are indicated by a {@link BatchResponse} + * delivery. An exception here indicates a total failure, meaning that the messages could not + * be delivered to any recipient. Partial failures are indicated by a {@link BatchResponse} * return value. + * @deprecated Use {@link #sendEachForMulticast(MulticastMessage)} instead. */ public BatchResponse sendMulticast( @NonNull MulticastMessage message) throws FirebaseMessagingException { @@ -244,9 +440,10 @@ public BatchResponse sendMulticast( * @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send. * @return A {@link BatchResponse} indicating the result of the operation. * @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for - * delivery. An exception here indicates a total failure -- i.e. the messages could not be - * delivered to any recipient. Partial failures are indicated by a {@link BatchResponse} + * delivery. An exception here indicates a total failure, meaning that the messages could not + * be delivered to any recipient. Partial failures are indicated by a {@link BatchResponse} * return value. + * @deprecated Use {@link #sendEachForMulticast(MulticastMessage, boolean)} instead. */ public BatchResponse sendMulticast( @NonNull MulticastMessage message, boolean dryRun) throws FirebaseMessagingException { @@ -261,6 +458,7 @@ public BatchResponse sendMulticast( * @param message A non-null {@link MulticastMessage}. * @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when * the messages have been sent. + * @deprecated Use {@link #sendEachForMulticastAsync(MulticastMessage)} instead. */ public ApiFuture sendMulticastAsync(@NonNull MulticastMessage message) { return sendMulticastAsync(message, false); @@ -274,6 +472,7 @@ public ApiFuture sendMulticastAsync(@NonNull MulticastMessage mes * @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send. * @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when * the messages have been sent. + * @deprecated Use {@link #sendEachForMulticastAsync(MulticastMessage, boolean)} instead. */ public ApiFuture sendMulticastAsync( @NonNull MulticastMessage message, boolean dryRun) { diff --git a/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java b/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java index 9ae7bba24..bebae6e4d 100644 --- a/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java +++ b/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java @@ -96,6 +96,103 @@ public void testSendError() throws InterruptedException { } } + @Test + public void testSendEach() throws Exception { + List messages = new ArrayList<>(); + messages.add( + Message.builder() + .setNotification(Notification.builder() + .setTitle("Title") + .setBody("Body") + .build()) + .setTopic("foo-bar") + .build()); + messages.add( + Message.builder() + .setNotification(Notification.builder() + .setTitle("Title") + .setBody("Body") + .build()) + .setTopic("foo-bar") + .build()); + messages.add( + Message.builder() + .setNotification(Notification.builder() + .setTitle("Title") + .setBody("Body") + .build()) + .setToken("not-a-token") + .build()); + + BatchResponse response = FirebaseMessaging.getInstance().sendEach(messages, true); + + assertEquals(2, response.getSuccessCount()); + assertEquals(1, response.getFailureCount()); + + List responses = response.getResponses(); + assertEquals(3, responses.size()); + assertTrue(responses.get(0).isSuccessful()); + String id = responses.get(0).getMessageId(); + assertTrue(id != null && id.matches("^projects/.*/messages/.*$")); + + assertTrue(responses.get(1).isSuccessful()); + id = responses.get(1).getMessageId(); + assertTrue(id != null && id.matches("^projects/.*/messages/.*$")); + + assertFalse(responses.get(2).isSuccessful()); + assertNull(responses.get(2).getMessageId()); + FirebaseMessagingException exception = responses.get(2).getException(); + assertNotNull(exception); + assertEquals(ErrorCode.INVALID_ARGUMENT, exception.getErrorCode()); + } + + @Test + public void testSendFiveHundredWithSendEach() throws Exception { + List messages = new ArrayList<>(); + for (int i = 0; i < 500; i++) { + messages.add(Message.builder().setTopic("foo-bar-" + (i % 10)).build()); + } + + BatchResponse response = FirebaseMessaging.getInstance().sendEach(messages, true); + + assertEquals(500, response.getResponses().size()); + assertEquals(500, response.getSuccessCount()); + assertEquals(0, response.getFailureCount()); + for (SendResponse sendResponse : response.getResponses()) { + if (!sendResponse.isSuccessful()) { + sendResponse.getException().printStackTrace(); + } + assertTrue(sendResponse.isSuccessful()); + String id = sendResponse.getMessageId(); + assertTrue(id != null && id.matches("^projects/.*/messages/.*$")); + assertNull(sendResponse.getException()); + } + } + + @Test + public void testSendEachForMulticast() throws Exception { + MulticastMessage multicastMessage = MulticastMessage.builder() + .setNotification(Notification.builder() + .setTitle("Title") + .setBody("Body") + .build()) + .addToken("not-a-token") + .addToken("also-not-a-token") + .build(); + + BatchResponse response = FirebaseMessaging.getInstance().sendEachForMulticast( + multicastMessage, true); + + assertEquals(0, response.getSuccessCount()); + assertEquals(2, response.getFailureCount()); + assertEquals(2, response.getResponses().size()); + for (SendResponse sendResponse : response.getResponses()) { + assertFalse(sendResponse.isSuccessful()); + assertNull(sendResponse.getMessageId()); + assertNotNull(sendResponse.getException()); + } + } + @Test public void testSendAll() throws Exception { List messages = new ArrayList<>(); diff --git a/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java b/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java index 8e8e79e07..dc07ce002 100644 --- a/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java +++ b/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java @@ -16,6 +16,9 @@ package com.google.firebase.messaging; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -27,13 +30,19 @@ 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.firebase.ErrorCode; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.auth.MockGoogleCredentials; +import com.google.firebase.internal.Nullable; + +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; + import org.junit.After; import org.junit.Test; @@ -46,6 +55,9 @@ public class FirebaseMessagingTest { private static final Message EMPTY_MESSAGE = Message.builder() .setTopic("test-topic") .build(); + private static final Message EMPTY_MESSAGE_2 = Message.builder() + .setTopic("test-topic2") + .build(); private static final MulticastMessage TEST_MULTICAST_MESSAGE = MulticastMessage.builder() .addToken("test-fcm-token1") .addToken("test-fcm-token2") @@ -262,6 +274,277 @@ public void testSendAsyncFailure() throws InterruptedException { assertFalse(client.isLastDryRun); } + @Test + public void testSendEachWithNull() throws FirebaseMessagingException { + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId(null); + FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); + + try { + messaging.sendEach(null); + fail("No error thrown for null message list"); + } catch (NullPointerException expected) { + // expected + } + + assertNull(client.lastMessage); + } + + @Test + public void testSendEachWithEmptyList() throws FirebaseMessagingException { + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId(null); + FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); + + try { + messaging.sendEach(ImmutableList.of()); + fail("No error thrown for empty message list"); + } catch (IllegalArgumentException expected) { + // expected + } + + assertNull(client.lastMessage); + } + + @Test + public void testSendEachWithTooManyMessages() throws FirebaseMessagingException { + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId(null); + FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); + ImmutableList.Builder listBuilder = ImmutableList.builder(); + for (int i = 0; i < 501; i++) { + listBuilder.add(Message.builder().setTopic("topic").build()); + } + + try { + messaging.sendEach(listBuilder.build(), false); + fail("No error thrown for too many messages in the list"); + } catch (IllegalArgumentException expected) { + // expected + } + + assertNull(client.lastMessage); + } + + @Test + public void testSendEach() throws FirebaseMessagingException { + ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE, EMPTY_MESSAGE_2); + ImmutableList messageIds = ImmutableList.of("test1", "test2"); + Map messageMap = new HashMap<>(); + for (int i = 0; i < 2; i++) { + messageMap.put(messages.get(i), SendResponse.fromMessageId(messageIds.get(i))); + } + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageMap(messageMap); + FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); + + BatchResponse response = messaging.sendEach(messages); + + assertEquals(2, response.getSuccessCount()); + for (int i = 0; i < 2; i++) { + assertEquals(messageIds.get(i), response.getResponses().get(i).getMessageId()); + } + assertThat(client.lastMessage, anyOf(is(EMPTY_MESSAGE), is(EMPTY_MESSAGE_2))); + assertFalse(client.isLastDryRun); + } + + @Test + public void testSendEachDryRun() throws FirebaseMessagingException { + ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE, EMPTY_MESSAGE_2); + ImmutableList messageIds = ImmutableList.of("test1", "test2"); + Map messageMap = new HashMap<>(); + for (int i = 0; i < 2; i++) { + messageMap.put(messages.get(i), SendResponse.fromMessageId(messageIds.get(i))); + } + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageMap(messageMap); + FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); + + BatchResponse response = messaging.sendEach(messages, true); + + assertEquals(2, response.getSuccessCount()); + for (int i = 0; i < 2; i++) { + assertEquals(messageIds.get(i), response.getResponses().get(i).getMessageId()); + } + assertThat(client.lastMessage, anyOf(is(EMPTY_MESSAGE), is(EMPTY_MESSAGE_2))); + assertTrue(client.isLastDryRun); + } + + @Test + public void testSendEachFailure() throws FirebaseMessagingException { + ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE, EMPTY_MESSAGE_2); + Map messageMap = new HashMap<>(); + messageMap.put(messages.get(0), SendResponse.fromMessageId("test")); + messageMap.put(messages.get(1), SendResponse.fromException(TEST_EXCEPTION)); + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageMap(messageMap); + FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); + + BatchResponse response = messaging.sendEach(messages); + + assertEquals(1, response.getFailureCount()); + assertEquals(1, response.getSuccessCount()); + assertEquals("test", response.getResponses().get(0).getMessageId()); + assertEquals(TEST_EXCEPTION, response.getResponses().get(1).getException()); + assertFalse(client.isLastDryRun); + } + + @Test + public void testSendEachAsync() throws Exception { + ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE, EMPTY_MESSAGE_2); + ImmutableList messageIds = ImmutableList.of("test1", "test2"); + Map messageMap = new HashMap<>(); + for (int i = 0; i < 2; i++) { + messageMap.put(messages.get(i), SendResponse.fromMessageId(messageIds.get(i))); + } + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageMap(messageMap); + FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); + + BatchResponse response = messaging.sendEachAsync(messages).get(); + + assertEquals(2, response.getSuccessCount()); + for (int i = 0; i < 2; i++) { + assertEquals(messageIds.get(i), response.getResponses().get(i).getMessageId()); + } + assertThat(client.lastMessage, anyOf(is(EMPTY_MESSAGE), is(EMPTY_MESSAGE_2))); + assertFalse(client.isLastDryRun); + } + + @Test + public void testSendEachAsyncDryRun() throws Exception { + ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE, EMPTY_MESSAGE_2); + ImmutableList messageIds = ImmutableList.of("test1", "test2"); + Map messageMap = new HashMap<>(); + for (int i = 0; i < 2; i++) { + messageMap.put(messages.get(i), SendResponse.fromMessageId(messageIds.get(i))); + } + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageMap(messageMap); + FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); + + BatchResponse response = messaging.sendEachAsync(messages, true).get(); + + assertEquals(2, response.getSuccessCount()); + for (int i = 0; i < 2; i++) { + assertEquals(messageIds.get(i), response.getResponses().get(i).getMessageId()); + } + assertThat(client.lastMessage, anyOf(is(EMPTY_MESSAGE), is(EMPTY_MESSAGE_2))); + assertTrue(client.isLastDryRun); + } + + @Test + public void testSendEachAsyncFailure() throws Exception { + ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE, EMPTY_MESSAGE_2); + Map messageMap = new HashMap<>(); + messageMap.put(messages.get(0), SendResponse.fromMessageId("test")); + messageMap.put(messages.get(1), SendResponse.fromException(TEST_EXCEPTION)); + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageMap(messageMap); + FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); + + BatchResponse response = messaging.sendEachAsync(messages).get(); + + assertEquals(1, response.getFailureCount()); + assertEquals(1, response.getSuccessCount()); + assertEquals("test", response.getResponses().get(0).getMessageId()); + assertEquals(TEST_EXCEPTION, response.getResponses().get(1).getException()); + assertFalse(client.isLastDryRun); + } + + @Test + public void testSendEachForMulticastWithNull() throws FirebaseMessagingException { + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId(null); + FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); + + try { + messaging.sendEachForMulticast(null); + fail("No error thrown for null multicast message"); + } catch (NullPointerException expected) { + // expected + } + + assertNull(client.lastMessage); + } + + @Test + public void testSendEachForMulticast() throws FirebaseMessagingException { + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId("test"); + FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); + + BatchResponse response = messaging.sendEachForMulticast(TEST_MULTICAST_MESSAGE); + + assertEquals(2, response.getSuccessCount()); + for (int i = 0; i < 2; i++) { + assertEquals("test", response.getResponses().get(i).getMessageId()); + } + assertFalse(client.isLastDryRun); + } + + @Test + public void testSendEachForMulticastDryRun() throws FirebaseMessagingException { + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId("test"); + FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); + + BatchResponse response = messaging.sendEachForMulticast(TEST_MULTICAST_MESSAGE, true); + + assertEquals(2, response.getSuccessCount()); + for (int i = 0; i < 2; i++) { + assertEquals("test", response.getResponses().get(i).getMessageId()); + } + assertTrue(client.isLastDryRun); + } + + @Test + public void testSendEachForMulticastFailure() throws FirebaseMessagingException { + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromException(TEST_EXCEPTION); + FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); + + BatchResponse response = messaging.sendEachForMulticast(TEST_MULTICAST_MESSAGE); + + assertEquals(2, response.getFailureCount()); + assertEquals(0, response.getSuccessCount()); + for (int i = 0; i < 2; i++) { + assertEquals(TEST_EXCEPTION, response.getResponses().get(i).getException()); + } + assertFalse(client.isLastDryRun); + } + + @Test + public void testSendEachForMulticastAsync() throws Exception { + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId("test"); + FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); + + BatchResponse response = messaging.sendEachForMulticastAsync(TEST_MULTICAST_MESSAGE).get(); + + assertEquals(2, response.getSuccessCount()); + for (int i = 0; i < 2; i++) { + assertEquals("test", response.getResponses().get(i).getMessageId()); + } + assertFalse(client.isLastDryRun); + } + + @Test + public void testSendEachForMulticastAsyncDryRun() throws Exception { + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId("test"); + FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); + + BatchResponse response = messaging.sendEachForMulticastAsync( + TEST_MULTICAST_MESSAGE, true).get(); + + assertEquals(2, response.getSuccessCount()); + for (int i = 0; i < 2; i++) { + assertEquals("test", response.getResponses().get(i).getMessageId()); + } + assertTrue(client.isLastDryRun); + } + + @Test + public void testSendEachForMulticastAsyncFailure() throws Exception { + MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromException(TEST_EXCEPTION); + FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); + + BatchResponse response = messaging.sendEachForMulticastAsync(TEST_MULTICAST_MESSAGE).get(); + + assertEquals(2, response.getFailureCount()); + assertEquals(0, response.getSuccessCount()); + for (int i = 0; i < 2; i++) { + assertEquals(TEST_EXCEPTION, response.getResponses().get(i).getException()); + } + assertFalse(client.isLastDryRun); + } + @Test public void testSendAllWithNull() throws FirebaseMessagingException { MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId(null); @@ -681,6 +964,7 @@ private static class MockFirebaseMessagingClient implements FirebaseMessagingCli private Message lastMessage; private List lastBatch; private boolean isLastDryRun; + private ImmutableMap messageMap; private MockFirebaseMessagingClient( String messageId, BatchResponse batchResponse, FirebaseMessagingException exception) { @@ -689,10 +973,20 @@ private MockFirebaseMessagingClient( this.exception = exception; } + private MockFirebaseMessagingClient( + Map messageMap, FirebaseMessagingException exception) { + this.messageMap = ImmutableMap.copyOf(messageMap); + this.exception = exception; + } + static MockFirebaseMessagingClient fromMessageId(String messageId) { return new MockFirebaseMessagingClient(messageId, null, null); } + static MockFirebaseMessagingClient fromMessageMap(Map messageMap) { + return new MockFirebaseMessagingClient(messageMap, null); + } + static MockFirebaseMessagingClient fromBatchResponse(BatchResponse batchResponse) { return new MockFirebaseMessagingClient(null, batchResponse, null); } @@ -702,13 +996,23 @@ static MockFirebaseMessagingClient fromException(FirebaseMessagingException exce } @Override + @Nullable public String send(Message message, boolean dryRun) throws FirebaseMessagingException { lastMessage = message; isLastDryRun = dryRun; if (exception != null) { throw exception; } - return messageId; + if (messageMap == null) { + return messageId; + } + if (!messageMap.containsKey(message)) { + return null; + } + if (messageMap.get(message).getException() != null) { + throw messageMap.get(message).getException(); + } + return messageMap.get(message).getMessageId(); } @Override From b940966a907e3527b61bd3eeab2027d9ea7c6bfd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 11:21:40 -0400 Subject: [PATCH 117/269] chore(deps): bump maven-project-info-reports-plugin from 3.4.4 to 3.4.5 (#830) Bumps [maven-project-info-reports-plugin](https://github.com/apache/maven-project-info-reports-plugin) from 3.4.4 to 3.4.5. - [Commits](https://github.com/apache/maven-project-info-reports-plugin/compare/maven-project-info-reports-plugin-3.4.4...maven-project-info-reports-plugin-3.4.5) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-project-info-reports-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0a67f6c14..126bd3e4f 100644 --- a/pom.xml +++ b/pom.xml @@ -367,7 +367,7 @@ maven-project-info-reports-plugin - 3.4.4 + 3.4.5 From ed01081597bf8e52f439699a7e3909eba016eeb6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 15:24:35 +0000 Subject: [PATCH 118/269] chore(deps): bump maven-surefire-plugin from 3.1.0 to 3.1.2 (#828) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 126bd3e4f..d326e9e06 100644 --- a/pom.xml +++ b/pom.xml @@ -280,7 +280,7 @@ maven-surefire-plugin - 3.1.0 + 3.1.2 ${skipUTs} From 4465a9141f0e747ccf1cd5cc770213382883636b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 15:27:39 +0000 Subject: [PATCH 119/269] chore(deps): bump maven-failsafe-plugin from 3.1.0 to 3.1.2 (#829) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d326e9e06..ba056d98e 100644 --- a/pom.xml +++ b/pom.xml @@ -337,7 +337,7 @@ maven-failsafe-plugin - 3.1.0 + 3.1.2 From 0f414298eee57e56de8d49c29796f2adbd718dc6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Jun 2023 13:05:19 -0400 Subject: [PATCH 120/269] chore(deps): bump netty-handler from 4.1.93.Final to 4.1.94.Final (#833) Bumps [netty-handler](https://github.com/netty/netty) from 4.1.93.Final to 4.1.94.Final. - [Commits](https://github.com/netty/netty/compare/netty-4.1.93.Final...netty-4.1.94.Final) --- updated-dependencies: - dependency-name: io.netty:netty-handler dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ba056d98e..878d83855 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.93.Final + 4.1.94.Final From adc7cf550497188c5ea41eeeea3cafed4e98626d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Jun 2023 17:24:15 +0000 Subject: [PATCH 121/269] chore(deps): bump libraries-bom from 26.16.0 to 26.17.0 (#831) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 878d83855..3d99466d1 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.16.0 + 26.17.0 pom import From 82da78bbd1b5d4a8b9bf1be9e2905c10fda39687 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Thu, 22 Jun 2023 15:37:55 -0400 Subject: [PATCH 122/269] [chore] Release 9.2.0 (#835) - Release 9.2.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3d99466d1..ddd16db34 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 9.1.1 + 9.2.0 jar firebase-admin From 7a32102c61ade8e48649569c4c19c7b7aed01982 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Jul 2023 15:52:33 +0000 Subject: [PATCH 123/269] chore(deps): bump libraries-bom from 26.17.0 to 26.18.0 (#839) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ddd16db34..e56f31a13 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.17.0 + 26.18.0 pom import From 999742520a93cc8c232591802e640d5774ae5efd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Aug 2023 12:24:50 -0400 Subject: [PATCH 124/269] chore(deps): bump com.google.cloud:libraries-bom from 26.18.0 to 26.22.0 (#854) Bumps [com.google.cloud:libraries-bom](https://github.com/googleapis/java-cloud-bom) from 26.18.0 to 26.22.0. - [Release notes](https://github.com/googleapis/java-cloud-bom/releases) - [Changelog](https://github.com/googleapis/java-cloud-bom/blob/main/release-please-config.json) - [Commits](https://github.com/googleapis/java-cloud-bom/compare/v26.18.0...v26.22.0) --- updated-dependencies: - dependency-name: com.google.cloud:libraries-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e56f31a13..0e672f5d1 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.18.0 + 26.22.0 pom import From 13a285723aab9584f587762a6b70c38386acf527 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Aug 2023 16:28:32 +0000 Subject: [PATCH 125/269] chore(deps): bump netty.version from 4.1.94.Final to 4.1.96.Final (#847) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0e672f5d1..6e41a7757 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.94.Final + 4.1.96.Final From 315f5c0bccf07fa025fa38939400feca9950ba45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Oct 2023 16:54:59 +0000 Subject: [PATCH 126/269] chore(deps): bump com.google.cloud:libraries-bom from 26.22.0 to 26.24.0 (#864) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6e41a7757..ec70ea90c 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.22.0 + 26.24.0 pom import From a7ce4c396ba4472d812876692537d2a47efdc780 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Oct 2023 16:57:52 +0000 Subject: [PATCH 127/269] chore(deps): bump org.slf4j:slf4j-api from 2.0.7 to 2.0.9 (#858) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ec70ea90c..70763e2cc 100644 --- a/pom.xml +++ b/pom.xml @@ -438,7 +438,7 @@ org.slf4j slf4j-api - 2.0.7 + 2.0.9 io.netty From 2dcd91a1ddef8e1e4c2f56a722ec5bebec26154d Mon Sep 17 00:00:00 2001 From: Vedran Pavic Date: Wed, 4 Oct 2023 19:08:00 +0200 Subject: [PATCH 128/269] Mark `sendAll` and `sendMulticast` variants as deprecated (#836) In d1baee36 all `sendAll` and `sendMulticast` variants were deprecated, however those methods were marked as deprecated only in javadoc but not using `@Deprecated`. This commit adds `@Deprecated` annotation where needed, in order to better signal the deprecation to developers. Co-authored-by: Lahiru Maramba --- .../com/google/firebase/messaging/FirebaseMessaging.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java b/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java index 882fec8fc..e16b6ac9d 100644 --- a/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java +++ b/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java @@ -344,6 +344,7 @@ public ApiFuture sendEachForMulticastAsync( * value. * @deprecated Use {@link #sendEach(List)} instead. */ + @Deprecated public BatchResponse sendAll( @NonNull List messages) throws FirebaseMessagingException { return sendAll(messages, false); @@ -371,6 +372,7 @@ public BatchResponse sendAll( * value. * @deprecated Use {@link #sendEach(List, boolean)} instead. */ + @Deprecated public BatchResponse sendAll( @NonNull List messages, boolean dryRun) throws FirebaseMessagingException { return sendAllOp(messages, dryRun).call(); @@ -384,6 +386,7 @@ public BatchResponse sendAll( * the messages have been sent. * @deprecated Use {@link #sendEachAsync(List)} instead. */ + @Deprecated public ApiFuture sendAllAsync(@NonNull List messages) { return sendAllAsync(messages, false); } @@ -397,6 +400,7 @@ public ApiFuture sendAllAsync(@NonNull List messages) { * the messages have been sent, or when the emulation has finished. * @deprecated Use {@link #sendEachAsync(List, boolean)} instead. */ + @Deprecated public ApiFuture sendAllAsync( @NonNull List messages, boolean dryRun) { return sendAllOp(messages, dryRun).callAsync(app); @@ -418,6 +422,7 @@ public ApiFuture sendAllAsync( * return value. * @deprecated Use {@link #sendEachForMulticast(MulticastMessage)} instead. */ + @Deprecated public BatchResponse sendMulticast( @NonNull MulticastMessage message) throws FirebaseMessagingException { return sendMulticast(message, false); @@ -445,6 +450,7 @@ public BatchResponse sendMulticast( * return value. * @deprecated Use {@link #sendEachForMulticast(MulticastMessage, boolean)} instead. */ + @Deprecated public BatchResponse sendMulticast( @NonNull MulticastMessage message, boolean dryRun) throws FirebaseMessagingException { checkNotNull(message, "multicast message must not be null"); @@ -460,6 +466,7 @@ public BatchResponse sendMulticast( * the messages have been sent. * @deprecated Use {@link #sendEachForMulticastAsync(MulticastMessage)} instead. */ + @Deprecated public ApiFuture sendMulticastAsync(@NonNull MulticastMessage message) { return sendMulticastAsync(message, false); } @@ -474,6 +481,7 @@ public ApiFuture sendMulticastAsync(@NonNull MulticastMessage mes * the messages have been sent. * @deprecated Use {@link #sendEachForMulticastAsync(MulticastMessage, boolean)} instead. */ + @Deprecated public ApiFuture sendMulticastAsync( @NonNull MulticastMessage message, boolean dryRun) { checkNotNull(message, "multicast message must not be null"); From 473500022d58fb4603de9fb0e06abf98c3e9e176 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Oct 2023 17:17:06 +0000 Subject: [PATCH 129/269] chore(deps): bump netty.version from 4.1.96.Final to 4.1.99.Final (#865) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 70763e2cc..82eb1e4f9 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.96.Final + 4.1.99.Final From b4ab5903fb337b6023e331feb317f0866872c439 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Thu, 12 Oct 2023 12:48:24 -0400 Subject: [PATCH 130/269] Update `github.ref` value in `release.yml` (#867) - Fixes the release workflow to match the updates to `github.ref` - `github.ref` now returns a fully-formed value `refs/heads/...` - See https://github.blog/changelog/2023-09-13-github-actions-updates-to-github_ref-and-github-ref/ --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b49a453fc..94a42cfd5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,7 +72,7 @@ jobs: # 3. with the label 'release:publish', and # 4. the title prefix '[chore] Release '. if: github.event.pull_request.merged && - github.ref == 'master' && + github.ref == 'refs/heads/master' && contains(github.event.pull_request.labels.*.name, 'release:publish') && startsWith(github.event.pull_request.title, '[chore] Release ') From 7822976286bb7ef2b1f91ef31315671e3b14dadf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 13:37:11 -0500 Subject: [PATCH 131/269] chore(deps): bump org.apache.maven.plugins:maven-javadoc-plugin (#881) Bumps [org.apache.maven.plugins:maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.5.0 to 3.6.3. - [Release notes](https://github.com/apache/maven-javadoc-plugin/releases) - [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.5.0...maven-javadoc-plugin-3.6.3) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-javadoc-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 82eb1e4f9..a141d2400 100644 --- a/pom.xml +++ b/pom.xml @@ -89,7 +89,7 @@ maven-javadoc-plugin - 3.5.0 + 3.6.3 site @@ -301,7 +301,7 @@ maven-javadoc-plugin - 3.5.0 + 3.6.3 attach-javadocs From acc184093d3a7a588df1659e5f47d7c5d99f6c6a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 18:42:13 +0000 Subject: [PATCH 132/269] chore(deps): bump org.jacoco:jacoco-maven-plugin from 0.8.10 to 0.8.11 (#870) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a141d2400..86de9cfd1 100644 --- a/pom.xml +++ b/pom.xml @@ -189,7 +189,7 @@ org.jacoco jacoco-maven-plugin - 0.8.10 + 0.8.11 pre-unit-test From 70c4808302d34fd927981aeae40f071767cb1739 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 18:44:25 +0000 Subject: [PATCH 133/269] chore(deps): bump com.google.cloud:libraries-bom from 26.24.0 to 26.28.0 (#884) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 86de9cfd1..f8f04ba9f 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.24.0 + 26.28.0 pom import From 27fc4bf68d0c0e534c1313271f8ab9e48d2f3817 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 18:50:49 +0000 Subject: [PATCH 134/269] chore(deps): bump netty.version from 4.1.99.Final to 4.1.101.Final (#885) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f8f04ba9f..4a9191d8a 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.99.Final + 4.1.101.Final From 003b5544095de9e139f279a595520c03e057b22e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 11:43:13 -0500 Subject: [PATCH 135/269] chore(deps): bump org.apache.maven.plugins:maven-surefire-plugin (#889) Bumps [org.apache.maven.plugins:maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.1.2 to 3.2.2. - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.1.2...surefire-3.2.2) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-surefire-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4a9191d8a..b517accbe 100644 --- a/pom.xml +++ b/pom.xml @@ -280,7 +280,7 @@ maven-surefire-plugin - 3.1.2 + 3.2.2 ${skipUTs} From fe35c0fa27c33f0633741ecec8cb0580d1da2fbf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 16:46:25 +0000 Subject: [PATCH 136/269] chore(deps): bump org.apache.maven.plugins:maven-project-info-reports-plugin (#886) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b517accbe..a289cac28 100644 --- a/pom.xml +++ b/pom.xml @@ -367,7 +367,7 @@ maven-project-info-reports-plugin - 3.4.5 + 3.5.0 From a4aac69d9365189d8db93fb0ce35f285408c99fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 16:49:13 +0000 Subject: [PATCH 137/269] chore(deps): bump org.codehaus.mojo:exec-maven-plugin (#887) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a289cac28..60fd1e115 100644 --- a/pom.xml +++ b/pom.xml @@ -215,7 +215,7 @@ org.codehaus.mojo exec-maven-plugin - 3.1.0 + 3.1.1 test From 4357513d7b491eab27f8a66918e6c93ecc907e33 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 16:52:03 +0000 Subject: [PATCH 138/269] chore(deps): bump org.apache.maven.plugins:maven-failsafe-plugin (#892) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 60fd1e115..c6c42d5d7 100644 --- a/pom.xml +++ b/pom.xml @@ -337,7 +337,7 @@ maven-failsafe-plugin - 3.1.2 + 3.2.3 From 2f111fbd3d372be7bc0aab157b8781a7f80f1940 Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Tue, 19 Dec 2023 12:33:34 -0500 Subject: [PATCH 139/269] chore: Update integration test project setup instructions. (#877) * Update integration test project setup instructions. * fix: remove duplicate * fix: addresses some of the code review notes * fix: addresses remaining code review notes and some typos * Add note on 2nd rtdb reference. * fix: nits * fix: pencil * Added service account management note. --- CONTRIBUTING.md | 105 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 84 insertions(+), 21 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a3b65e101..2e6914c5f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -127,27 +127,90 @@ mvn test Integration tests are also written using Junit4. They coexist with the unit tests in the `src/test` subdirectory. Integration tests follow the naming convention `*IT.java` (e.g. `DataTestIT.java`), which enables the Maven Surefire and Failsafe plugins to differentiate between the two types of -tests. Integration tests are executed against a real life Firebase project, and therefore -requires an Internet connection. - -Create a new project in the [Firebase console](https://console.firebase.google.com/) if you do -not already have one. Use a separate, dedicated project for integration tests since the test suite -makes a large number of writes to the Firebase realtime database. Download the service account -private key from the "Settings > Service Accounts" page of the project, and save it as -`integration_cert.json` at the root of the codebase. Grant your service account the `Firebase -Authentication Admin` role at -[Google Cloud Platform Console / IAM & admin](https://console.cloud.google.com/iam-admin). This is -required to ensure that exported user records contain the password hashes of the user accounts. -Also obtain the web API key of the project from the "Settings > General" page, and save it as -`integration_apikey.txt` at the root of the codebase. - -Some of the integration tests require an -[Identity Platform](https://cloud.google.com/identity-platform/) project with multi-tenancy -[enabled](https://cloud.google.com/identity-platform/docs/multi-tenancy-quickstart#enabling_multi-tenancy). -An existing Firebase project can be upgraded to an Identity Platform project without losing any -functionality via the -[Identity Platform Marketplace Page](https://console.cloud.google.com/customer-identity). Note that -charges may be incurred for active users beyond the Identity Platform free tier. +tests. + +Integration tests are executed against a real life Firebase project. If you do not already +have one suitable for running the tests against, you can create a new project in the +[Firebase Console](https://console.firebase.google.com) following the setup guide below. +If you already have a Firebase project, you'll need to obtain credentials to communicate and +authorize access to your Firebase project: + +1. Service account certificate: This allows access to your Firebase project through a service account +which is required for all integration tests. This can be downloaded as a JSON file from the +**Settings > Service Accounts** tab of the Firebase console when you click the +**Generate new private key** button. Copy the file into the repo so it's available at +`integration_cert.json`. + > **Note:** Service accounts should be carefully managed and their keys should never be stored in publicly accessible source code or repositories. + + +2. Web API key: This allows for Auth sign-in needed for some Authentication and Tenant Management +integration tests. This is displayed in the **Settings > General** tab of the Firebase console +after enabling Authentication as described in the steps below. Copy it and save to a new text +file at `integration_apikey.txt`. + + +Set up your Firebase project as follows: + + +1. Enable Authentication: + 1. Go to the Firebase Console, and select **Authentication** from the **Build** menu. + 2. Click on **Get Started**. + 3. Select **Sign-in method > Add new provider > Email/Password** then enable both the + **Email/Password** and **Email link (passwordless sign-in)** options. + + +2. Enable Firestore: + 1. Go to the Firebase Console, and select **Firestore Database** from the **Build** menu. + 2. Click on the **Create database** button. You can choose to set up Firestore either in + the production mode or in the test mode. + + +3. Enable Realtime Database: + 1. Go to the Firebase Console, and select **Realtime Database** from the **Build** menu. + 2. Click on the **Create Database** button. You can choose to set up the Realtime Database + either in the locked mode or in the test mode. + + > **Note:** Integration tests are not run against the default Realtime Database reference and are + instead run against a database created at `https://{PROJECT_ID}.firebaseio.com`. + This second Realtime Database reference is created in the following steps. + + 3. In the **Data** tab click on the kebab menu (3 dots) and select **Create Database**. + 4. Enter your Project ID (Found in the **General** tab in **Account Settings**) as the + **Realtime Database reference**. Again, you can choose to set up the Realtime Database + either in the locked mode or in the test mode. + + +4. Enable Storage: + 1. Go to the Firebase Console, and select **Storage** from the **Build** menu. + 2. Click on the **Get started** button. You can choose to set up Cloud Storage + either in the production mode or in the test mode. + + +5. Enable the IAM API: + 1. Go to the [Google Cloud console](https://console.cloud.google.com) + and make sure your Firebase project is selected. + 2. Select **APIs & Services** from the main menu, and click the + **ENABLE APIS AND SERVICES** button. + 3. Search for and enable **Identity and Access Management (IAM) API** by Google Enterprise API. + + +6. Enable Tenant Management: + 1. Go to + [Google Cloud console | Identity Platform](https://console.cloud.google.com/customer-identity/) + and if it is not already enabled, click **Enable**. + 2. Then + [enable multi-tenancy](https://cloud.google.com/identity-platform/docs/multi-tenancy-quickstart#enabling_multi-tenancy) + for your project. + + +7. Ensure your service account has the **Firebase Authentication Admin** role. This is required +to ensure that exported user records contain the password hashes of the user accounts: + 1. Go to [Google Cloud console | IAM & admin](https://console.cloud.google.com/iam-admin). + 2. Find your service account in the list. If not added click the pencil icon to edit its + permissions. + 3. Click **ADD ANOTHER ROLE** and choose **Firebase Authentication Admin**. + 4. Click **SAVE**. + Now run the following command to invoke the integration test suite: From 5609fb45739222bd0cad25401dad8b7e4c287a64 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 16:48:50 -0500 Subject: [PATCH 140/269] chore(deps): bump com.google.cloud:libraries-bom from 26.28.0 to 26.31.0 (#907) Bumps [com.google.cloud:libraries-bom](https://github.com/googleapis/java-cloud-bom) from 26.28.0 to 26.31.0. - [Release notes](https://github.com/googleapis/java-cloud-bom/releases) - [Changelog](https://github.com/googleapis/java-cloud-bom/blob/main/release-please-config.json) - [Commits](https://github.com/googleapis/java-cloud-bom/compare/v26.28.0...v26.31.0) --- updated-dependencies: - dependency-name: com.google.cloud:libraries-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c6c42d5d7..a6804d543 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.28.0 + 26.31.0 pom import From cbf68e5642d99119996219e9ca5bc10c983230dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 21:51:35 +0000 Subject: [PATCH 141/269] chore(deps): bump org.apache.maven.plugins:maven-surefire-plugin (#903) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a6804d543..3321dd105 100644 --- a/pom.xml +++ b/pom.xml @@ -280,7 +280,7 @@ maven-surefire-plugin - 3.2.2 + 3.2.5 ${skipUTs} From 67e8435f7f5289863eb3cba8222a0160846ce7c9 Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Wed, 7 Feb 2024 09:28:41 -0500 Subject: [PATCH 142/269] [chore] Update Github action workflows to fix node version and `set-output` deprecation warnings (#905) * Update Github action workflows to fix node version and `set-output` deprecation warnings * revert upload-artifact to v1 * revert upload-artifact to v1 * fix: JDK version syntax --- .github/scripts/publish_preflight_check.sh | 13 +++++---- .github/workflows/ci.yml | 5 ++-- .github/workflows/nightly.yml | 9 +++--- .github/workflows/release.yml | 32 ++++++++++------------ 4 files changed, 29 insertions(+), 30 deletions(-) diff --git a/.github/scripts/publish_preflight_check.sh b/.github/scripts/publish_preflight_check.sh index 7a191518d..7c8e384e2 100755 --- a/.github/scripts/publish_preflight_check.sh +++ b/.github/scripts/publish_preflight_check.sh @@ -69,7 +69,7 @@ if [[ ! "${RELEASE_VERSION}" =~ ^([0-9]*)\.([0-9]*)\.([0-9]*)$ ]]; then fi echo_info "Extracted release version: ${RELEASE_VERSION}" -echo "::set-output name=version::v${RELEASE_VERSION}" +echo "version=v${RELEASE_VERSION}" >> $GITHUB_OUTPUT echo_info "" @@ -132,12 +132,13 @@ readonly CHANGELOG=`${CURRENT_DIR}/generate_changelog.sh` echo "$CHANGELOG" # Parse and preformat the text to handle multi-line output. -# See https://github.amrom.workers.devmunity/t5/GitHub-Actions/set-output-Truncates-Multiline-Strings/td-p/37870 +# See https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#example-of-a-multiline-string +# and https://github.com/github/docs/issues/21529#issue-1418590935 FILTERED_CHANGELOG=`echo "$CHANGELOG" | grep -v "\\[INFO\\]"` -FILTERED_CHANGELOG="${FILTERED_CHANGELOG//'%'/'%25'}" -FILTERED_CHANGELOG="${FILTERED_CHANGELOG//$'\n'/'%0A'}" -FILTERED_CHANGELOG="${FILTERED_CHANGELOG//$'\r'/'%0D'}" -echo "::set-output name=changelog::${FILTERED_CHANGELOG}" +FILTERED_CHANGELOG="${FILTERED_CHANGELOG//$'\''/'"'}" +echo "changelog<> $GITHUB_OUTPUT +echo -e "$FILTERED_CHANGELOG" >> $GITHUB_OUTPUT +echo "CHANGELOGEOF" >> $GITHUB_OUTPUT echo "" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb09f08c6..39948e9e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,11 +24,12 @@ jobs: java-version: [8, 11, 17] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v1 + uses: actions/setup-java@v4 with: + distribution: 'zulu' java-version: ${{ matrix.java-version }} # Does the following: diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index dd3110c46..b29cd2fc4 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -29,14 +29,15 @@ jobs: steps: - name: Checkout source for staging - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: ref: ${{ github.event.client_payload.ref || github.ref }} - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - name: Set up JDK 8 + uses: actions/setup-java@v4 with: - java-version: 1.8 + distribution: 'zulu' + java-version: 8 - name: Compile, test and package run: ./.github/scripts/package_artifacts.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 94a42cfd5..c8a7050d8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,14 +40,15 @@ jobs: # via the 'ref' client parameter. steps: - name: Checkout source for staging - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: ref: ${{ github.event.client_payload.ref || github.ref }} - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - name: Set up JDK 8 + uses: actions/setup-java@v4 with: - java-version: 1.8 + distribution: 'zulu' + java-version: 8 - name: Compile, test and package run: ./.github/scripts/package_artifacts.sh @@ -80,12 +81,13 @@ jobs: steps: - name: Checkout source for publish - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - name: Set up JDK 8 + uses: actions/setup-java@v4 with: - java-version: 1.8 + distribution: 'zulu' + java-version: 8 - name: Publish preflight check id: preflight @@ -99,19 +101,13 @@ jobs: NEXUS_OSSRH_USERNAME: ${{ secrets.NEXUS_OSSRH_USERNAME }} NEXUS_OSSRH_PASSWORD: ${{ secrets.NEXUS_OSSRH_PASSWORD }} - # We pull this action from a custom fork of a contributor until - # https://github.com/actions/create-release/pull/32 is merged. Also note that v1 of - # this action does not support the "body" parameter. + # See: https://cli.github.com/manual/gh_release_create - name: Create release tag - uses: fleskesvor/create-release@1a72e235c178bf2ae6c51a8ae36febc24568c5fe env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ steps.preflight.outputs.version }} - release_name: Firebase Admin Java SDK ${{ steps.preflight.outputs.version }} - body: ${{ steps.preflight.outputs.changelog }} - draft: false - prerelease: false + run: gh release create ${{ steps.preflight.outputs.version }} + --title "Firebase Admin Java SDK ${{ steps.preflight.outputs.version }}" + --notes '${{ steps.preflight.outputs.changelog }}' # Post to Twitter if explicitly opted-in by adding the label 'release:tweet'. - name: Post to Twitter From 7e61d864209c631fa306e575e1254c39d4e77cd5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 16:41:04 +0000 Subject: [PATCH 143/269] chore(deps): bump com.google.api-client:google-api-client-bom (#911) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3321dd105..09e73a644 100644 --- a/pom.xml +++ b/pom.xml @@ -392,7 +392,7 @@ com.google.api-client google-api-client-bom - 2.2.0 + 2.3.0 pom import From 8214d8b2341d53899f8bf0b99ff789754f89e5f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 15:57:43 -0500 Subject: [PATCH 144/269] chore(deps): bump netty.version from 4.1.101.Final to 4.1.107.Final (#913) Bumps `netty.version` from 4.1.101.Final to 4.1.107.Final. Updates `io.netty:netty-codec-http` from 4.1.101.Final to 4.1.107.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.101.Final...netty-4.1.107.Final) Updates `io.netty:netty-handler` from 4.1.101.Final to 4.1.107.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.101.Final...netty-4.1.107.Final) Updates `io.netty:netty-transport` from 4.1.101.Final to 4.1.107.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.101.Final...netty-4.1.107.Final) --- updated-dependencies: - dependency-name: io.netty:netty-codec-http dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-handler dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-transport dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 09e73a644..a3e60453b 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.101.Final + 4.1.107.Final From 4b01900164a7925612c2fcf54ab3d3c508ae5fda Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 20:59:47 +0000 Subject: [PATCH 145/269] chore(deps): bump org.slf4j:slf4j-api from 2.0.9 to 2.0.12 (#910) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a3e60453b..816fca592 100644 --- a/pom.xml +++ b/pom.xml @@ -438,7 +438,7 @@ org.slf4j slf4j-api - 2.0.9 + 2.0.12 io.netty From dbc10e85bc2b24a19e7f80c0f66a7b57357115d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 21:14:31 +0000 Subject: [PATCH 146/269] chore(deps): bump org.apache.maven.plugins:maven-compiler-plugin (#897) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 816fca592..da047fbed 100644 --- a/pom.xml +++ b/pom.xml @@ -270,7 +270,7 @@ maven-compiler-plugin - 3.11.0 + 3.12.1 1.8 1.8 From fd4595c8289aeed4c14d5c714ad5d7b6f6b794c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 15:01:50 -0500 Subject: [PATCH 147/269] chore(deps): bump org.apache.maven.plugins:maven-failsafe-plugin (#914) Bumps [org.apache.maven.plugins:maven-failsafe-plugin](https://github.com/apache/maven-surefire) from 3.2.3 to 3.2.5. - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.2.3...surefire-3.2.5) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-failsafe-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index da047fbed..131c6f3a8 100644 --- a/pom.xml +++ b/pom.xml @@ -337,7 +337,7 @@ maven-failsafe-plugin - 3.2.3 + 3.2.5 From bd6b207a664aca510a3d78ac8d729e6a557b85d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 20:03:57 +0000 Subject: [PATCH 148/269] chore(deps): bump com.google.cloud:libraries-bom from 26.31.0 to 26.32.0 (#915) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 131c6f3a8..8c6df98a5 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.31.0 + 26.32.0 pom import From 4e7c425489d0f887cd78b904bfc7b4275a7385b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Mar 2024 11:48:32 -0400 Subject: [PATCH 149/269] chore(deps): bump io.netty:netty-codec-http (#927) Bumps [io.netty:netty-codec-http](https://github.com/netty/netty) from 4.1.107.Final to 4.1.108.Final. - [Commits](https://github.com/netty/netty/compare/netty-4.1.107.Final...netty-4.1.108.Final) --- updated-dependencies: - dependency-name: io.netty:netty-codec-http dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8c6df98a5..3218d8f2f 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.107.Final + 4.1.108.Final From 12a6c3698d452215f8444de776ee711efa8d18fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:50:51 +0000 Subject: [PATCH 150/269] chore(deps): bump org.apache.maven.plugins:maven-gpg-plugin (#928) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3218d8f2f..f6a329ebf 100644 --- a/pom.xml +++ b/pom.xml @@ -157,7 +157,7 @@ maven-gpg-plugin - 3.1.0 + 3.2.2 sign-artifacts From 3c09099ee430eb36fd2280c3e853208f8e8ba1b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:53:21 +0000 Subject: [PATCH 151/269] chore(deps): bump com.google.cloud:libraries-bom from 26.32.0 to 26.34.0 (#921) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f6a329ebf..59d80a14d 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.32.0 + 26.34.0 pom import From 5cf06b0addafdad3431c745bc1d467b0b9d98407 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:55:58 +0000 Subject: [PATCH 152/269] chore(deps): bump org.codehaus.mojo:exec-maven-plugin (#918) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 59d80a14d..69392b00a 100644 --- a/pom.xml +++ b/pom.xml @@ -215,7 +215,7 @@ org.codehaus.mojo exec-maven-plugin - 3.1.1 + 3.2.0 test From 3df58ccfc88dea4afa6519284be35ee5fe6c3de5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:59:13 +0000 Subject: [PATCH 153/269] chore(deps): bump com.google.api-client:google-api-client-bom (#926) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 69392b00a..40458c83e 100644 --- a/pom.xml +++ b/pom.xml @@ -392,7 +392,7 @@ com.google.api-client google-api-client-bom - 2.3.0 + 2.4.0 pom import From 190e6afb100dfdcb99c0d37ee0c1a878fd8ddac4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Apr 2024 14:12:17 -0400 Subject: [PATCH 154/269] chore(deps): bump org.apache.maven.plugins:maven-compiler-plugin (#930) Bumps [org.apache.maven.plugins:maven-compiler-plugin](https://github.com/apache/maven-compiler-plugin) from 3.12.1 to 3.13.0. - [Release notes](https://github.com/apache/maven-compiler-plugin/releases) - [Commits](https://github.com/apache/maven-compiler-plugin/compare/maven-compiler-plugin-3.12.1...maven-compiler-plugin-3.13.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-compiler-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 40458c83e..96a64d4a5 100644 --- a/pom.xml +++ b/pom.xml @@ -270,7 +270,7 @@ maven-compiler-plugin - 3.12.1 + 3.13.0 1.8 1.8 From bb4900f8854f4606967459e9d650864b80b817b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Apr 2024 18:14:39 +0000 Subject: [PATCH 155/269] chore(deps): bump com.google.cloud:libraries-bom from 26.34.0 to 26.36.0 (#932) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 96a64d4a5..4ab9b6065 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.34.0 + 26.36.0 pom import From c044a9c55f838d4ffd7a8429ea5e5d7d830f3b3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 19:36:39 +0000 Subject: [PATCH 156/269] chore(deps): bump org.slf4j:slf4j-api from 2.0.12 to 2.0.13 (#937) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4ab9b6065..d52fd09aa 100644 --- a/pom.xml +++ b/pom.xml @@ -438,7 +438,7 @@ org.slf4j slf4j-api - 2.0.12 + 2.0.13 io.netty From de76f4e85e6ed9947cdcc98e0e428a6da0d43d52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 19:40:09 +0000 Subject: [PATCH 157/269] chore(deps): bump org.apache.maven.plugins:maven-source-plugin (#936) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d52fd09aa..09eea59e2 100644 --- a/pom.xml +++ b/pom.xml @@ -289,7 +289,7 @@ maven-source-plugin - 3.3.0 + 3.3.1 attach-sources From b5251dd20f7407d6d8f61e2b0220bb9ed7698f88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 19:44:30 +0000 Subject: [PATCH 158/269] chore(deps): bump com.google.cloud:libraries-bom from 26.36.0 to 26.37.0 (#935) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 09eea59e2..13399d553 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.36.0 + 26.37.0 pom import From 451a4e3532bb7e9f70c38dda586e5f777b856460 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 19:48:07 +0000 Subject: [PATCH 159/269] chore(deps): bump org.jacoco:jacoco-maven-plugin from 0.8.11 to 0.8.12 (#934) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 13399d553..51239b23b 100644 --- a/pom.xml +++ b/pom.xml @@ -189,7 +189,7 @@ org.jacoco jacoco-maven-plugin - 0.8.11 + 0.8.12 pre-unit-test From 06bf0b3ef471cba09aa56a27be20761560ff9aac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 May 2024 16:24:06 -0400 Subject: [PATCH 160/269] chore(deps): bump org.apache.maven.plugins:maven-gpg-plugin (#940) Bumps [org.apache.maven.plugins:maven-gpg-plugin](https://github.com/apache/maven-gpg-plugin) from 3.2.2 to 3.2.4. - [Release notes](https://github.com/apache/maven-gpg-plugin/releases) - [Commits](https://github.com/apache/maven-gpg-plugin/compare/maven-gpg-plugin-3.2.2...maven-gpg-plugin-3.2.4) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-gpg-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 51239b23b..3a59e7d01 100644 --- a/pom.xml +++ b/pom.xml @@ -157,7 +157,7 @@ maven-gpg-plugin - 3.2.2 + 3.2.4 sign-artifacts From 907344b102d749db157dcebd49278761612e1944 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 May 2024 20:26:54 +0000 Subject: [PATCH 161/269] chore(deps): bump com.google.api-client:google-api-client-bom (#939) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3a59e7d01..71f813396 100644 --- a/pom.xml +++ b/pom.xml @@ -392,7 +392,7 @@ com.google.api-client google-api-client-bom - 2.4.0 + 2.4.1 pom import From 307a28b8e8d61aeda8912e36bbeede287a110130 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 May 2024 20:29:37 +0000 Subject: [PATCH 162/269] chore(deps): bump netty.version from 4.1.108.Final to 4.1.109.Final (#938) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 71f813396..39257c4cb 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.108.Final + 4.1.109.Final From d7e0470e44a2cd3a103cf18a7ad6185bddb0c2b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 13:29:24 -0400 Subject: [PATCH 163/269] chore(deps): bump com.google.cloud:libraries-bom from 26.37.0 to 26.38.0 (#942) Bumps [com.google.cloud:libraries-bom](https://github.com/googleapis/java-cloud-bom) from 26.37.0 to 26.38.0. - [Release notes](https://github.com/googleapis/java-cloud-bom/releases) - [Changelog](https://github.com/googleapis/java-cloud-bom/blob/main/release-please-config.json) - [Commits](https://github.com/googleapis/java-cloud-bom/compare/v26.37.0...v26.38.0) --- updated-dependencies: - dependency-name: com.google.cloud:libraries-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 39257c4cb..33d89ee67 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.37.0 + 26.38.0 pom import From bad0201c4c8decd0878f7238b9d1521d0d59fc72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 May 2024 11:25:35 -0400 Subject: [PATCH 164/269] chore(deps): bump com.google.api-client:google-api-client-bom (#946) Bumps [com.google.api-client:google-api-client-bom](https://github.com/googleapis/google-api-java-client) from 2.4.1 to 2.5.0. - [Release notes](https://github.com/googleapis/google-api-java-client/releases) - [Changelog](https://github.com/googleapis/google-api-java-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/googleapis/google-api-java-client/compare/v2.4.1...v2.5.0) --- updated-dependencies: - dependency-name: com.google.api-client:google-api-client-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 33d89ee67..09782cda6 100644 --- a/pom.xml +++ b/pom.xml @@ -392,7 +392,7 @@ com.google.api-client google-api-client-bom - 2.4.1 + 2.5.0 pom import From 01dd6cc2f5d52ae17bba890bc3913338fac7b837 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 May 2024 17:02:59 +0000 Subject: [PATCH 165/269] chore(deps): bump com.google.cloud:libraries-bom from 26.38.0 to 26.39.0 (#944) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 09782cda6..c9dad6269 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.38.0 + 26.39.0 pom import From ebd86a592adb039f2f1b3f558c68ee3896f0ba7e Mon Sep 17 00:00:00 2001 From: Felix Rittler Date: Wed, 15 May 2024 21:19:43 +0200 Subject: [PATCH 166/269] fix: Make writeTimeout configurable (similar to readTimeout) (#900) * add option to configure write timeout in FirebaseOptions * improve tests and fix javadoc --------- Co-authored-by: Felix Rittler Co-authored-by: Lahiru Maramba --- .../com/google/firebase/FirebaseOptions.java | 28 +++++++++++++++++++ .../internal/FirebaseRequestInitializer.java | 3 ++ .../google/firebase/FirebaseOptionsTest.java | 12 +++++++- .../auth/FirebaseUserManagerTest.java | 2 ++ .../FirebaseRequestInitializerTest.java | 3 ++ 5 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/google/firebase/FirebaseOptions.java b/src/main/java/com/google/firebase/FirebaseOptions.java index 8e7a29ccb..849b38b8a 100644 --- a/src/main/java/com/google/firebase/FirebaseOptions.java +++ b/src/main/java/com/google/firebase/FirebaseOptions.java @@ -82,6 +82,7 @@ public GoogleCredentials get() { private final HttpTransport httpTransport; private final int connectTimeout; private final int readTimeout; + private final int writeTimeout; private final JsonFactory jsonFactory; private final ThreadManager threadManager; private final FirestoreOptions firestoreOptions; @@ -112,6 +113,8 @@ private FirebaseOptions(@NonNull final FirebaseOptions.Builder builder) { this.connectTimeout = builder.connectTimeout; checkArgument(builder.readTimeout >= 0); this.readTimeout = builder.readTimeout; + checkArgument(builder.writeTimeout >= 0); + this.writeTimeout = builder.writeTimeout; this.firestoreOptions = builder.firestoreOptions; } @@ -207,6 +210,16 @@ public int getReadTimeout() { return readTimeout; } + /** + * Returns the write timeout in milliseconds, which is applied to outgoing REST calls + * made by the SDK. + * + * @return Write timeout in milliseconds. 0 indicates an infinite timeout. + */ + public int getWriteTimeout() { + return writeTimeout; + } + @NonNull ThreadManager getThreadManager() { return threadManager; @@ -260,6 +273,7 @@ public static final class Builder { private ThreadManager threadManager; private int connectTimeout; private int readTimeout; + private int writeTimeout; /** * Constructs an empty builder. @@ -289,6 +303,7 @@ public Builder(FirebaseOptions options) { threadManager = options.threadManager; connectTimeout = options.connectTimeout; readTimeout = options.readTimeout; + writeTimeout = options.writeTimeout; firestoreOptions = options.firestoreOptions; } @@ -495,6 +510,19 @@ public Builder setReadTimeout(int readTimeout) { return this; } + /** + * Sets the write timeout for outgoing HTTP (REST) calls made by the SDK. This does not affect + * the {@link com.google.firebase.database.FirebaseDatabase} and + * {@link com.google.firebase.cloud.FirestoreClient} APIs. + * + * @param writeTimeout Write timeout in milliseconds. Must not be negative. + * @return This Builder instance is returned so subsequent calls can be chained. + */ + public Builder setWriteTimeout(int writeTimeout) { + this.writeTimeout = writeTimeout; + return this; + } + /** * Builds the {@link FirebaseOptions} instance from the previously set options. * diff --git a/src/main/java/com/google/firebase/internal/FirebaseRequestInitializer.java b/src/main/java/com/google/firebase/internal/FirebaseRequestInitializer.java index e73f93953..e12690629 100644 --- a/src/main/java/com/google/firebase/internal/FirebaseRequestInitializer.java +++ b/src/main/java/com/google/firebase/internal/FirebaseRequestInitializer.java @@ -60,16 +60,19 @@ private static class TimeoutInitializer implements HttpRequestInitializer { private final int connectTimeoutMillis; private final int readTimeoutMillis; + private final int writeTimeoutMillis; TimeoutInitializer(FirebaseOptions options) { this.connectTimeoutMillis = options.getConnectTimeout(); this.readTimeoutMillis = options.getReadTimeout(); + this.writeTimeoutMillis = options.getWriteTimeout(); } @Override public void initialize(HttpRequest request) { request.setConnectTimeout(connectTimeoutMillis); request.setReadTimeout(readTimeoutMillis); + request.setWriteTimeout(writeTimeoutMillis); } } } diff --git a/src/test/java/com/google/firebase/FirebaseOptionsTest.java b/src/test/java/com/google/firebase/FirebaseOptionsTest.java index c74215beb..0a4e9e81a 100644 --- a/src/test/java/com/google/firebase/FirebaseOptionsTest.java +++ b/src/test/java/com/google/firebase/FirebaseOptionsTest.java @@ -38,7 +38,7 @@ import org.junit.Test; -/** +/** * Tests for {@link FirebaseOptions}. */ public class FirebaseOptionsTest { @@ -87,6 +87,7 @@ public void createOptionsWithAllValuesSet() throws IOException { .setThreadManager(MOCK_THREAD_MANAGER) .setConnectTimeout(30000) .setReadTimeout(60000) + .setWriteTimeout(90000) .setFirestoreOptions(firestoreOptions) .build(); assertEquals(FIREBASE_DB_URL, firebaseOptions.getDatabaseUrl()); @@ -97,6 +98,7 @@ public void createOptionsWithAllValuesSet() throws IOException { assertSame(MOCK_THREAD_MANAGER, firebaseOptions.getThreadManager()); assertEquals(30000, firebaseOptions.getConnectTimeout()); assertEquals(60000, firebaseOptions.getReadTimeout()); + assertEquals(90000, firebaseOptions.getWriteTimeout()); assertSame(firestoreOptions, firebaseOptions.getFirestoreOptions()); GoogleCredentials credentials = firebaseOptions.getCredentials(); @@ -209,6 +211,14 @@ public void createOptionsWithInvalidReadTimeout() { .build(); } + @Test(expected = IllegalArgumentException.class) + public void createOptionsWithInvalidWriteTimeout() { + FirebaseOptions.builder() + .setCredentials(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream())) + .setWriteTimeout(-1) + .build(); + } + @Test public void testNotEquals() throws IOException { GoogleCredentials credentials = GoogleCredentials.fromStream(ServiceAccount.EDITOR.asStream()); diff --git a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java index f83c5f56f..41085d181 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java @@ -979,6 +979,7 @@ public void testTimeout() throws Exception { .setHttpTransport(transport) .setConnectTimeout(30000) .setReadTimeout(60000) + .setWriteTimeout(90000) .build()); FirebaseAuth auth = FirebaseAuth.getInstance(); FirebaseUserManager userManager = auth.getUserManager(); @@ -989,6 +990,7 @@ public void testTimeout() throws Exception { HttpRequest request = interceptor.getResponse().getRequest(); assertEquals(30000, request.getConnectTimeout()); assertEquals(60000, request.getReadTimeout()); + assertEquals(90000, request.getWriteTimeout()); } @Test diff --git a/src/test/java/com/google/firebase/internal/FirebaseRequestInitializerTest.java b/src/test/java/com/google/firebase/internal/FirebaseRequestInitializerTest.java index fde2f918b..49805d373 100644 --- a/src/test/java/com/google/firebase/internal/FirebaseRequestInitializerTest.java +++ b/src/test/java/com/google/firebase/internal/FirebaseRequestInitializerTest.java @@ -38,6 +38,7 @@ public class FirebaseRequestInitializerTest { private static final int MAX_RETRIES = 5; private static final int CONNECT_TIMEOUT_MILLIS = 30000; private static final int READ_TIMEOUT_MILLIS = 60000; + private static final int WRITE_TIMEOUT_MILLIS = 90000; @After public void tearDown() { @@ -69,6 +70,7 @@ public void testExplicitTimeouts() throws Exception { .setCredentials(new MockGoogleCredentials("token")) .setConnectTimeout(CONNECT_TIMEOUT_MILLIS) .setReadTimeout(READ_TIMEOUT_MILLIS) + .setWriteTimeout(WRITE_TIMEOUT_MILLIS) .build()); HttpRequest request = TestUtils.createRequest(); @@ -77,6 +79,7 @@ public void testExplicitTimeouts() throws Exception { assertEquals(CONNECT_TIMEOUT_MILLIS, request.getConnectTimeout()); assertEquals(READ_TIMEOUT_MILLIS, request.getReadTimeout()); + assertEquals(WRITE_TIMEOUT_MILLIS, request.getWriteTimeout()); assertEquals("Bearer token", request.getHeaders().getAuthorization()); assertEquals(HttpRequest.DEFAULT_NUMBER_OF_RETRIES, request.getNumberOfRetries()); assertNull(request.getIOExceptionHandler()); From 2988d9c83dc1dad707195d006ac132085ed3a80b Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Thu, 16 May 2024 14:29:05 +0000 Subject: [PATCH 167/269] Fix doc strings in #900 (#947) Address TW reviews in #900 --- src/main/java/com/google/firebase/FirebaseOptions.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/google/firebase/FirebaseOptions.java b/src/main/java/com/google/firebase/FirebaseOptions.java index 849b38b8a..03f1b34a4 100644 --- a/src/main/java/com/google/firebase/FirebaseOptions.java +++ b/src/main/java/com/google/firebase/FirebaseOptions.java @@ -201,8 +201,7 @@ public int getConnectTimeout() { } /** - * Returns the read timeout in milliseconds, which is applied to outgoing REST calls - * made by the SDK. + * Returns the read timeout applied to outgoing REST calls in milliseconds. * * @return Read timeout in milliseconds. 0 indicates an infinite timeout. */ @@ -211,8 +210,7 @@ public int getReadTimeout() { } /** - * Returns the write timeout in milliseconds, which is applied to outgoing REST calls - * made by the SDK. + * Returns the write timeout applied to outgoing REST calls in milliseconds. * * @return Write timeout in milliseconds. 0 indicates an infinite timeout. */ From 02eda9d093eb540f1f57a28c57547424646aebd8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 May 2024 10:27:21 -0400 Subject: [PATCH 168/269] --- (#949) updated-dependencies: - dependency-name: com.google.api-client:google-api-client-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c9dad6269..c37d3ebc2 100644 --- a/pom.xml +++ b/pom.xml @@ -392,7 +392,7 @@ com.google.api-client google-api-client-bom - 2.5.0 + 2.5.1 pom import From 037af4f4f0a37f91c435ddbbf22c72f879e5e9b9 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Tue, 21 May 2024 18:14:19 +0000 Subject: [PATCH 169/269] [chore] Release 9.3.0 (#948) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c37d3ebc2..c421c06fc 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 9.2.0 + 9.3.0 jar firebase-admin From c673c266c8a644d6f8675da8cbe1e96966d727d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 15:36:17 -0400 Subject: [PATCH 170/269] chore(deps): Bump org.apache.maven.plugins:maven-javadoc-plugin (#955) Bumps [org.apache.maven.plugins:maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.6.3 to 3.7.0. - [Release notes](https://github.com/apache/maven-javadoc-plugin/releases) - [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.6.3...maven-javadoc-plugin-3.7.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-javadoc-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index c421c06fc..bd45b4dd3 100644 --- a/pom.xml +++ b/pom.xml @@ -89,7 +89,7 @@ maven-javadoc-plugin - 3.6.3 + 3.7.0 site @@ -301,7 +301,7 @@ maven-javadoc-plugin - 3.6.3 + 3.7.0 attach-javadocs From 312434a8a85e602ba3e39d0cce596a23f3fa7776 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 19:38:27 +0000 Subject: [PATCH 171/269] chore(deps): Bump org.codehaus.mojo:exec-maven-plugin (#954) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bd45b4dd3..30b4df7ed 100644 --- a/pom.xml +++ b/pom.xml @@ -215,7 +215,7 @@ org.codehaus.mojo exec-maven-plugin - 3.2.0 + 3.3.0 test From e728ce2d1532053b94858c886b48f6626fc8d5dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 19:40:44 +0000 Subject: [PATCH 172/269] chore(deps): Bump org.sonatype.plugins:nexus-staging-maven-plugin (#952) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 30b4df7ed..7f7bd423e 100644 --- a/pom.xml +++ b/pom.xml @@ -352,7 +352,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.13 + 1.7.0 true ossrh From 9806e3549ef2eff94801ee5ace294f591d1d6bbb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 19:44:13 +0000 Subject: [PATCH 173/269] chore(deps): Bump netty.version from 4.1.109.Final to 4.1.110.Final (#953) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7f7bd423e..128632f95 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.109.Final + 4.1.110.Final From 9ce559bb548d92ea2b6367e699e7b9ea6b756948 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Wed, 3 Jul 2024 16:16:18 +0000 Subject: [PATCH 174/269] chore: Update integration test resources (#966) * chore: Update integration test resources * Trigger CI --- .../resources/integ-service-account.json.gpg | Bin 1734 -> 1762 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.github/resources/integ-service-account.json.gpg b/.github/resources/integ-service-account.json.gpg index da547f44dff331d7c4b1e454ca1a230aef03622c..7740dccd8bdada2eecc181f75c552c00e912e5c2 100644 GIT binary patch literal 1762 zcmV<81|9i~4Fm}T0)n*iur~xoxnP%@af)-$RhZ&7)3w(?>+$-b1cw>C)uo%^A>P*=<*0iel03 za}Fsg1`ruYzno_!>aY;)OsTkc%h&&pe{$nZw5?@UjcH^%VjHu)VgwEnKQUK4HJk0= zyZ5gIrQ>xo>l)_!-wlcQad@p8gshGgv1xcg_Yjj!nW#U|n*LMBm3Wlbk?i_CZ5Ume zal+1184AaJ?*V#JRCa=MTz1K6B-nEb*&W)}8_{YN|7UTh+U!ds%WR1v?9Hbxk@ z+EogP!glp>+*yB=n_Y5O!Q;p67Vmj%K0Z&IQHoSQT$5HK`B~z1yiX&eY#WwU=~;7S z*R}F3&c9V&rDM=B?(>!Z9}?HjEfYzeD!pR<0+x@F>TU9zk7}`hv#*;%+wZyrU0HEofh5VjbOiejUW3pY z12D!+Wf#4>(VKPmD}rZ71T zja(Co^VJ`de?Q=DM7vMvigtGHs?54vGS7;%Hh%Kgpcm|%^7?KEi%S69Cr3HH;P{#2 zQKW(H3{(*{l=~Bf7Rl-tT)anXQbTFgdpURPL%zy_*wvVIb#5uC(O*IiG}XiMGUA%y z{hDOjK*5$4CjH6|s5_9ya=cleH7`i?@E_&%aD!Azwfl->_ z*G%8=hHUxvc;-w-8SZhmd?W7x5Q>Gdn&TWCqCer@&YB71xG=dZ z)$Q#4CnyByk9+}lQNhr0X%Yl^Z@x^E{?CS9l0qFDy5|%MRx3RMp&FRUbXi#z!svt^ z$1+LBRDKak{r8lR!uG7M$3y`i=mM%I6C;mOE*azp=yC&EjEO@hZAV~%vb<>vw*Tx}<@1-F%E1MJ z&Kx%>OC=ss;A2c#Xs*e!O^%Y!Dj=VPAc>{~z^9u8{@H%2A-4|8w>siL6sFHXXGAGp z6u#;-ci&~<6YWll-R0BZ(9=FJ*d=Z^1zpP&loXySLaK2LILSvW!Dc?^#B&6$r*#Z0VQL=xlo$_b_5MO*>kW6!}M4b=rO4;kC? z7>1#k9x6oenqHJ=>|?M)^;{lP0reAV^DsBc!yt{hJ=)44ze!{NMcolK=)E*z3xPIFQ}^9xIoZ8E2fWR@#G|3UOCwegz@y^#$8?WWnJ zs2>$dD-U!>e@QG8RfQO0o(=0E;V|(Wz7kVeam0oZT7r|6xiFJ zNd8hqdzqJ0-`fL-a@okskU*`zWMi=%n1PPxq!&m!cL|J#l~QZ}A3JebK?T zy$sIEE!|IkVGk9;6HzHA)9Y-IXTCA)$QGOVqdT1OhM%`yYT53qvYtc=4KjLTfqCH9 zgC*H0Jk^%P@UH!391-ApSu$Z{4D#3;W{vq*kO zsL+qpghA`Jm|4{gJrTOb%(>>3vR5{Am*n3SBI9H-+h-WW$}kgTg9MyYUUi*K+#nml Ero6&wRR910 literal 1734 zcmV;%208hR4Fm}T0_WqJMLyD96#vrd0kSseMv-_5+A>|dXkh)Lxg1WlZ_;HhFTh#x zDXB$vd955)y_i6fJ)Oj)aBCs+sb^rD8ckG^9BEnrFn(oz#yfzS@$+cn3lx!+B3v>8 z+oZYA$+d_~HR_?8Y|47V&47`mfrj&cS28OWOy@;W6RABVdPnTBDiQppT7W_J!vZ~k zJU+n4wlc$u!0!vj)k)conSKAdiLj5M4~3gTnMvFiA3OAaxG9$-Fi4Hz8zrK}UW+wP zy2KkBMs*nJt{;S3op3>^&flbtsElr~I!hsD^U2aEDCp+ScE}*!0)iJIYxI~0rz0!! z$*UJ{b*NbH9yB>mQVu(OxgZzVjskCoA1LLO)@Ob*I`%xmx3pY(UfU8<8kU$joj5L4 zATtgXL$nQ}etB%PN4yHDL~%HN5EDvD5vvXO-mjxu`Jw^zi}3M^AJM(=3}3DOf8t`1 zj@}P~D3y#8&Z~?x@1we($j}oT5e)y|907FiLk@uC)}}$TRV@Z^%|3?~AHf{N=JdS& zmAzu?{0*volC=e78`s^b_7LW?R+;1;+(&V(( zT*de9#KZNX^&6OE$fSpA*AbRQ@qfp9pz}Jz&>w6bXOw_#xf2&v-L&Phl|oHWm@D3} zOys2V$RMQlp$0{v*s34|sV=iP;x;zP=5RpwWJqnMP&QI7t)^63FDPCOen|MjFf6Fy zbH8(4!{wkLz8MP^38`HGosmm`E%%?rxE~2g)rNC_O(^3S*C5=3vYzG?3+y!oJ{s{1 zzUNA>f{FX&_8Zr~`3Aw+iI_(l<`oeGJGikkiq2sFOWR;=B!0NQRSX2%#^zb?1Vrn0 zUAk&@PC~v?RWOgIT?*LH&uK#GKtFXuFkP#zy6ByVhMXCsN=h$QNcx6Fab!8QW6Ur& zu}^KvHWAtV9#3GkF0lWTE~;FldqZH>&o2Z%KX~iA^p+fyiGvth=T-)<@CAc* z?GOwo%qlyt!74iU48M9XWmg)7k`fK)b}RNsiVo+UFE1g{y{tK^E0QF7xiW`>{iE_L z2e)`aCjrF4vaJ(AvkPqelNNjHn&uE@P7P07O+*CceK`8`Ej^O>;!(i7HaCaidZ~kj z$!=OMn@HL1CK6=ew?$zQygRGVm+>k2DR7{CKke27evSnGj z1DV~gv?jynB1yH$8kV3HDk^T{2PYCrSXN1{hh09 z;l&(GhZ7|l=@6E5%7=&;~1HUa3Edum39CeKllCDf5wr&+x@&+?!$Me2rKSZQ|DnUwYV~xd6kbA z^fr&FN-_FU?HQ=YD~>e6BR*{U((QLQmN58HK)d%N*5aFTL7;s!iBET0tq(c}0dVe8m%YTz{Xp6^}&9rC_ zFW>egRI>>@J|qb&;qSrsBP~vN2r{|}xny7Ft7$IqnF5kN{Ynzk**xBl6FNa!ASKZr zjkC^DnaK9M?q75@{PckJ6UMReK^Nr?iNe=Y6Rz(Imio@B{ax^h!#Hx-I^eU|&f@Sk zeh`^4eBkBo*Y~t$e?2{Lb0)LXxo?T=3jk#6n!+oCl3nSeNpa@uI5OjP zK#5K-d&uK-Mex4OqRTzG{P7!FFn+a From db472394f73275e7bb60faa091c4360181a022ac Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Tue, 6 Aug 2024 23:40:21 +0000 Subject: [PATCH 175/269] chore: Update GHA resources (#970) --- .github/resources/firebase.asc.gpg | Bin 5213 -> 4078 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.github/resources/firebase.asc.gpg b/.github/resources/firebase.asc.gpg index dae950b79907fe1755832d9d37ad34cef5e2e284..feb690edb55d475e520c91829b1bddf74e5210a3 100644 GIT binary patch literal 4078 zcmV@KM2ENfd` znc#W*a3Ooqd*(W{g1h_a8@BIbn7Nt>^*8M7f?&enK2bc&q|G~nl#e|f<2iTRItr2~ z<8LWj4`IS0eBc)Y)1(D8Qnh*R5a*?g zt;VUB`_4$)G30ADeXbW9j_^MOT>5b6LVbl;EQ6{8%}k`3TzszD*fk!l?!-5&Twqgm ziC_HBh84?sC2A)O@^nnt7X)m6H-#dVQm$bp#vd~TNg@pm;`L=y4cb^-cIFte9V3e?Y2`s|I&Xw-=Xhff??~-R>{K%)84+;-8g|Ju0*l&04TLb4b-vPvL8lrFk~VW zs>D<1-tYm>mI9$KEo#j_i9{%EOqaq^H6i(>GU#}5aAEh`B8A*_F%EbEbVld04aA4g z6}J6Ek+CfOJc!6N;d;^6(~Q~-jIsN>3#ER;5E{fK9v}qQqf- zoJ!LThr^Y{843w;o|SUab2q8Hbi9WC6_1)$xJW4Mhq%YOwSW4=6q9ASm=7VAzhcr3 z1w)OTjuA;1PaWv`FdrOZ^Hs-28s_c`@ua&Ah$ zi${nW!Vk$Z7x<4l(!HbY6)KyS|gW`KGoitXa8m%#-k>|5@Y8mkoC{c8~TKdIRL7r(AdMf_+~wYXRQxht!( zD{D zL{JkyB6SRl*~G5Ty$PDu6TG7?hqbUDx3$d4=#98gb&7iabITcOFE=}Ss#bill^)Tj z+gR2=zxAs9GAh)Ql~}yg+YX=={Wil5%>gic6!6^bRzp#?K9nZWDI$JE(>Lz$YQZb; z^v`lltn%;Sj5-aNQG;43ujMC&T2;yThD&)`FiiQpV}|2(>GNXh2d*Cx1t#F$h=2pSCGfKlunKi$YK57L56Dtr=fRRtj-qI2kNpi}2Q^ zB154`675|)zdKJy;EBb+_Jo$RnU3UjD^3;H=Ethqt$Y6s8kpNW>j@>XT#InnAQ#Tv zq{3?io7fp+&A;QxCS}u~JcS*SZJt8Dmf#lj3s{rUBnkE?RwO5YuyG{{)3;Ij? zFHspS+eq|NtP3W8jn?`e`&!|h@uZ6m?D>6?9aYg0R+(~i0SjaI5?+{kOZKNHs8-*X z*@;2nopIv7&asv{fQEG!01RN5w!HI@g;u=C{_hsZ!Cldo%6Tl>LNkfbCu;!ozTH4L zXyu~Ea2~aULuul^v^0j*&VQ>0ApoG+Ocuu#P@&2NHKYdAP5SEomQ)(H7+x_2VRU8+ zjtU`Zra4$+*g$n@{8M$Mf@gk_545HMU-o`~-JF6SiL02m zd)2{k)qETfW-|8%feRGm6eo+(qV?=e5sqXkqnS}W+8>bmM0I#dp!PbK#rXX;r!Fop z?osyTL~1kCR1A<L_FImJFXKI zD}Izrm6!-7v8V*|x2dob34a`$X$du_B@WeeA&$47OfPB{*R*avqx9|adC~JVrbbcX zKQ<~HEIAAGd8AS4op4DO40s4*}tFidP8!l+~>c%#?t$br$ zlUw(xg?Vyq>|i{tc7|i%s8PXDeq}dJ+X^Hs+fCg-0Sq25_!!3{H5vn3Y~c04T@-Wm z{2;spuy=7kYxC&EL~IW0C$y&ViMQM7Hib|o@&=kyVDcjs72ZW9u#vR8jglzn!Tr zUVdnmKBskyd1hDdMbOWcZQ1;7+{xJD_&6c*&nZ9#m2!GX<(-^`<-p5gc`n35Trm=R z0qXbS;F4TWjruUJc`>la!PE0h=05=4=zY+lxer^l)aPkzF+cYyOo!(Zd+-s^=5?kJ zbmUI|xUdW&`s$(lVW7qSs*YT_X5Xgl2KzN)ijnrEIHPvO`@;>hYGRF>X6V(71P9eh#z8xW&N6 z&D_j?e_LysY$B}2!}8{m2j%>aC}mK{^80`_P-I<>gu{y&JlrG?SQI3d=_H_#9l`yk zSr6;J9t#BIcWMC|o{9b(=D*O+kkg-%DbT|s+(<1ldE+LsMW8q>)k8&BpANs1gdeV5 zvd;hi+(&s`BQ?iu$n{MkP?_$1?QsT~`mpl^u^$sOe5MoctjNY6MYsqIYLQeR%PG+u zzZ<_}b%Tb>WP&oC@H<+SL-&_}^x>A{%gGsdWHE8vZW6O+J69RRC97bPiuO{_1av>+ zxy8JkX!S%-8x^?9T0VE8u9vDo4b{h&%0pVHF(+6I1g2AdU|uB{Vhry}j0S3#s>mo2 zbh>&S!<2$uiD*{;aHw<}=|%Ht3vi+v0521EGkJhw>ow?sIt3jPO86zHt$p~WJbt10 ziM#lf{DnH$!5SAfC?0amV>nBO;zgE@(xA7ZPKbDB`7F+wL(Nj|ubUx8f<|MV^!*l< zZ465}A%c3VXdFyL1`DCwHUl6djAmD~0_D_Y8Li!ZD&C7!TlE22?>X|Z>9V?dz;q%L z4jwEMo_GHOrVAex0Dool7CwI2rSP$-K6m->U z5$TGTK0S2_9Rq6q^_t`poDSrkUzMvvpv|@|7uX!C7W)k{^k?Rd8TPl}oXf>?RB=MV zC-bGECQ_xMxCEyxTsNWypXXwvmQbWWMC?e8x9J<^hxK>%B@kSBjOEPsPSx2s$&jzw zUmAwjBVECS8p`cl&~ch1-kD+;A+-N8r2T{t%oXns?OaCP{4a^RlyKB~XR_yGXST0r z+q^%bJS0W)C<6XAo{P3XtS_OnvUiOya6#tOEg6Ok^uP?r5~%VjI{WcJ)c-bqJ1M`4VkU*Z&S=hs13i$!t8MK zxhR1Or*Dc+w1Mxk6A$X$zk&--%wXXj4ZvTQ<+qF{a~Az9#-IRyw+bFeuMOqqyzNb9e0jgD9K%dSDj*zb?{vRYnL|$) zSF?pTBkqMIYho$kKK!mAN&nEI_gTf7hO|cEnq>#KsO?p%t3X{D((akW-ghN2JhtMQ z3R-6vnBTd&=Ax8h-S(cCrqVb?)kkZ_Nl?l6JyEW zDuxY(4xmK}UEs=<^4pp29v`<&_Q4 zvr9p-TjdRBaUj(A=EV3D7%WS$HU7Wl?$pAz4J{n7oqET#Wj!ad$s)GxAm6lwu&m@jqXF2t7fY&)M0JbQAeBLHp=odW_)Iz3(<{dUbrYik5#>A^QO zj~xc02=e9?)Z~_2t?!_k`(ZmsX+Q6fmE}U@afYv(ORqUl06go~;U`0Kz+w$1Q(;!` zXh^_0zX&PO9lXa}GeDj>x|eC`K`&7H=qWI!sxLC5j!_5c6? literal 5213 zcmV-j6r$^l4Fm}T0y~cD1mMJ_9{NSJ|6_^aOlzDF;9a=bJ=4>+~FpqQRBCwHSU;vuJq+^UYpVdggTmZa@nZ zh%<#5tD=T>i3_X%SbV<2CTS7D4c#%aWv)d#b*tw~8+%O{skDSn|13iD2v)_1xzdfx zd|N-H=t9%Fl}2giyw5{Ko-Yi%h2^JwFp>_YEU}vRh3*8E<>KjRhm_>N0-Et3d9jru zcglr?n~c_YSsz6F^M5CZ6c^N|c={`W@y04~XVu+29IoBZf0&sBDqggsVeMcuCFjyRczh;{iahd*t}*fNh7Y&8{hAouJ*d`L zLWIR{NJw-U5mx&0MsxLXLFTW`mXC%Yb($@IP4aQam*rc}o$jj){;`BWb%*GXoGrmbShPt=lzBr%B!Z8QZ~0WaMV}5 z)Ae`!7C-WsW+n~8cqYdF>dEhUtr6di!U zg7DWt`tl}u%-FV28=Ut>XZJ|DyQ!&3#$7xT*App?cawN9el^octlJhGCwb657Kg`# z%Dda_0-j;vIiqfx{j?n5`RLiMNYN-huKS2B$jqTJTzH>of45!+F1`Kces;fvdQgcM z0*YsDI+0s}Q?Ww*c?U7qrYZd&b{{Mj=j>`J$x9@Eoz_Dn=KSALFFqi75s|JdIy^fx7vxN&M2He#y3zgZvkp@I1BuSMQIV5Pyq z+pH4*kP2>ps;;66BZUFe5puUt5>AjvdI;hy&~{^b>m2j0Pm}=15s7u}qWeSvAN_xO zFTK-J=GM4upv;@lMs%7e72iDQiDFEMJaNYnUZ=T^^WsxFu!l_UD5lOU;tXtlzL+(~HMS;Gx2aLeKTdj%Gx;V*D=(3db+ zwWSCh?d(vsBKxj?e1qQt>2JWs;I@5A7#gCKoLx>9V=&A!ZS;~}+&z!IkJaVGAQ%Qg zb^L8PX7bvMx(&hjf1eQwM?yS+@&p4Q0_xsFo&OJ>4I1d;LIF8NcJVMdB~U{Ffw^MQXx z&9vf}0n)!`L>n9ML{yOYR{F|iiLdsROq1h+1{}PLJa2>Q-8#}eEbO_$OQfNs$;x3~ zR02wC;HAo3NmHasI7207%XyLYV&Z72U|2(MEwO0Uh&eNGr8%v&p`X%1DaYxgl}$rz z@v7eUU1St}cN9s_o%N@VgGGt57^^1vtF|0eoNSDdwTzP?)?68|xeP%QqW=l1!|9^uwEf|YAhU4T{bn8+~W$5>maxkg4Hd6%FsoN7c zO~I^o*L2nTZR&zNl07Oi)y{oN^DtcvK(SOI*Y0>Ybuar6xrG(XK^dfK`&C4MJ-e=K zZnKxHf%qwe;50O8;3k)4bdoBjyTAqs(!tnp$Buf68tvWUlz7z$6xk$i)kD zX=0YAOOh$u#R6V$%F?*l&$EHHVA{gVEh(NNv|bKo^$yxT4}#pL0Blp9spkEst&Z!g zqbMUyFXg+eZjeT+^RTaV&Jm)hd21Y%AaW3^Lqblg)2sER6*#gei94~!<(M{b_3{HF zwd3`)aIxZMI3J&c#~0b?w01xqb{S)|QK83#L%(IKo!QdckrvC!zIHb6tu1CQIt%XL ziK;-F57I`VaR3d{o^SW0z`Tlg6r|7(u?SW;R-=R$T0cuCo_SIyidv+e-Z zlm<`xPuT1mGQbfoUQC!VzP^{#%8U?tCE?zvh{b+;_VNmb%-x*u{R5Qtc9U?x7fC|;Hfrw7oO#%MOJ--mnj1<&fOU)~#i76#MW=V27AZO-$K(LeD zETl~4j=1ql&j~rVzN$L)%@D+U1M2)Dj{*{6)I}&UPGq0OE^K zpXR(!Z|F2*D(P&67eq-9X5&(LiN1Y}@htX#z}jOO8|N9rLrve76IAB00?FUVz0e(Y zx;0bf^@>O=lvjkR#9q*1!B%*NO9^PH$S)rn)%%g+1nKM?F!pRRxBRQ0VeCplGS7_| zsh|S;`1XAL_9f*Sss)a)YzWY^Z_o(#^%mc2^A2fbC~Vtls4maI_>Dr{FFRZ0&Pqq) z`wdZhsh<38I16GIm{0ZTEr__!fP+3G{$(2JR{mhg@V4G?kX>M zQ~JTq;1ey%npIir>qza~5oHsK(oV>Ht+t}HZ0@=_D?KHEST}Je-cZ-AI~>5&{t21a zn42zwbYG7}h>#bw608CQV-Q-v1!o~0b|hj9_R@_qhLF%pA$G8kc^ zC_tXksu8YLCz4CM;(D3%-ryzJ7)na?G`Ml(Q`3Ns`!Yvg9dUfe-LRSy9?uho8Y2r# zM+W8ftj=T-A6`83-Xog@nS1Wz#5}I^TmQ1c&F4zGWb80K5TFXTsEZ<?|9v;S*RR1OTUf9YJcxZ zCVh$uA#0s=$pB?Dz3<|t4T;3!kdqw9;y%G-dIkz~W+??tSn046hCZtL!2l`r%7nu~X-p-g zktkXLj~Qu04H$CKb!C);=}pCVe>6jf&Xof@Or$Z7(Kf?UYgVrljDij~n!5pdrAX*h zmag^pefSf4E_boQ1JV~T-BZt6QcNhOMdy7;2tEX@P@j19q&SzqEZQC|Eug08?UUjl zSLZzcX*LTzr(@20JJNi%7)Pt+HFLev{Jc;GkdI0RgeFOpQ41(4`R$v0~7Iy_KR%wT#&H(nTV`|6XCIt6*=d_M&7iVrtPCYl6I$ z9Xs6+BtcYY)vh;lIxle`)@Phfw@xRvS#Z?(0~b)V5@nYemY-UWvYxmX?XD_-zm#Q* zq~Aup`}dRl?Jndj|E|r_2QH(1@9GvUh=&agz{ z`BFGi6qXUV=jAw_E2ioD$`41a<7fJ*4Im*iZLve}sTPEq+^Hidp!awyGOS~tL>8rV zwiPNSKve@m-*Fd7I(HOE6r!Dk=Cyr#q*X;A+%W9`_0AI~oE=fOUGk%~%+@)L*A^z) zD_$-y@5AMaYx_AH#6VQY?C98DY4_$FWfuaA7J@a6DnsBZ*J=D%x-Rtx+|gQAUx0|5 z9tVWKXZ>u-{_&b6w7vKIK~p>czIL@JkKw6Z6o}66Xw~IAKx7{_B$4nMFB51}N~-$3 zs`8z!S_12b>iz?Plj|e~1LinXR|$vjG-G-CIvJql&leGNU09xUm?v3@St*^kOJM6a z(4<3o_%=)S0@n@~wkwOE9j5+x(UQT$6p6WlLnC>19D6LpujoCJ|0sY>nK|e3hb+>A zExOpMWTv00f7|i*)loysK-wxGfEB>ooR(4ojRgc=yGev{W0vNW`fAwDh%rsXv>HTR zWOCt;b;`sZcvh+#WB@ue2$3I*uAfZh;DaSOkR zX+V4KL+PwD^t6m5} z$`%AFpYhO1>Ta-;>Sd6lxwR96;*2EyJH7!1vVA`N(a7?{cNKBSXj>E>=gU6G(xPI#O>NY`-DvZK@}Q`%7k7 zTJ28;wOmL6mfQKFM(M%0O>wr;kI?ZjI+i`b6V?w;MBd00#Tp1YEL|TUqdOf=wwzcG zUd6{L_b&v{yZTZn59HISeoLE4h|1KN!HYI!(Kdp!v0%XIH3ve-gKX)Y^#D4!uk*KE zxY8Q2VoNnL6^^z;ADGg|()E-#?PFSI{ATHo(^BZ9T<~|F%WZWr&|JVm7~?I?_zG1 z20U2JtZuP{KO?}g(Wtp4GrUVNBAZ_KA<(*`0ZFUxIEwkfsSxO0jFYqR$ar+WvNh+3 z#?`SS1=E4Y@Lc`3`^jkg*>%cv`&7%o+f3HpZXIqZnE3{)6;!mFr4`Ww`{2Rc?QxFb zYVp_h)YqFpcd;0iAHE=ex$EYy0eUIpc1X(5wGi17HWTpM$~gm#k-y*2jdSyJSl`de$Jd^wjLMoL$Ax@B5lGf> z4FxvLE9j7m%wTH=^1STMjE`t6XSeW%0?}voD8f2ePtYkM(d1JeS~V~<6jfqvzeieJJ3oPP|?Ss_L#q1s z!upQ5m~;!NlZ3WqJ&nAA9iz5q6o<$yy}nO;WU%go8bT@5`if&rsX%C) XrIgL-1=1;}YjbYhxg363z=nJD3 Date: Tue, 6 Aug 2024 21:21:50 -0400 Subject: [PATCH 176/269] Migrate from the deprecated Charsets constants (in Guava) to the StandardCharsets constants (in the JDK). (#971) --- .../com/google/firebase/database/utilities/Utilities.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/google/firebase/database/utilities/Utilities.java b/src/main/java/com/google/firebase/database/utilities/Utilities.java index 5a3c64822..6181071ec 100644 --- a/src/main/java/com/google/firebase/database/utilities/Utilities.java +++ b/src/main/java/com/google/firebase/database/utilities/Utilities.java @@ -19,7 +19,6 @@ import com.google.api.core.ApiFuture; import com.google.api.core.SettableApiFuture; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Charsets; import com.google.common.base.Strings; import com.google.common.io.BaseEncoding; import com.google.common.net.UrlEscapers; @@ -32,6 +31,7 @@ import java.net.URI; import java.net.URLDecoder; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -108,8 +108,8 @@ static Map getQueryParamsMap(String queryString) for (String paramPair : paramPairs) { String[] pairParts = paramPair.split("="); // both the first and second part will be encoded now, we must decode them - String decodedKey = URLDecoder.decode(pairParts[0], Charsets.UTF_8.name()); - String decodedValue = URLDecoder.decode(pairParts[1], Charsets.UTF_8.name()); + String decodedKey = URLDecoder.decode(pairParts[0], StandardCharsets.UTF_8.name()); + String decodedValue = URLDecoder.decode(pairParts[1], StandardCharsets.UTF_8.name()); String runningValue = paramsMap.get(decodedKey); if (Strings.isNullOrEmpty(runningValue)) { runningValue = decodedValue; From 90b0cd60f92fa35776223f21e1ca6982a7739c4f Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Tue, 20 Aug 2024 09:50:42 -0400 Subject: [PATCH 177/269] chore: Remove tests for deprecated FCM apis (#978) * chore: Remove tests for deprecated FCM apis * Trigger Integration Tests * Remove remaining tests for deprecated FCM apis --- .../FirebaseMessagingClientImplTest.java | 260 ----------------- .../messaging/FirebaseMessagingIT.java | 97 ------- .../messaging/FirebaseMessagingTest.java | 272 ------------------ 3 files changed, 629 deletions(-) diff --git a/src/test/java/com/google/firebase/messaging/FirebaseMessagingClientImplTest.java b/src/test/java/com/google/firebase/messaging/FirebaseMessagingClientImplTest.java index 17848cc65..3180aea01 100644 --- a/src/test/java/com/google/firebase/messaging/FirebaseMessagingClientImplTest.java +++ b/src/test/java/com/google/firebase/messaging/FirebaseMessagingClientImplTest.java @@ -17,7 +17,6 @@ package com.google.firebase.messaging; 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.assertSame; @@ -29,7 +28,6 @@ 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.HttpRequestInitializer; import com.google.api.client.http.HttpResponseException; import com.google.api.client.http.HttpResponseInterceptor; import com.google.api.client.http.HttpTransport; @@ -73,16 +71,9 @@ public class FirebaseMessagingClientImplTest { private static final String MOCK_RESPONSE = "{\"name\": \"mock-name\"}"; - private static final String MOCK_BATCH_SUCCESS_RESPONSE = TestUtils.loadResource( - "fcm_batch_success.txt"); - - private static final String MOCK_BATCH_FAILURE_RESPONSE = TestUtils.loadResource( - "fcm_batch_failure.txt"); - private static final Message EMPTY_MESSAGE = Message.builder() .setTopic("test-topic") .build(); - private static final List MESSAGE_LIST = ImmutableList.of(EMPTY_MESSAGE, EMPTY_MESSAGE); private static final boolean DRY_RUN_ENABLED = true; private static final boolean DRY_RUN_DISABLED = false; @@ -320,187 +311,6 @@ public void testSendErrorWithDetailsAndNoCode() { } } - @Test - public void testSendAll() throws Exception { - final TestResponseInterceptor interceptor = new TestResponseInterceptor(); - FirebaseMessagingClient client = initMessagingClientForBatchRequests( - MOCK_BATCH_SUCCESS_RESPONSE, interceptor); - - BatchResponse responses = client.sendAll(MESSAGE_LIST, false); - - assertBatchResponse(responses, interceptor, 2, 0); - } - - @Test - public void testSendAllDryRun() throws Exception { - final TestResponseInterceptor interceptor = new TestResponseInterceptor(); - FirebaseMessagingClient client = initMessagingClientForBatchRequests( - MOCK_BATCH_SUCCESS_RESPONSE, interceptor); - - BatchResponse responses = client.sendAll(MESSAGE_LIST, true); - - assertBatchResponse(responses, interceptor, 2, 0); - } - - @Test - public void testRequestInitializerAppliedToBatchRequests() throws Exception { - TestResponseInterceptor interceptor = new TestResponseInterceptor(); - MockHttpTransport transport = new MockHttpTransport.Builder() - .setLowLevelHttpResponse(getBatchResponse(MOCK_BATCH_SUCCESS_RESPONSE)) - .build(); - HttpRequestInitializer initializer = new HttpRequestInitializer() { - @Override - public void initialize(HttpRequest httpRequest) { - httpRequest.getHeaders().set("x-custom-header", "test-value"); - } - }; - FirebaseMessagingClientImpl client = FirebaseMessagingClientImpl.builder() - .setProjectId("test-project") - .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) - .setRequestFactory(transport.createRequestFactory(initializer)) - .setChildRequestFactory(Utils.getDefaultTransport().createRequestFactory()) - .setResponseInterceptor(interceptor) - .build(); - - try { - client.sendAll(MESSAGE_LIST, DRY_RUN_DISABLED); - } finally { - HttpRequest request = interceptor.getLastRequest(); - assertEquals("test-value", request.getHeaders().get("x-custom-header")); - } - } - - @Test - public void testSendAllFailure() throws Exception { - final TestResponseInterceptor interceptor = new TestResponseInterceptor(); - FirebaseMessagingClient client = initMessagingClientForBatchRequests( - MOCK_BATCH_FAILURE_RESPONSE, interceptor); - List messages = ImmutableList.of(EMPTY_MESSAGE, EMPTY_MESSAGE, EMPTY_MESSAGE); - - BatchResponse responses = client.sendAll(messages, DRY_RUN_DISABLED); - - assertBatchResponse(responses, interceptor, 1, 2); - } - - @Test - public void testSendAllHttpError() { - for (int code : HTTP_ERRORS) { - response.setStatusCode(code).setContent("{}"); - - try { - client.sendAll(MESSAGE_LIST, DRY_RUN_DISABLED); - fail("No error thrown for HTTP error"); - } catch (FirebaseMessagingException error) { - checkExceptionFromHttpResponse(error, HTTP_2_ERROR.get(code), null, - "Unexpected HTTP response with status: " + code + "\n{}"); - } - checkBatchRequestHeader(interceptor.getLastRequest()); - } - } - - @Test - public void testSendAllTransportError() { - FirebaseMessagingClient client = initClientWithFaultyTransport(); - - try { - client.sendAll(MESSAGE_LIST, DRY_RUN_DISABLED); - fail("No error thrown for HTTP error"); - } catch (FirebaseMessagingException error) { - assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); - assertEquals( - "Unknown error while making a remote service call: transport error", error.getMessage()); - assertTrue(error.getCause() instanceof IOException); - assertNull(error.getHttpResponse()); - assertNull(error.getMessagingErrorCode()); - } - } - - @Test - public void testSendAllErrorWithEmptyResponse() { - for (int code : HTTP_ERRORS) { - response.setStatusCode(code).setZeroContent(); - - try { - client.sendAll(MESSAGE_LIST, DRY_RUN_DISABLED); - fail("No error thrown for HTTP error"); - } catch (FirebaseMessagingException error) { - checkExceptionFromHttpResponse(error, HTTP_2_ERROR.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnull"); - } - checkBatchRequestHeader(interceptor.getLastRequest()); - } - } - - @Test - public void testSendAllErrorWithDetails() { - for (int code : HTTP_ERRORS) { - response.setStatusCode(code).setContent( - "{\"error\": {\"status\": \"INVALID_ARGUMENT\", \"message\": \"test error\"}}"); - - try { - client.sendAll(MESSAGE_LIST, DRY_RUN_DISABLED); - fail("No error thrown for HTTP error"); - } catch (FirebaseMessagingException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, null); - } - checkBatchRequestHeader(interceptor.getLastRequest()); - } - } - - @Test - public void testSendAllErrorWithCanonicalCode() { - for (int code : HTTP_ERRORS) { - response.setStatusCode(code).setContent( - "{\"error\": {\"status\": \"NOT_FOUND\", \"message\": \"test error\"}}"); - - try { - client.sendAll(MESSAGE_LIST, DRY_RUN_DISABLED); - fail("No error thrown for HTTP error"); - } catch (FirebaseMessagingException error) { - checkExceptionFromHttpResponse(error, ErrorCode.NOT_FOUND, null); - } - checkBatchRequestHeader(interceptor.getLastRequest()); - } - } - - @Test - public void testSendAllErrorWithFcmError() { - for (int code : HTTP_ERRORS) { - response.setStatusCode(code).setContent( - "{\"error\": {\"status\": \"INVALID_ARGUMENT\", \"message\": \"test error\", " - + "\"details\":[{\"@type\": \"type.googleapis.com/google.firebase.fcm" - + ".v1.FcmError\", \"errorCode\": \"UNREGISTERED\"}]}}"); - - try { - client.sendAll(MESSAGE_LIST, DRY_RUN_DISABLED); - fail("No error thrown for HTTP error"); - } catch (FirebaseMessagingException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, - MessagingErrorCode.UNREGISTERED); - } - checkBatchRequestHeader(interceptor.getLastRequest()); - } - } - - @Test - public void testSendAllErrorWithoutMessage() { - final String responseBody = "{\"error\": {\"status\": \"INVALID_ARGUMENT\", " - + "\"details\":[{\"@type\": \"type.googleapis.com/google.firebase.fcm" - + ".v1.FcmError\", \"errorCode\": \"UNREGISTERED\"}]}}"; - for (int code : HTTP_ERRORS) { - response.setStatusCode(code).setContent(responseBody); - - try { - client.sendAll(MESSAGE_LIST, DRY_RUN_DISABLED); - fail("No error thrown for HTTP error"); - } catch (FirebaseMessagingException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, - MessagingErrorCode.UNREGISTERED, - "Unexpected HTTP response with status: " + code + "\n" + responseBody); - } - checkBatchRequestHeader(interceptor.getLastRequest()); - } - } @Test(expected = IllegalArgumentException.class) public void testBuilderNullProjectId() { @@ -563,18 +373,6 @@ private FirebaseMessagingClientImpl initMessagingClient( .build(); } - private FirebaseMessagingClientImpl initMessagingClientForBatchRequests( - String responsePayload, TestResponseInterceptor interceptor) { - MockLowLevelHttpResponse httpResponse = getBatchResponse(responsePayload); - return initMessagingClient(httpResponse, interceptor); - } - - private MockLowLevelHttpResponse getBatchResponse(String responsePayload) { - return new MockLowLevelHttpResponse() - .setContentType("multipart/mixed; boundary=test_boundary") - .setContent(responsePayload); - } - private FirebaseMessagingClientImpl initClientWithFaultyTransport() { HttpTransport transport = TestUtils.createFaultyHttpTransport(); return FirebaseMessagingClientImpl.builder() @@ -603,64 +401,6 @@ private void checkRequest( assertEquals(expected, parsed); } - private void assertBatchResponse( - BatchResponse batchResponse, TestResponseInterceptor interceptor, - int successCount, int failureCount) throws IOException { - - assertEquals(successCount, batchResponse.getSuccessCount()); - assertEquals(failureCount, batchResponse.getFailureCount()); - - List responses = batchResponse.getResponses(); - assertEquals(successCount + failureCount, responses.size()); - for (int i = 0; i < successCount; i++) { - SendResponse sendResponse = responses.get(i); - assertTrue(sendResponse.isSuccessful()); - assertEquals("projects/test-project/messages/" + (i + 1), sendResponse.getMessageId()); - assertNull(sendResponse.getException()); - } - - for (int i = successCount; i < failureCount; i++) { - SendResponse sendResponse = responses.get(i); - assertFalse(sendResponse.isSuccessful()); - assertNull(sendResponse.getMessageId()); - - FirebaseMessagingException exception = sendResponse.getException(); - assertNotNull(exception); - assertEquals(ErrorCode.INVALID_ARGUMENT, exception.getErrorCode()); - assertNull(exception.getCause()); - assertNull(exception.getHttpResponse()); - assertEquals(MessagingErrorCode.INVALID_ARGUMENT, exception.getMessagingErrorCode()); - } - - checkBatchRequestHeader(interceptor.getLastRequest()); - checkBatchRequest(interceptor.getLastRequest(), successCount + failureCount); - } - - private void checkBatchRequestHeader(HttpRequest request) { - assertEquals("POST", request.getRequestMethod()); - assertEquals("https://fcm.googleapis.com/batch", request.getUrl().toString()); - } - - private void checkBatchRequest(HttpRequest request, int expectedParts) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - request.getContent().writeTo(out); - String[] lines = out.toString().split("\n"); - assertEquals(expectedParts, countLinesWithPrefix(lines, "POST " + TEST_FCM_URL)); - assertEquals(expectedParts, countLinesWithPrefix(lines, "x-goog-api-format-version: 2")); - assertEquals(expectedParts, countLinesWithPrefix( - lines, "x-firebase-client: fire-admin-java/" + SdkUtils.getVersion())); - } - - private int countLinesWithPrefix(String[] lines, String prefix) { - int matchCount = 0; - for (String line : lines) { - if (line.trim().startsWith(prefix)) { - matchCount++; - } - } - return matchCount; - } - private FirebaseMessagingClientImpl.Builder fullyPopulatedBuilder() { return FirebaseMessagingClientImpl.builder() .setProjectId("test-project") diff --git a/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java b/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java index bebae6e4d..d9375781c 100644 --- a/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java +++ b/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java @@ -193,103 +193,6 @@ public void testSendEachForMulticast() throws Exception { } } - @Test - public void testSendAll() throws Exception { - List messages = new ArrayList<>(); - messages.add( - Message.builder() - .setNotification(Notification.builder() - .setTitle("Title") - .setBody("Body") - .build()) - .setTopic("foo-bar") - .build()); - messages.add( - Message.builder() - .setNotification(Notification.builder() - .setTitle("Title") - .setBody("Body") - .build()) - .setTopic("foo-bar") - .build()); - messages.add( - Message.builder() - .setNotification(Notification.builder() - .setTitle("Title") - .setBody("Body") - .build()) - .setToken("not-a-token") - .build()); - - BatchResponse response = FirebaseMessaging.getInstance().sendAll(messages, true); - - assertEquals(2, response.getSuccessCount()); - assertEquals(1, response.getFailureCount()); - - List responses = response.getResponses(); - assertEquals(3, responses.size()); - assertTrue(responses.get(0).isSuccessful()); - String id = responses.get(0).getMessageId(); - assertTrue(id != null && id.matches("^projects/.*/messages/.*$")); - - assertTrue(responses.get(1).isSuccessful()); - id = responses.get(1).getMessageId(); - assertTrue(id != null && id.matches("^projects/.*/messages/.*$")); - - assertFalse(responses.get(2).isSuccessful()); - assertNull(responses.get(2).getMessageId()); - FirebaseMessagingException exception = responses.get(2).getException(); - assertNotNull(exception); - assertEquals(ErrorCode.INVALID_ARGUMENT, exception.getErrorCode()); - } - - @Test - public void testSendFiveHundred() throws Exception { - List messages = new ArrayList<>(); - for (int i = 0; i < 500; i++) { - messages.add(Message.builder().setTopic("foo-bar-" + (i % 10)).build()); - } - - BatchResponse response = FirebaseMessaging.getInstance().sendAll(messages, true); - - assertEquals(500, response.getResponses().size()); - assertEquals(500, response.getSuccessCount()); - assertEquals(0, response.getFailureCount()); - for (SendResponse sendResponse : response.getResponses()) { - if (!sendResponse.isSuccessful()) { - sendResponse.getException().printStackTrace(); - } - assertTrue(sendResponse.isSuccessful()); - String id = sendResponse.getMessageId(); - assertTrue(id != null && id.matches("^projects/.*/messages/.*$")); - assertNull(sendResponse.getException()); - } - } - - @Test - public void testSendMulticast() throws Exception { - MulticastMessage multicastMessage = MulticastMessage.builder() - .setNotification(Notification.builder() - .setTitle("Title") - .setBody("Body") - .build()) - .addToken("not-a-token") - .addToken("also-not-a-token") - .build(); - - BatchResponse response = FirebaseMessaging.getInstance().sendMulticast( - multicastMessage, true); - - assertEquals(0, response.getSuccessCount()); - assertEquals(2, response.getFailureCount()); - assertEquals(2, response.getResponses().size()); - for (SendResponse sendResponse : response.getResponses()) { - assertFalse(sendResponse.isSuccessful()); - assertNull(sendResponse.getMessageId()); - assertNotNull(sendResponse.getException()); - } - } - @Test public void testSubscribe() throws Exception { FirebaseMessaging messaging = FirebaseMessaging.getInstance(); diff --git a/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java b/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java index dc07ce002..6d61f51de 100644 --- a/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java +++ b/src/test/java/com/google/firebase/messaging/FirebaseMessagingTest.java @@ -545,260 +545,6 @@ public void testSendEachForMulticastAsyncFailure() throws Exception { assertFalse(client.isLastDryRun); } - @Test - public void testSendAllWithNull() throws FirebaseMessagingException { - MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId(null); - FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - - try { - messaging.sendAll(null); - fail("No error thrown for null message list"); - } catch (NullPointerException expected) { - // expected - } - - assertNull(client.lastBatch); - } - - @Test - public void testSendAllWithEmptyList() throws FirebaseMessagingException { - MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId(null); - FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - - try { - messaging.sendAll(ImmutableList.of()); - fail("No error thrown for empty message list"); - } catch (IllegalArgumentException expected) { - // expected - } - - assertNull(client.lastBatch); - } - - @Test - public void testSendAllWithTooManyMessages() throws FirebaseMessagingException { - MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId(null); - FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - ImmutableList.Builder listBuilder = ImmutableList.builder(); - for (int i = 0; i < 501; i++) { - listBuilder.add(Message.builder().setTopic("topic").build()); - } - - try { - messaging.sendAll(listBuilder.build(), false); - fail("No error thrown for too many messages in the list"); - } catch (IllegalArgumentException expected) { - // expected - } - - assertNull(client.lastBatch); - } - - @Test - public void testSendAll() throws FirebaseMessagingException { - BatchResponse batchResponse = getBatchResponse("test"); - MockFirebaseMessagingClient client = MockFirebaseMessagingClient - .fromBatchResponse(batchResponse); - FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE); - - BatchResponse response = messaging.sendAll(messages); - - assertSame(batchResponse, response); - assertSame(messages, client.lastBatch); - assertFalse(client.isLastDryRun); - } - - @Test - public void testSendAllDryRun() throws FirebaseMessagingException { - BatchResponse batchResponse = getBatchResponse("test"); - MockFirebaseMessagingClient client = MockFirebaseMessagingClient - .fromBatchResponse(batchResponse); - FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE); - - BatchResponse response = messaging.sendAll(messages, true); - - assertSame(batchResponse, response); - assertSame(messages, client.lastBatch); - assertTrue(client.isLastDryRun); - } - - @Test - public void testSendAllFailure() { - MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromException(TEST_EXCEPTION); - FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE); - - try { - messaging.sendAll(messages); - } catch (FirebaseMessagingException e) { - assertSame(TEST_EXCEPTION, e); - } - - assertSame(messages, client.lastBatch); - assertFalse(client.isLastDryRun); - } - - @Test - public void testSendAllAsync() throws Exception { - BatchResponse batchResponse = getBatchResponse("test"); - MockFirebaseMessagingClient client = MockFirebaseMessagingClient - .fromBatchResponse(batchResponse); - FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE); - - BatchResponse response = messaging.sendAllAsync(messages).get(); - - assertSame(batchResponse, response); - assertSame(messages, client.lastBatch); - assertFalse(client.isLastDryRun); - } - - @Test - public void testSendAllAsyncDryRun() throws Exception { - BatchResponse batchResponse = getBatchResponse("test"); - MockFirebaseMessagingClient client = MockFirebaseMessagingClient - .fromBatchResponse(batchResponse); - FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE); - - BatchResponse response = messaging.sendAllAsync(messages, true).get(); - - assertSame(batchResponse, response); - assertSame(messages, client.lastBatch); - assertTrue(client.isLastDryRun); - } - - @Test - public void testSendAllAsyncFailure() throws InterruptedException { - MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromException(TEST_EXCEPTION); - FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - ImmutableList messages = ImmutableList.of(EMPTY_MESSAGE); - - try { - messaging.sendAllAsync(messages).get(); - } catch (ExecutionException e) { - assertSame(TEST_EXCEPTION, e.getCause()); - } - - assertSame(messages, client.lastBatch); - assertFalse(client.isLastDryRun); - } - - @Test - public void testSendMulticastWithNull() throws FirebaseMessagingException { - MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId(null); - FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - - try { - messaging.sendMulticast(null); - fail("No error thrown for null multicast message"); - } catch (NullPointerException expected) { - // expected - } - - assertNull(client.lastBatch); - } - - @Test - public void testSendMulticast() throws FirebaseMessagingException { - BatchResponse batchResponse = getBatchResponse("test"); - MockFirebaseMessagingClient client = MockFirebaseMessagingClient - .fromBatchResponse(batchResponse); - FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - - BatchResponse response = messaging.sendMulticast(TEST_MULTICAST_MESSAGE); - - assertSame(batchResponse, response); - assertEquals(2, client.lastBatch.size()); - assertEquals("test-fcm-token1", client.lastBatch.get(0).getToken()); - assertEquals("test-fcm-token2", client.lastBatch.get(1).getToken()); - assertFalse(client.isLastDryRun); - } - - @Test - public void testSendMulticastDryRun() throws FirebaseMessagingException { - BatchResponse batchResponse = getBatchResponse("test"); - MockFirebaseMessagingClient client = MockFirebaseMessagingClient - .fromBatchResponse(batchResponse); - FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - - BatchResponse response = messaging.sendMulticast(TEST_MULTICAST_MESSAGE, true); - - assertSame(batchResponse, response); - assertEquals(2, client.lastBatch.size()); - assertEquals("test-fcm-token1", client.lastBatch.get(0).getToken()); - assertEquals("test-fcm-token2", client.lastBatch.get(1).getToken()); - assertTrue(client.isLastDryRun); - } - - @Test - public void testSendMulticastFailure() { - MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromException(TEST_EXCEPTION); - FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - - try { - messaging.sendMulticast(TEST_MULTICAST_MESSAGE); - } catch (FirebaseMessagingException e) { - assertSame(TEST_EXCEPTION, e); - } - - assertEquals(2, client.lastBatch.size()); - assertEquals("test-fcm-token1", client.lastBatch.get(0).getToken()); - assertEquals("test-fcm-token2", client.lastBatch.get(1).getToken()); - assertFalse(client.isLastDryRun); - } - - @Test - public void testSendMulticastAsync() throws Exception { - BatchResponse batchResponse = getBatchResponse("test"); - MockFirebaseMessagingClient client = MockFirebaseMessagingClient - .fromBatchResponse(batchResponse); - FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - - BatchResponse response = messaging.sendMulticastAsync(TEST_MULTICAST_MESSAGE).get(); - - assertSame(batchResponse, response); - assertEquals(2, client.lastBatch.size()); - assertEquals("test-fcm-token1", client.lastBatch.get(0).getToken()); - assertEquals("test-fcm-token2", client.lastBatch.get(1).getToken()); - assertFalse(client.isLastDryRun); - } - - @Test - public void testSendMulticastAsyncDryRun() throws Exception { - BatchResponse batchResponse = getBatchResponse("test"); - MockFirebaseMessagingClient client = MockFirebaseMessagingClient - .fromBatchResponse(batchResponse); - FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - - BatchResponse response = messaging.sendMulticastAsync(TEST_MULTICAST_MESSAGE, true).get(); - - assertSame(batchResponse, response); - assertEquals(2, client.lastBatch.size()); - assertEquals("test-fcm-token1", client.lastBatch.get(0).getToken()); - assertEquals("test-fcm-token2", client.lastBatch.get(1).getToken()); - assertTrue(client.isLastDryRun); - } - - @Test - public void testSendMulticastAsyncFailure() throws InterruptedException { - MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromException(TEST_EXCEPTION); - FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client)); - - try { - messaging.sendMulticastAsync(TEST_MULTICAST_MESSAGE).get(); - } catch (ExecutionException e) { - assertSame(TEST_EXCEPTION, e.getCause()); - } - - assertEquals(2, client.lastBatch.size()); - assertEquals("test-fcm-token1", client.lastBatch.get(0).getToken()); - assertEquals("test-fcm-token2", client.lastBatch.get(1).getToken()); - assertFalse(client.isLastDryRun); - } - @Test public void testInvalidSubscribe() throws FirebaseMessagingException { MockInstanceIdClient client = MockInstanceIdClient.fromResponse(null); @@ -947,14 +693,6 @@ private FirebaseMessaging getMessagingForTopicManagement( .build(); } - private BatchResponse getBatchResponse(String ...messageIds) { - ImmutableList.Builder listBuilder = ImmutableList.builder(); - for (String messageId : messageIds) { - listBuilder.add(SendResponse.fromMessageId(messageId)); - } - return new BatchResponseImpl(listBuilder.build()); - } - private static class MockFirebaseMessagingClient implements FirebaseMessagingClient { private String messageId; @@ -962,7 +700,6 @@ private static class MockFirebaseMessagingClient implements FirebaseMessagingCli private FirebaseMessagingException exception; private Message lastMessage; - private List lastBatch; private boolean isLastDryRun; private ImmutableMap messageMap; @@ -987,10 +724,6 @@ static MockFirebaseMessagingClient fromMessageMap(Map mes return new MockFirebaseMessagingClient(messageMap, null); } - static MockFirebaseMessagingClient fromBatchResponse(BatchResponse batchResponse) { - return new MockFirebaseMessagingClient(null, batchResponse, null); - } - static MockFirebaseMessagingClient fromException(FirebaseMessagingException exception) { return new MockFirebaseMessagingClient(null, null, exception); } @@ -1018,11 +751,6 @@ public String send(Message message, boolean dryRun) throws FirebaseMessagingExce @Override public BatchResponse sendAll( List messages, boolean dryRun) throws FirebaseMessagingException { - lastBatch = messages; - isLastDryRun = dryRun; - if (exception != null) { - throw exception; - } return batchResponse; } } From d812307a3e23b12dad33f150ca6d9708b277f507 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Aug 2024 15:01:28 +0000 Subject: [PATCH 178/269] chore(deps): Bump com.google.cloud:libraries-bom from 26.39.0 to 26.44.0 (#975) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 128632f95..3da4d9fe7 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.39.0 + 26.44.0 pom import From f6472d9f552771e8956d524061070b10940a3b5f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Aug 2024 14:44:50 -0400 Subject: [PATCH 179/269] chore(deps): Bump netty.version from 4.1.110.Final to 4.1.112.Final (#980) Bumps `netty.version` from 4.1.110.Final to 4.1.112.Final. Updates `io.netty:netty-codec-http` from 4.1.110.Final to 4.1.112.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.110.Final...netty-4.1.112.Final) Updates `io.netty:netty-handler` from 4.1.110.Final to 4.1.112.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.110.Final...netty-4.1.112.Final) Updates `io.netty:netty-transport` from 4.1.110.Final to 4.1.112.Final - [Commits](https://github.com/netty/netty/compare/netty-4.1.110.Final...netty-4.1.112.Final) --- updated-dependencies: - dependency-name: io.netty:netty-codec-http dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-handler dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-transport dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3da4d9fe7..8edc7d537 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.110.Final + 4.1.112.Final From 5465926ebecebfaee99f765b0425406e3dc5ad1b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Aug 2024 18:47:58 +0000 Subject: [PATCH 180/269] chore(deps): Bump org.apache.maven.plugins:maven-javadoc-plugin (#986) --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 8edc7d537..f5d141ed0 100644 --- a/pom.xml +++ b/pom.xml @@ -89,7 +89,7 @@ maven-javadoc-plugin - 3.7.0 + 3.10.0 site @@ -301,7 +301,7 @@ maven-javadoc-plugin - 3.7.0 + 3.10.0 attach-javadocs From 6817e15e43c204079e45c1081a0e04b015bf6554 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Aug 2024 18:50:05 +0000 Subject: [PATCH 181/269] chore(deps): Bump com.google.api-client:google-api-client-bom (#987) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f5d141ed0..cabf2260a 100644 --- a/pom.xml +++ b/pom.xml @@ -392,7 +392,7 @@ com.google.api-client google-api-client-bom - 2.5.1 + 2.7.0 pom import From bbfa43a145d0b4a423b98414ae6fb83bdf226643 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Aug 2024 18:53:00 +0000 Subject: [PATCH 182/269] chore(deps): Bump org.apache.maven.plugins:maven-project-info-reports-plugin (#983) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cabf2260a..40185081f 100644 --- a/pom.xml +++ b/pom.xml @@ -367,7 +367,7 @@ maven-project-info-reports-plugin - 3.5.0 + 3.7.0 From 797a8af55a2992e0aa79420617c9b079571fba4b Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Fri, 30 Aug 2024 14:55:28 -0400 Subject: [PATCH 183/269] fix: Limit default `ThreadPoolExecutor` thread count and remove deadlock scenario (#985) * fix: Remove possible deadlock with nested `callAsync()` * Set default `ThreadPoolExecutor` to a fixed thread pool * fix: add keepAliveTime to close idle threads * Added comments explainging the sendEachAsync change --- .../internal/FirebaseThreadManagers.java | 9 +++- .../firebase/messaging/FirebaseMessaging.java | 52 +++++++++++-------- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/google/firebase/internal/FirebaseThreadManagers.java b/src/main/java/com/google/firebase/internal/FirebaseThreadManagers.java index 877450fcc..c14fbd611 100644 --- a/src/main/java/com/google/firebase/internal/FirebaseThreadManagers.java +++ b/src/main/java/com/google/firebase/internal/FirebaseThreadManagers.java @@ -23,7 +23,10 @@ import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -84,7 +87,11 @@ private static class DefaultThreadManager extends GlobalThreadManager { protected ExecutorService doInit() { ThreadFactory threadFactory = FirebaseScheduledExecutor.getThreadFactoryWithName( getThreadFactory(), "firebase-default-%d"); - return Executors.newCachedThreadPool(threadFactory); + + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(100, 100, 60L, + TimeUnit.SECONDS, new LinkedBlockingQueue(), threadFactory); + threadPoolExecutor.allowCoreThreadTimeOut(true); + return threadPoolExecutor; } @Override diff --git a/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java b/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java index e16b6ac9d..870940f77 100644 --- a/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java +++ b/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java @@ -26,6 +26,7 @@ import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.MoreExecutors; import com.google.firebase.ErrorCode; import com.google.firebase.FirebaseApp; import com.google.firebase.ImplFirebaseTrampolines; @@ -159,7 +160,7 @@ protected String execute() throws FirebaseMessagingException { * no failures are only indicated by a {@link BatchResponse}. */ public BatchResponse sendEach(@NonNull List messages) throws FirebaseMessagingException { - return sendEachOp(messages, false).call(); + return sendEach(messages, false); } @@ -186,7 +187,11 @@ public BatchResponse sendEach(@NonNull List messages) throws FirebaseMe */ public BatchResponse sendEach( @NonNull List messages, boolean dryRun) throws FirebaseMessagingException { - return sendEachOp(messages, dryRun).call(); + try { + return sendEachOpAsync(messages, dryRun).get(); + } catch (InterruptedException | ExecutionException e) { + throw new FirebaseMessagingException(ErrorCode.CANCELLED, SERVICE_ID); + } } /** @@ -197,7 +202,7 @@ public BatchResponse sendEach( * the messages have been sent. */ public ApiFuture sendEachAsync(@NonNull List messages) { - return sendEachOp(messages, false).callAsync(app); + return sendEachOpAsync(messages, false); } /** @@ -209,32 +214,37 @@ public ApiFuture sendEachAsync(@NonNull List messages) { * the messages have been sent. */ public ApiFuture sendEachAsync(@NonNull List messages, boolean dryRun) { - return sendEachOp(messages, dryRun).callAsync(app); + return sendEachOpAsync(messages, dryRun); } - private CallableOperation sendEachOp( + // Returns an ApiFuture directly since this function is non-blocking. Individual child send + // requests are still called async and run in background threads. + private ApiFuture sendEachOpAsync( final List messages, final boolean dryRun) { final List immutableMessages = ImmutableList.copyOf(messages); checkArgument(!immutableMessages.isEmpty(), "messages list must not be empty"); checkArgument(immutableMessages.size() <= 500, "messages list must not contain more than 500 elements"); - return new CallableOperation() { - @Override - protected BatchResponse execute() throws FirebaseMessagingException { - List> list = new ArrayList<>(); - for (Message message : immutableMessages) { - ApiFuture messageId = sendOpForSendResponse(message, dryRun).callAsync(app); - list.add(messageId); - } - try { - List responses = ApiFutures.allAsList(list).get(); - return new BatchResponseImpl(responses); - } catch (InterruptedException | ExecutionException e) { - throw new FirebaseMessagingException(ErrorCode.CANCELLED, SERVICE_ID); - } - } - }; + List> list = new ArrayList<>(); + for (Message message : immutableMessages) { + // Make async send calls per message + ApiFuture messageId = sendOpForSendResponse(message, dryRun).callAsync(app); + list.add(messageId); + } + + // Gather all futures and combine into a list + ApiFuture> responsesFuture = ApiFutures.allAsList(list); + + // Chain this future to wrap the eventual responses in a BatchResponse without blocking + // the main thread. This uses the current thread to execute, but since the transformation + // function is non-blocking the transformation itself is also non-blocking. + return ApiFutures.transform( + responsesFuture, + (responses) -> { + return new BatchResponseImpl(responses); + }, + MoreExecutors.directExecutor()); } private CallableOperation sendOpForSendResponse( From db445c58ad901fb5bccefa9ecfc9e22b77ef3c8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Sep 2024 11:21:33 -0400 Subject: [PATCH 184/269] chore(deps): Bump com.google.cloud:libraries-bom from 26.44.0 to 26.45.0 (#989) Bumps [com.google.cloud:libraries-bom](https://github.com/googleapis/java-cloud-bom) from 26.44.0 to 26.45.0. - [Release notes](https://github.com/googleapis/java-cloud-bom/releases) - [Changelog](https://github.com/googleapis/java-cloud-bom/blob/main/release-please-config.json) - [Commits](https://github.com/googleapis/java-cloud-bom/compare/v26.44.0...v26.45.0) --- updated-dependencies: - dependency-name: com.google.cloud:libraries-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 40185081f..57548b668 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.44.0 + 26.45.0 pom import From feed2c5bc731c7d171ea73ab2c5bd149671c1cc4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Sep 2024 15:24:39 +0000 Subject: [PATCH 185/269] chore(deps): Bump org.slf4j:slf4j-api from 2.0.13 to 2.0.16 (#991) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 57548b668..cbebef211 100644 --- a/pom.xml +++ b/pom.xml @@ -438,7 +438,7 @@ org.slf4j slf4j-api - 2.0.13 + 2.0.16 io.netty From 183d4ecda7e8268f88224acedfc698210b0f4009 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Sep 2024 15:26:55 +0000 Subject: [PATCH 186/269] chore(deps): Bump org.apache.maven.plugins:maven-gpg-plugin (#992) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cbebef211..6ab199498 100644 --- a/pom.xml +++ b/pom.xml @@ -157,7 +157,7 @@ maven-gpg-plugin - 3.2.4 + 3.2.5 sign-artifacts From db91e3d1c3753635311919fb5edebe897379fb74 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 11:11:19 -0400 Subject: [PATCH 187/269] chore(deps): Bump org.apache.maven.plugins:maven-failsafe-plugin (#990) Bumps [org.apache.maven.plugins:maven-failsafe-plugin](https://github.com/apache/maven-surefire) from 3.2.5 to 3.5.0. - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.2.5...surefire-3.5.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-failsafe-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6ab199498..f3ba72d2c 100644 --- a/pom.xml +++ b/pom.xml @@ -337,7 +337,7 @@ maven-failsafe-plugin - 3.2.5 + 3.5.0 From 2229ec2cb591baf5b4a04e9e5e7713f6f7c5ed23 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 15:13:32 +0000 Subject: [PATCH 188/269] chore(deps): Bump org.apache.maven.plugins:maven-surefire-plugin (#997) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f3ba72d2c..6cef75888 100644 --- a/pom.xml +++ b/pom.xml @@ -280,7 +280,7 @@ maven-surefire-plugin - 3.2.5 + 3.5.0 ${skipUTs} From b7c792bbfa7105343ba4130c59e67a4a0f14289c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 15:16:05 +0000 Subject: [PATCH 189/269] chore(deps-dev): Bump org.hamcrest:hamcrest from 2.2 to 3.0 (#996) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6cef75888..20428dd00 100644 --- a/pom.xml +++ b/pom.xml @@ -485,7 +485,7 @@ org.hamcrest hamcrest - 2.2 + 3.0 test From f4e50542148ced1321aeb4509b62a981b56284cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 15:18:47 +0000 Subject: [PATCH 190/269] chore(deps): Bump netty.version from 4.1.112.Final to 4.1.113.Final (#995) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 20428dd00..3c2963cde 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.112.Final + 4.1.113.Final From ce8e7fd0df9d8f89b8445ba767762589411e60c2 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Thu, 12 Sep 2024 15:07:12 -0400 Subject: [PATCH 191/269] chore: Update actions/upload-artifact (#998) --- .github/workflows/nightly.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index b29cd2fc4..7bda5ba58 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -48,7 +48,7 @@ jobs: # Attach the packaged artifacts to the workflow output. These can be manually # downloaded for later inspection if necessary. - name: Archive artifacts - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: dist path: dist diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c8a7050d8..ca1f042c4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -59,7 +59,7 @@ jobs: # Attach the packaged artifacts to the workflow output. These can be manually # downloaded for later inspection if necessary. - name: Archive artifacts - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: dist path: dist From ab46aaae81e790c6007afc456080773f8b74f897 Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Fri, 4 Oct 2024 10:59:48 -0400 Subject: [PATCH 192/269] feat: Add HTTP/2 enabled transport as default transport (#979) * Added HTTP/2 enabled transport and made it default. * fix: Use internal default transport * Added test coverage for timeouts * fix: lint * fix: Timeout tests no longer remove Firebase Apps for other integration tests. * Mirror tests from google java client and added more descriptive error messages. * fix: Remove `NO_CONNECT_URL` * debug IT Error * debug * debug * debug * debug * debug * Remove test debug * Address review comments * Use local server to test connect timeout and fix lint * Fix testConnectTimeoutGet test * Fix lint * remove deplicate tests * fix: catch `java.net.SocketTimeoutException` * Proxy tests --- pom.xml | 10 + .../ApacheHttp2AsyncEntityProducer.java | 150 +++++++ .../firebase/internal/ApacheHttp2Request.java | 147 +++++++ .../internal/ApacheHttp2Response.java | 100 +++++ .../internal/ApacheHttp2Transport.java | 125 ++++++ .../firebase/internal/ApiClientUtils.java | 2 +- .../internal/MockApacheHttp2AsyncClient.java | 73 ++++ .../google/firebase/FirebaseOptionsTest.java | 4 +- .../internal/ApacheHttp2TransportIT.java | 409 ++++++++++++++++++ .../internal/ApacheHttp2TransportTest.java | 379 ++++++++++++++++ .../internal/MockApacheHttp2Response.java | 188 ++++++++ .../FirebaseMessagingClientImplTest.java | 8 +- .../messaging/InstanceIdClientImplTest.java | 2 +- .../FirebaseRemoteConfigClientImplTest.java | 2 +- 14 files changed, 1590 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/google/firebase/internal/ApacheHttp2AsyncEntityProducer.java create mode 100644 src/main/java/com/google/firebase/internal/ApacheHttp2Request.java create mode 100644 src/main/java/com/google/firebase/internal/ApacheHttp2Response.java create mode 100644 src/main/java/com/google/firebase/internal/ApacheHttp2Transport.java create mode 100644 src/main/java/com/google/firebase/internal/MockApacheHttp2AsyncClient.java create mode 100644 src/test/java/com/google/firebase/internal/ApacheHttp2TransportIT.java create mode 100644 src/test/java/com/google/firebase/internal/ApacheHttp2TransportTest.java create mode 100644 src/test/java/com/google/firebase/internal/MockApacheHttp2Response.java diff --git a/pom.xml b/pom.xml index 3c2963cde..9fe0bfdd7 100644 --- a/pom.xml +++ b/pom.xml @@ -455,6 +455,11 @@ netty-transport ${netty.version} + + org.apache.httpcomponents.client5 + httpclient5 + 5.3.1 + @@ -488,5 +493,10 @@ 3.0 test + + org.mock-server + mockserver-junit-rule-no-dependencies + 5.14.0 + diff --git a/src/main/java/com/google/firebase/internal/ApacheHttp2AsyncEntityProducer.java b/src/main/java/com/google/firebase/internal/ApacheHttp2AsyncEntityProducer.java new file mode 100644 index 000000000..9bdf208c7 --- /dev/null +++ b/src/main/java/com/google/firebase/internal/ApacheHttp2AsyncEntityProducer.java @@ -0,0 +1,150 @@ +/* + * Copyright 2024 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.util.StreamingContent; +import com.google.common.annotations.VisibleForTesting; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.nio.AsyncEntityProducer; +import org.apache.hc.core5.http.nio.DataStreamChannel; + +public class ApacheHttp2AsyncEntityProducer implements AsyncEntityProducer { + private ByteBuffer bytebuf; + private ByteArrayOutputStream baos; + private final StreamingContent content; + private final ContentType contentType; + private final long contentLength; + private final String contentEncoding; + private final CompletableFuture writeFuture; + private final AtomicReference exception; + + public ApacheHttp2AsyncEntityProducer(StreamingContent content, ContentType contentType, + String contentEncoding, long contentLength, CompletableFuture writeFuture) { + this.content = content; + this.contentType = contentType; + this.contentEncoding = contentEncoding; + this.contentLength = contentLength; + this.writeFuture = writeFuture; + this.bytebuf = null; + + this.baos = new ByteArrayOutputStream((int) (contentLength < 0 ? 0 : contentLength)); + this.exception = new AtomicReference<>(); + } + + public ApacheHttp2AsyncEntityProducer(ApacheHttp2Request request, + CompletableFuture writeFuture) { + this( + request.getStreamingContent(), + ContentType.parse(request.getContentType()), + request.getContentEncoding(), + request.getContentLength(), + writeFuture); + } + + @Override + public boolean isRepeatable() { + return true; + } + + @Override + public String getContentType() { + return contentType != null ? contentType.toString() : null; + } + + @Override + public long getContentLength() { + return contentLength; + } + + @Override + public int available() { + return Integer.MAX_VALUE; + } + + @Override + public String getContentEncoding() { + return contentEncoding; + } + + @Override + public boolean isChunked() { + return contentLength == -1; + } + + @Override + public Set getTrailerNames() { + return null; + } + + @Override + public void produce(DataStreamChannel channel) throws IOException { + if (bytebuf == null) { + if (content != null) { + try { + content.writeTo(baos); + } catch (IOException e) { + failed(e); + throw e; + } + } + + this.bytebuf = ByteBuffer.wrap(baos.toByteArray()); + } + + if (bytebuf.hasRemaining()) { + channel.write(bytebuf); + } + + if (!bytebuf.hasRemaining()) { + channel.endStream(); + writeFuture.complete(null); + releaseResources(); + } + } + + @Override + public void failed(Exception cause) { + if (exception.compareAndSet(null, cause)) { + releaseResources(); + writeFuture.completeExceptionally(cause); + } + } + + public final Exception getException() { + return exception.get(); + } + + @Override + public void releaseResources() { + if (bytebuf != null) { + bytebuf.clear(); + } + } + + @VisibleForTesting + ByteBuffer getBytebuf() { + return bytebuf; + } +} diff --git a/src/main/java/com/google/firebase/internal/ApacheHttp2Request.java b/src/main/java/com/google/firebase/internal/ApacheHttp2Request.java new file mode 100644 index 000000000..56c2c9034 --- /dev/null +++ b/src/main/java/com/google/firebase/internal/ApacheHttp2Request.java @@ -0,0 +1,147 @@ +/* + * Copyright 2024 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.LowLevelHttpRequest; +import com.google.api.client.http.LowLevelHttpResponse; +import com.google.common.annotations.VisibleForTesting; + +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.apache.hc.client5.http.ConnectTimeoutException; +import org.apache.hc.client5.http.HttpHostConnectException; +import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; +import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder; +import org.apache.hc.client5.http.async.methods.SimpleResponseConsumer; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.nio.support.BasicRequestProducer; +import org.apache.hc.core5.http2.H2StreamResetException; +import org.apache.hc.core5.util.Timeout; + +final class ApacheHttp2Request extends LowLevelHttpRequest { + private final CloseableHttpAsyncClient httpAsyncClient; + private final SimpleRequestBuilder requestBuilder; + private SimpleHttpRequest request; + private final RequestConfig.Builder requestConfig; + private int writeTimeout; + private ApacheHttp2AsyncEntityProducer entityProducer; + + ApacheHttp2Request( + CloseableHttpAsyncClient httpAsyncClient, SimpleRequestBuilder requestBuilder) { + this.httpAsyncClient = httpAsyncClient; + this.requestBuilder = requestBuilder; + this.writeTimeout = 0; + + this.requestConfig = RequestConfig.custom() + .setRedirectsEnabled(false); + } + + @Override + public void addHeader(String name, String value) { + requestBuilder.addHeader(name, value); + } + + @Override + public void setTimeout(int connectionTimeout, int readTimeout) throws IOException { + requestConfig + .setConnectTimeout(Timeout.ofMilliseconds(connectionTimeout)) + .setResponseTimeout(Timeout.ofMilliseconds(readTimeout)); + } + + @Override + public void setWriteTimeout(int writeTimeout) throws IOException { + this.writeTimeout = writeTimeout; + } + + @Override + public LowLevelHttpResponse execute() throws IOException { + // Set request configs + requestBuilder.setRequestConfig(requestConfig.build()); + + // Build request + request = requestBuilder.build(); + + // Make Producer + CompletableFuture writeFuture = new CompletableFuture<>(); + entityProducer = new ApacheHttp2AsyncEntityProducer(this, writeFuture); + + // Execute + final Future responseFuture = httpAsyncClient.execute( + new BasicRequestProducer(request, entityProducer), + SimpleResponseConsumer.create(), + new FutureCallback() { + @Override + public void completed(final SimpleHttpResponse response) { + } + + @Override + public void failed(final Exception exception) { + } + + @Override + public void cancelled() { + } + }); + + // Wait for write + try { + if (writeTimeout != 0) { + writeFuture.get(writeTimeout, TimeUnit.MILLISECONDS); + } + } catch (TimeoutException e) { + throw new IOException("Write Timeout", e.getCause()); + } catch (Exception e) { + throw new IOException("Exception in write", e.getCause()); + } + + // Wait for response + try { + final SimpleHttpResponse response = responseFuture.get(); + return new ApacheHttp2Response(response); + } catch (ExecutionException e) { + if (e.getCause() instanceof ConnectTimeoutException + || e.getCause() instanceof SocketTimeoutException) { + throw new IOException("Connection Timeout", e.getCause()); + } else if (e.getCause() instanceof HttpHostConnectException) { + throw new IOException("Connection exception in request", e.getCause()); + } else if (e.getCause() instanceof H2StreamResetException) { + throw new IOException("Stream exception in request", e.getCause()); + } else { + throw new IOException("Unknown exception in request", e); + } + } catch (InterruptedException e) { + throw new IOException("Request Interrupted", e); + } catch (CancellationException e) { + throw new IOException("Request Cancelled", e); + } + } + + @VisibleForTesting + ApacheHttp2AsyncEntityProducer getEntityProducer() { + return entityProducer; + } +} diff --git a/src/main/java/com/google/firebase/internal/ApacheHttp2Response.java b/src/main/java/com/google/firebase/internal/ApacheHttp2Response.java new file mode 100644 index 000000000..4c05b0e03 --- /dev/null +++ b/src/main/java/com/google/firebase/internal/ApacheHttp2Response.java @@ -0,0 +1,100 @@ +/* + * Copyright 2024 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.LowLevelHttpResponse; +import com.google.common.annotations.VisibleForTesting; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.Header; + +public class ApacheHttp2Response extends LowLevelHttpResponse { + private final SimpleHttpResponse response; + private final Header[] allHeaders; + + ApacheHttp2Response(SimpleHttpResponse response) { + this.response = response; + allHeaders = response.getHeaders(); + } + + @Override + public int getStatusCode() { + return response.getCode(); + } + + @Override + public InputStream getContent() throws IOException { + return new ByteArrayInputStream(response.getBodyBytes()); + } + + @Override + public String getContentEncoding() { + Header contentEncodingHeader = response.getFirstHeader("Content-Encoding"); + return contentEncodingHeader == null ? null : contentEncodingHeader.getValue(); + } + + @Override + public long getContentLength() { + String bodyText = response.getBodyText(); + return bodyText == null ? 0 : bodyText.length(); + } + + @Override + public String getContentType() { + ContentType contentType = response.getContentType(); + return contentType == null ? null : contentType.toString(); + } + + @Override + public String getReasonPhrase() { + return response.getReasonPhrase(); + } + + @Override + public String getStatusLine() { + return response.toString(); + } + + public String getHeaderValue(String name) { + return response.getLastHeader(name).getValue(); + } + + @Override + public String getHeaderValue(int index) { + return allHeaders[index].getValue(); + } + + @Override + public int getHeaderCount() { + return allHeaders.length; + } + + @Override + public String getHeaderName(int index) { + return allHeaders[index].getName(); + } + + @VisibleForTesting + public SimpleHttpResponse getResponse() { + return response; + } +} diff --git a/src/main/java/com/google/firebase/internal/ApacheHttp2Transport.java b/src/main/java/com/google/firebase/internal/ApacheHttp2Transport.java new file mode 100644 index 000000000..9c0e413fb --- /dev/null +++ b/src/main/java/com/google/firebase/internal/ApacheHttp2Transport.java @@ -0,0 +1,125 @@ +/* + * Copyright 2024 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.HttpTransport; + +import java.io.IOException; +import java.net.ProxySelector; +import java.util.concurrent.TimeUnit; + +import org.apache.hc.client5.http.async.HttpAsyncClient; +import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder; +import org.apache.hc.client5.http.config.ConnectionConfig; +import org.apache.hc.client5.http.config.TlsConfig; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; +import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner; +import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder; +import org.apache.hc.core5.http.config.Http1Config; +import org.apache.hc.core5.http2.HttpVersionPolicy; +import org.apache.hc.core5.http2.config.H2Config; +import org.apache.hc.core5.io.CloseMode; +import org.apache.hc.core5.ssl.SSLContexts; + +/** + * HTTP/2 enabled async transport based on the Apache HTTP Client library + */ +public final class ApacheHttp2Transport extends HttpTransport { + + private final CloseableHttpAsyncClient httpAsyncClient; + private final boolean isMtls; + + public ApacheHttp2Transport() { + this(newDefaultHttpAsyncClient(), false); + } + + public ApacheHttp2Transport(CloseableHttpAsyncClient httpAsyncClient) { + this(httpAsyncClient, false); + } + + public ApacheHttp2Transport(CloseableHttpAsyncClient httpAsyncClient, boolean isMtls) { + this.httpAsyncClient = httpAsyncClient; + this.isMtls = isMtls; + + httpAsyncClient.start(); + } + + public static CloseableHttpAsyncClient newDefaultHttpAsyncClient() { + return defaultHttpAsyncClientBuilder().build(); + } + + public static HttpAsyncClientBuilder defaultHttpAsyncClientBuilder() { + PoolingAsyncClientConnectionManager connectionManager = + PoolingAsyncClientConnectionManagerBuilder.create() + // Set Max total connections to match google api client limits + // https://github.com/googleapis/google-http-java-client/blob/f9d4e15bd3c784b1fd3b0f3468000a91c6f79715/google-http-client-apache-v5/src/main/java/com/google/api/client/http/apache/v5/Apache5HttpTransport.java#L151 + .setMaxConnTotal(200) + // Set max connections per route to match the concurrent stream limit of the FCM backend. + .setMaxConnPerRoute(100) + .setDefaultConnectionConfig( + ConnectionConfig.custom().setTimeToLive(-1, TimeUnit.MILLISECONDS).build()) + .setDefaultTlsConfig( + TlsConfig.custom().setVersionPolicy(HttpVersionPolicy.NEGOTIATE).build()) + .setTlsStrategy(ClientTlsStrategyBuilder.create() + .setSslContext(SSLContexts.createSystemDefault()) + .build()) + .build(); + + return HttpAsyncClientBuilder.create() + // Set maxConcurrentStreams to 100 to match the concurrent stream limit of the FCM backend. + .setH2Config(H2Config.custom().setMaxConcurrentStreams(100).build()) + .setHttp1Config(Http1Config.DEFAULT) + .setConnectionManager(connectionManager) + .setRoutePlanner(new SystemDefaultRoutePlanner(ProxySelector.getDefault())) + .disableRedirectHandling() + .disableAutomaticRetries(); + } + + @Override + public boolean supportsMethod(String method) { + return true; + } + + @Override + protected ApacheHttp2Request buildRequest(String method, String url) { + SimpleRequestBuilder requestBuilder = SimpleRequestBuilder.create(method).setUri(url); + return new ApacheHttp2Request(httpAsyncClient, requestBuilder); + } + + /** + * Gracefully shuts down the connection manager and releases allocated resources. This closes all + * connections, whether they are currently used or not. + */ + @Override + public void shutdown() throws IOException { + httpAsyncClient.close(CloseMode.GRACEFUL); + } + + /** Returns the Apache HTTP client. */ + public HttpAsyncClient getHttpClient() { + return httpAsyncClient; + } + + /** Returns if the underlying HTTP client is mTLS. */ + @Override + public boolean isMtls() { + return isMtls; + } +} diff --git a/src/main/java/com/google/firebase/internal/ApiClientUtils.java b/src/main/java/com/google/firebase/internal/ApiClientUtils.java index e586bd11f..339726105 100644 --- a/src/main/java/com/google/firebase/internal/ApiClientUtils.java +++ b/src/main/java/com/google/firebase/internal/ApiClientUtils.java @@ -88,6 +88,6 @@ public static JsonFactory getDefaultJsonFactory() { } public static HttpTransport getDefaultTransport() { - return Utils.getDefaultTransport(); + return new ApacheHttp2Transport(); } } diff --git a/src/main/java/com/google/firebase/internal/MockApacheHttp2AsyncClient.java b/src/main/java/com/google/firebase/internal/MockApacheHttp2AsyncClient.java new file mode 100644 index 000000000..c859ec485 --- /dev/null +++ b/src/main/java/com/google/firebase/internal/MockApacheHttp2AsyncClient.java @@ -0,0 +1,73 @@ +/* + * Copyright 2024 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 java.io.IOException; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.function.Supplier; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.nio.AsyncPushConsumer; +import org.apache.hc.core5.http.nio.AsyncRequestProducer; +import org.apache.hc.core5.http.nio.AsyncResponseConsumer; +import org.apache.hc.core5.http.nio.HandlerFactory; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.io.CloseMode; +import org.apache.hc.core5.reactor.IOReactorStatus; +import org.apache.hc.core5.util.TimeValue; + +public class MockApacheHttp2AsyncClient extends CloseableHttpAsyncClient { + + @Override + public void close(CloseMode closeMode) { + } + + @Override + public void close() throws IOException { + } + + @Override + public void start() { + } + + @Override + public IOReactorStatus getStatus() { + return null; + } + + @Override + public void awaitShutdown(TimeValue waitTime) throws InterruptedException { + } + + @Override + public void initiateShutdown() { + } + + @Override + protected Future doExecute(HttpHost target, AsyncRequestProducer requestProducer, + AsyncResponseConsumer responseConsumer, + HandlerFactory pushHandlerFactory, + HttpContext context, FutureCallback callback) { + return null; + } + + @Override + public void register(String hostname, String uriPattern, Supplier supplier) { + } +} diff --git a/src/test/java/com/google/firebase/FirebaseOptionsTest.java b/src/test/java/com/google/firebase/FirebaseOptionsTest.java index 0a4e9e81a..05613e98a 100644 --- a/src/test/java/com/google/firebase/FirebaseOptionsTest.java +++ b/src/test/java/com/google/firebase/FirebaseOptionsTest.java @@ -24,12 +24,12 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; -import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.gson.GsonFactory; import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.cloud.firestore.FirestoreOptions; +import com.google.firebase.internal.ApacheHttp2Transport; import com.google.firebase.testing.ServiceAccount; import com.google.firebase.testing.TestUtils; import java.io.IOException; @@ -74,7 +74,7 @@ protected ThreadFactory getThreadFactory() { @Test public void createOptionsWithAllValuesSet() throws IOException { GsonFactory jsonFactory = new GsonFactory(); - NetHttpTransport httpTransport = new NetHttpTransport(); + ApacheHttp2Transport httpTransport = new ApacheHttp2Transport(); FirestoreOptions firestoreOptions = FirestoreOptions.newBuilder().build(); FirebaseOptions firebaseOptions = FirebaseOptions.builder() diff --git a/src/test/java/com/google/firebase/internal/ApacheHttp2TransportIT.java b/src/test/java/com/google/firebase/internal/ApacheHttp2TransportIT.java new file mode 100644 index 000000000..c8fbe5533 --- /dev/null +++ b/src/test/java/com/google/firebase/internal/ApacheHttp2TransportIT.java @@ -0,0 +1,409 @@ +/* + * Copyright 2024 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 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 static org.mockserver.model.Header.header; +import static org.mockserver.model.HttpForward.forward; +import static org.mockserver.model.HttpRequest.request; +import static org.mockserver.model.HttpResponse.response; + +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequestFactory; +import com.google.api.client.http.HttpResponseException; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.LowLevelHttpResponse; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.util.GenericData; +import com.google.auth.oauth2.GoogleCredentials; +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.FirebaseOptions; +import com.google.firebase.IncomingHttpResponse; +import com.google.firebase.auth.MockGoogleCredentials; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.HttpAsyncClients; +import org.apache.hc.core5.http.EntityDetails; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpRequestInterceptor; +import org.apache.hc.core5.http.protocol.HttpContext; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.mockserver.integration.ClientAndServer; +import org.mockserver.model.HttpForward.Scheme; +import org.mockserver.socket.PortFactory; + +public class ApacheHttp2TransportIT { + private static FirebaseApp app; + private static final GoogleCredentials MOCK_CREDENTIALS = new MockGoogleCredentials("test_token"); + private static final ImmutableMap payload = + ImmutableMap.of("foo", "bar"); + + // Sets a 5 second delay before server response to simulate a slow network that + // results in a read timeout. + private static final String DELAY_URL = "https://nghttp2.org/httpbin/delay/5"; + private static final String GET_URL = "https://nghttp2.org/httpbin/get"; + private static final String POST_URL = "https://nghttp2.org/httpbin/post"; + + private static ServerSocket serverSocket; + private static Socket fillerSocket; + private static int port; + + private static ClientAndServer mockProxy; + private static ClientAndServer mockServer; + + @BeforeClass + public static void setUpClass() throws IOException { + // Start server socket with a backlog queue of 1 and a automatically assigned + // port + serverSocket = new ServerSocket(0, 1); + port = serverSocket.getLocalPort(); + // Fill the backlog queue to force socket to ignore future connections + fillerSocket = new Socket(); + fillerSocket.connect(serverSocket.getLocalSocketAddress()); + } + + @AfterClass + public static void cleanUpClass() throws IOException { + if (serverSocket != null && !serverSocket.isClosed()) { + serverSocket.close(); + } + if (fillerSocket != null && !fillerSocket.isClosed()) { + fillerSocket.close(); + } + } + + @After + public void cleanup() { + if (app != null) { + app.delete(); + } + + if (mockProxy != null && mockProxy.isRunning()) { + mockProxy.close(); + } + + if (mockServer != null && mockServer.isRunning()) { + mockServer.close(); + } + + System.clearProperty("http.proxyHost"); + System.clearProperty("http.proxyPort"); + System.clearProperty("https.proxyHost"); + System.clearProperty("https.proxyPort"); + } + + @Test(timeout = 10_000L) + public void testUnauthorizedGetRequest() throws FirebaseException { + ErrorHandlingHttpClient httpClient = getHttpClient(false); + HttpRequestInfo request = HttpRequestInfo.buildGetRequest(GET_URL); + IncomingHttpResponse response = httpClient.send(request); + assertEquals(200, response.getStatusCode()); + } + + @Test(timeout = 10_000L) + public void testUnauthorizedPostRequest() throws FirebaseException { + ErrorHandlingHttpClient httpClient = getHttpClient(false); + HttpRequestInfo request = HttpRequestInfo.buildJsonPostRequest(POST_URL, payload); + GenericData body = httpClient.sendAndParse(request, GenericData.class); + assertEquals("{\"foo\":\"bar\"}", body.get("data")); + } + + @Test(timeout = 10_000L) + public void testConnectTimeoutAuthorizedGet() throws FirebaseException { + app = FirebaseApp.initializeApp(FirebaseOptions.builder() + .setCredentials(MOCK_CREDENTIALS) + .setConnectTimeout(100) + .build(), "test-app"); + ErrorHandlingHttpClient httpClient = getHttpClient(true, app); + HttpRequestInfo request = HttpRequestInfo.buildGetRequest("https://localhost:" + port); + + try { + httpClient.send(request); + fail("No exception thrown for HTTP error response"); + } catch (FirebaseException e) { + assertEquals(ErrorCode.UNKNOWN, e.getErrorCode()); + assertEquals("IO error: Connection Timeout", e.getMessage()); + assertNull(e.getHttpResponse()); + } + } + + @Test(timeout = 10_000L) + public void testConnectTimeoutAuthorizedPost() throws FirebaseException { + app = FirebaseApp.initializeApp(FirebaseOptions.builder() + .setCredentials(MOCK_CREDENTIALS) + .setConnectTimeout(100) + .build(), "test-app"); + ErrorHandlingHttpClient httpClient = getHttpClient(true, app); + HttpRequestInfo request = HttpRequestInfo.buildJsonPostRequest("https://localhost:" + port, payload); + + try { + httpClient.send(request); + fail("No exception thrown for HTTP error response"); + } catch (FirebaseException e) { + assertEquals(ErrorCode.UNKNOWN, e.getErrorCode()); + assertEquals("IO error: Connection Timeout", e.getMessage()); + assertNull(e.getHttpResponse()); + } + } + + @Test(timeout = 10_000L) + public void testReadTimeoutAuthorizedGet() throws FirebaseException { + app = FirebaseApp.initializeApp(FirebaseOptions.builder() + .setCredentials(MOCK_CREDENTIALS) + .setReadTimeout(100) + .build(), "test-app"); + ErrorHandlingHttpClient httpClient = getHttpClient(true, app); + HttpRequestInfo request = HttpRequestInfo.buildGetRequest(DELAY_URL); + + try { + httpClient.send(request); + fail("No exception thrown for HTTP error response"); + } catch (FirebaseException e) { + assertEquals(ErrorCode.UNKNOWN, e.getErrorCode()); + assertEquals("IO error: Stream exception in request", e.getMessage()); + assertNull(e.getHttpResponse()); + } + } + + @Test(timeout = 10_000L) + public void testReadTimeoutAuthorizedPost() throws FirebaseException { + app = FirebaseApp.initializeApp(FirebaseOptions.builder() + .setCredentials(MOCK_CREDENTIALS) + .setReadTimeout(100) + .build(), "test-app"); + ErrorHandlingHttpClient httpClient = getHttpClient(true, app); + HttpRequestInfo request = HttpRequestInfo.buildJsonPostRequest(DELAY_URL, payload); + + try { + httpClient.send(request); + fail("No exception thrown for HTTP error response"); + } catch (FirebaseException e) { + assertEquals(ErrorCode.UNKNOWN, e.getErrorCode()); + assertEquals("IO error: Stream exception in request", e.getMessage()); + assertNull(e.getHttpResponse()); + } + } + + @Test(timeout = 10_000L) + public void testWriteTimeoutAuthorizedGet() throws FirebaseException { + app = FirebaseApp.initializeApp(FirebaseOptions.builder() + .setCredentials(MOCK_CREDENTIALS) + .setWriteTimeout(100) + .build(), "test-app"); + ErrorHandlingHttpClient httpClient = getHttpClient(true, app); + HttpRequestInfo request = HttpRequestInfo.buildGetRequest(GET_URL); + + try { + httpClient.send(request); + fail("No exception thrown for HTTP error response"); + } catch (FirebaseException e) { + assertEquals(ErrorCode.UNKNOWN, e.getErrorCode()); + assertEquals("IO error: Write Timeout", e.getMessage()); + assertNull(e.getHttpResponse()); + } + } + + @Test(timeout = 10_000L) + public void testWriteTimeoutAuthorizedPost() throws FirebaseException { + app = FirebaseApp.initializeApp(FirebaseOptions.builder() + .setCredentials(MOCK_CREDENTIALS) + .setWriteTimeout(100) + .build(), "test-app"); + ErrorHandlingHttpClient httpClient = getHttpClient(true, app); + HttpRequestInfo request = HttpRequestInfo.buildJsonPostRequest(POST_URL, payload); + + try { + httpClient.send(request); + fail("No exception thrown for HTTP error response"); + } catch (FirebaseException e) { + assertEquals(ErrorCode.UNKNOWN, e.getErrorCode()); + assertEquals("IO error: Write Timeout", e.getMessage()); + assertNull(e.getHttpResponse()); + } + } + + @Test(timeout = 10_000L) + public void testRequestShouldNotFollowRedirects() throws IOException { + ApacheHttp2Transport transport = new ApacheHttp2Transport(); + ApacheHttp2Request request = transport.buildRequest("GET", + "https://google.com"); + LowLevelHttpResponse response = request.execute(); + + assertEquals(301, response.getStatusCode()); + assert (response instanceof ApacheHttp2Response); + assertEquals("https://www.google.com/", ((ApacheHttp2Response) response).getHeaderValue("location")); + } + + @Test(timeout = 10_000L) + public void testRequestCanSetHeaders() { + final AtomicBoolean interceptorCalled = new AtomicBoolean(false); + CloseableHttpAsyncClient client = HttpAsyncClients.custom() + .addRequestInterceptorFirst( + new HttpRequestInterceptor() { + @Override + public void process( + HttpRequest request, EntityDetails details, HttpContext context) + throws HttpException, IOException { + Header header = request.getFirstHeader("foo"); + assertNotNull("Should have found header", header); + assertEquals("bar", header.getValue()); + interceptorCalled.set(true); + throw new IOException("cancelling request"); + } + }) + .build(); + + ApacheHttp2Transport transport = new ApacheHttp2Transport(client); + ApacheHttp2Request request = transport.buildRequest("GET", "http://www.google.com"); + request.addHeader("foo", "bar"); + try { + request.execute(); + fail("should not actually make the request"); + } catch (IOException exception) { + assertEquals("Unknown exception in request", exception.getMessage()); + } + assertTrue("Expected to have called our test interceptor", interceptorCalled.get()); + } + + @Test(timeout = 10_000L) + public void testVerifyProxyIsRespected() { + try { + System.setProperty("https.proxyHost", "localhost"); + System.setProperty("https.proxyPort", "8080"); + + HttpTransport transport = new ApacheHttp2Transport(); + transport.createRequestFactory().buildGetRequest(new GenericUrl(GET_URL)).execute(); + fail("No exception thrown for HTTP error response"); + } catch (IOException e) { + assertEquals("Connection exception in request", e.getMessage()); + assertTrue(e.getCause().getMessage().contains("localhost:8080")); + } + } + + @Test(timeout = 10_000L) + public void testProxyMockHttp() throws Exception { + // Start MockServer + mockProxy = ClientAndServer.startClientAndServer(PortFactory.findFreePort()); + mockServer = ClientAndServer.startClientAndServer(PortFactory.findFreePort()); + + System.setProperty("http.proxyHost", "localhost"); + System.setProperty("http.proxyPort", mockProxy.getPort().toString()); + + // Configure proxy to receieve requests and forward them to a mock destination + // server + mockProxy + .when( + request()) + .forward( + forward() + .withHost("localhost") + .withPort(mockServer.getPort()) + .withScheme(Scheme.HTTP)); + + // Configure server to listen and respond + mockServer + .when( + request()) + .respond( + response() + .withStatusCode(200) + .withBody("Expected server response")); + + // Send a request through the proxy + app = FirebaseApp.initializeApp(FirebaseOptions.builder() + .setCredentials(MOCK_CREDENTIALS) + .setWriteTimeout(100) + .build(), "test-app"); + ErrorHandlingHttpClient httpClient = getHttpClient(true, app); + HttpRequestInfo request = HttpRequestInfo.buildGetRequest("http://www.google.com"); + IncomingHttpResponse response = httpClient.send(request); + + // Verify that the proxy received request with destination host + mockProxy.verify( + request() + .withMethod("GET") + .withPath("/") + .withHeader(header("Host", "www.google.com"))); + + // Verify the forwarded request is received by the server + mockServer.verify( + request() + .withMethod("GET") + .withPath("/")); + + // Verify response + assertEquals(200, response.getStatusCode()); + assertEquals(response.getContent(), "Expected server response"); + } + + private static ErrorHandlingHttpClient getHttpClient(boolean authorized, + FirebaseApp app) { + HttpRequestFactory requestFactory; + if (authorized) { + requestFactory = ApiClientUtils.newAuthorizedRequestFactory(app); + } else { + requestFactory = ApiClientUtils.newUnauthorizedRequestFactory(app); + } + JsonFactory jsonFactory = ApiClientUtils.getDefaultJsonFactory(); + TestHttpErrorHandler errorHandler = new TestHttpErrorHandler(); + return new ErrorHandlingHttpClient<>(requestFactory, jsonFactory, errorHandler); + } + + private static ErrorHandlingHttpClient getHttpClient(boolean authorized) { + app = FirebaseApp.initializeApp(FirebaseOptions.builder() + .setCredentials(MOCK_CREDENTIALS) + .build(), "test-app"); + return getHttpClient(authorized, app); + } + + private static class TestHttpErrorHandler implements HttpErrorHandler { + @Override + public FirebaseException handleIOException(IOException e) { + return new FirebaseException( + ErrorCode.UNKNOWN, "IO error: " + e.getMessage(), e); + } + + @Override + public FirebaseException handleHttpResponseException( + HttpResponseException e, IncomingHttpResponse response) { + return new FirebaseException( + ErrorCode.INTERNAL, "Example error message: " + e.getContent(), e, response); + } + + @Override + public FirebaseException handleParseException(IOException e, IncomingHttpResponse response) { + return new FirebaseException(ErrorCode.UNKNOWN, "Parse error", e, response); + } + } +} diff --git a/src/test/java/com/google/firebase/internal/ApacheHttp2TransportTest.java b/src/test/java/com/google/firebase/internal/ApacheHttp2TransportTest.java new file mode 100644 index 000000000..fcaf563f3 --- /dev/null +++ b/src/test/java/com/google/firebase/internal/ApacheHttp2TransportTest.java @@ -0,0 +1,379 @@ +/* + * Copyright 2024 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 org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import com.google.api.client.http.ByteArrayContent; +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.HttpResponseException; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.InputStreamContent; +import com.google.api.client.http.LowLevelHttpResponse; +import com.google.api.client.util.ByteArrayStreamingContent; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.async.HttpAsyncClient; +import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder; +import org.apache.hc.client5.http.impl.async.HttpAsyncClients; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.EntityDetails; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpRequestMapper; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.impl.bootstrap.HttpServer; +import org.apache.hc.core5.http.impl.io.HttpService; +import org.apache.hc.core5.http.io.HttpRequestHandler; +import org.apache.hc.core5.http.io.entity.ByteArrayEntity; +import org.apache.hc.core5.http.io.support.BasicHttpServerRequestHandler; +import org.apache.hc.core5.http.nio.AsyncPushConsumer; +import org.apache.hc.core5.http.nio.AsyncRequestProducer; +import org.apache.hc.core5.http.nio.AsyncResponseConsumer; +import org.apache.hc.core5.http.nio.HandlerFactory; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.http.protocol.HttpProcessor; +import org.junit.Assert; +import org.junit.Test; + +public class ApacheHttp2TransportTest { + @Test + public void testContentLengthSet() throws Exception { + SimpleRequestBuilder requestBuilder = SimpleRequestBuilder.create(HttpMethods.POST) + .setUri("http://www.google.com"); + + ApacheHttp2Request request = new ApacheHttp2Request( + new MockApacheHttp2AsyncClient() { + @SuppressWarnings("unchecked") + @Override + public Future doExecute( + final HttpHost target, + final AsyncRequestProducer requestProducer, + final AsyncResponseConsumer responseConsumer, + final HandlerFactory pushHandlerFactory, + final HttpContext context, + final FutureCallback callback) { + return (Future) CompletableFuture.completedFuture(new SimpleHttpResponse(200)); + } + }, requestBuilder); + + HttpContent content = new ByteArrayContent("text/plain", + "sample".getBytes(StandardCharsets.UTF_8)); + request.setStreamingContent(content); + request.setContentLength(content.getLength()); + request.execute(); + + assertFalse(request.getEntityProducer().isChunked()); + assertEquals(6, request.getEntityProducer().getContentLength()); + } + + @Test + public void testChunked() throws Exception { + byte[] buf = new byte[300]; + Arrays.fill(buf, (byte) ' '); + SimpleRequestBuilder requestBuilder = SimpleRequestBuilder.create(HttpMethods.POST) + .setUri("http://www.google.com"); + ApacheHttp2Request request = new ApacheHttp2Request( + new MockApacheHttp2AsyncClient() { + @SuppressWarnings("unchecked") + @Override + public Future doExecute( + final HttpHost target, + final AsyncRequestProducer requestProducer, + final AsyncResponseConsumer responseConsumer, + final HandlerFactory pushHandlerFactory, + final HttpContext context, + final FutureCallback callback) { + return (Future) CompletableFuture.completedFuture(new SimpleHttpResponse(200)); + } + }, requestBuilder); + + HttpContent content = new InputStreamContent("text/plain", new ByteArrayInputStream(buf)); + request.setStreamingContent(content); + request.execute(); + + assertTrue(request.getEntityProducer().isChunked()); + assertEquals(-1, request.getEntityProducer().getContentLength()); + } + + @Test + public void testExecute() throws Exception { + SimpleHttpResponse simpleHttpResponse = SimpleHttpResponse.create(200, new byte[] { 1, 2, 3 }); + + SimpleRequestBuilder requestBuilder = SimpleRequestBuilder.create(HttpMethods.POST) + .setUri("http://www.google.com"); + + ApacheHttp2Request request = new ApacheHttp2Request( + new MockApacheHttp2AsyncClient() { + @SuppressWarnings("unchecked") + @Override + public Future doExecute( + final HttpHost target, + final AsyncRequestProducer requestProducer, + final AsyncResponseConsumer responseConsumer, + final HandlerFactory pushHandlerFactory, + final HttpContext context, + final FutureCallback callback) { + return (Future) CompletableFuture.completedFuture(simpleHttpResponse); + } + }, requestBuilder); + LowLevelHttpResponse response = request.execute(); + assertTrue(response instanceof ApacheHttp2Response); + + // we confirm that the simple response we prepared in this test is the same as + // the content's response + assertTrue(response.getContent() instanceof ByteArrayInputStream); + assertEquals(simpleHttpResponse, ((ApacheHttp2Response) response).getResponse()); + // No need to cloase ByteArrayInputStream since close() has no effect. + } + + @Test + public void testApacheHttpTransport() { + ApacheHttp2Transport transport = new ApacheHttp2Transport(); + checkHttpTransport(transport); + assertFalse(transport.isMtls()); + } + + @Test + public void testApacheHttpTransportWithParam() { + HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom(); + ApacheHttp2Transport transport = new ApacheHttp2Transport(clientBuilder.build(), true); + checkHttpTransport(transport); + assertTrue(transport.isMtls()); + } + + @Test + public void testNewDefaultHttpClient() { + HttpAsyncClient client = ApacheHttp2Transport.newDefaultHttpAsyncClient(); + checkHttpClient(client); + } + + @Test + public void testDefaultHttpClientBuilder() { + HttpAsyncClientBuilder clientBuilder = ApacheHttp2Transport.defaultHttpAsyncClientBuilder(); + HttpAsyncClient client = clientBuilder.build(); + checkHttpClient(client); + } + + private void checkHttpTransport(ApacheHttp2Transport transport) { + assertNotNull(transport); + HttpAsyncClient client = transport.getHttpClient(); + checkHttpClient(client); + } + + private void checkHttpClient(HttpAsyncClient client) { + assertNotNull(client); + } + + @Test + public void testRequestsWithContent() throws IOException { + // This test confirms that we can set the content on any type of request + CloseableHttpAsyncClient mockClient = new MockApacheHttp2AsyncClient() { + @SuppressWarnings("unchecked") + @Override + public Future doExecute( + final HttpHost target, + final AsyncRequestProducer requestProducer, + final AsyncResponseConsumer responseConsumer, + final HandlerFactory pushHandlerFactory, + final HttpContext context, + final FutureCallback callback) { + return (Future) CompletableFuture.completedFuture(new SimpleHttpResponse(200)); + } + }; + ApacheHttp2Transport transport = new ApacheHttp2Transport(mockClient); + + // Test GET. + execute(transport.buildRequest("GET", "http://www.test.url")); + // Test DELETE. + execute(transport.buildRequest("DELETE", "http://www.test.url")); + // Test HEAD. + execute(transport.buildRequest("HEAD", "http://www.test.url")); + // Test PATCH. + execute(transport.buildRequest("PATCH", "http://www.test.url")); + // Test PUT. + execute(transport.buildRequest("PUT", "http://www.test.url")); + // Test POST. + execute(transport.buildRequest("POST", "http://www.test.url")); + // Test PATCH. + execute(transport.buildRequest("PATCH", "http://www.test.url")); + } + + @Test + public void testNormalizedUrl() throws IOException { + final HttpRequestHandler handler = new HttpRequestHandler() { + @Override + public void handle( + ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) + throws HttpException, IOException { + // Extract the request URI and convert to bytes + byte[] responseData = request.getRequestUri().getBytes(StandardCharsets.UTF_8); + + // Set the response headers (status code and content length) + response.setCode(HttpStatus.SC_OK); + response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(responseData.length)); + + // Set the response entity (body) + ByteArrayEntity entity = new ByteArrayEntity(responseData, ContentType.TEXT_PLAIN); + response.setEntity(entity); + } + }; + try (FakeServer server = new FakeServer(handler)) { + HttpTransport transport = new ApacheHttp2Transport(); + GenericUrl testUrl = new GenericUrl("http://localhost/foo//bar"); + testUrl.setPort(server.getPort()); + com.google.api.client.http.HttpResponse response = transport.createRequestFactory() + .buildGetRequest(testUrl) + .execute(); + assertEquals(200, response.getStatusCode()); + assertEquals("/foo//bar", response.parseAsString()); + } + } + + @Test + public void testReadErrorStream() throws IOException { + final HttpRequestHandler handler = new HttpRequestHandler() { + @Override + public void handle( + ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) + throws HttpException, IOException { + byte[] responseData = "Forbidden".getBytes(StandardCharsets.UTF_8); + response.setCode(HttpStatus.SC_FORBIDDEN); // 403 Forbidden + response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(responseData.length)); + ByteArrayEntity entity = new ByteArrayEntity(responseData, ContentType.TEXT_PLAIN); + response.setEntity(entity); + } + }; + try (FakeServer server = new FakeServer(handler)) { + HttpTransport transport = new ApacheHttp2Transport(); + GenericUrl testUrl = new GenericUrl("http://localhost/foo//bar"); + testUrl.setPort(server.getPort()); + com.google.api.client.http.HttpRequest getRequest = transport.createRequestFactory() + .buildGetRequest(testUrl); + getRequest.setThrowExceptionOnExecuteError(false); + com.google.api.client.http.HttpResponse response = getRequest.execute(); + assertEquals(403, response.getStatusCode()); + assertEquals("Forbidden", response.parseAsString()); + } + } + + @Test + public void testReadErrorStream_withException() throws IOException { + final HttpRequestHandler handler = new HttpRequestHandler() { + @Override + public void handle( + ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) + throws HttpException, IOException { + byte[] responseData = "Forbidden".getBytes(StandardCharsets.UTF_8); + response.setCode(HttpStatus.SC_FORBIDDEN); // 403 Forbidden + response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(responseData.length)); + ByteArrayEntity entity = new ByteArrayEntity(responseData, ContentType.TEXT_PLAIN); + response.setEntity(entity); + } + }; + try (FakeServer server = new FakeServer(handler)) { + HttpTransport transport = new ApacheHttp2Transport(); + GenericUrl testUrl = new GenericUrl("http://localhost/foo//bar"); + testUrl.setPort(server.getPort()); + com.google.api.client.http.HttpRequest getRequest = transport.createRequestFactory() + .buildGetRequest(testUrl); + try { + getRequest.execute(); + Assert.fail(); + } catch (HttpResponseException ex) { + assertEquals("Forbidden", ex.getContent()); + } + } + } + + private void execute(ApacheHttp2Request request) throws IOException { + byte[] bytes = "abc".getBytes(StandardCharsets.UTF_8); + request.setStreamingContent(new ByteArrayStreamingContent(bytes)); + request.setContentType("text/html"); + request.setContentLength(bytes.length); + request.execute(); + } + + private static class FakeServer implements AutoCloseable { + private final HttpServer server; + + FakeServer(final HttpRequestHandler httpHandler) throws IOException { + HttpRequestMapper mapper = new HttpRequestMapper() { + @Override + public HttpRequestHandler resolve(HttpRequest request, HttpContext context) + throws HttpException { + return httpHandler; + } + }; + server = new HttpServer( + 0, + HttpService.builder() + .withHttpProcessor( + new HttpProcessor() { + @Override + public void process( + HttpRequest request, EntityDetails entity, HttpContext context) + throws HttpException, IOException { + } + + @Override + public void process( + HttpResponse response, EntityDetails entity, HttpContext context) + throws HttpException, IOException { + } + }) + .withHttpServerRequestHandler(new BasicHttpServerRequestHandler(mapper)) + .build(), + null, + null, + null, + null, + null, + null); + server.start(); + } + + public int getPort() { + return server.getLocalPort(); + } + + @Override + public void close() { + server.initiateShutdown(); + } + } +} diff --git a/src/test/java/com/google/firebase/internal/MockApacheHttp2Response.java b/src/test/java/com/google/firebase/internal/MockApacheHttp2Response.java new file mode 100644 index 000000000..0f9c04042 --- /dev/null +++ b/src/test/java/com/google/firebase/internal/MockApacheHttp2Response.java @@ -0,0 +1,188 @@ +/* + * Copyright 2024 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 java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; + +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.ProtocolException; +import org.apache.hc.core5.http.ProtocolVersion; + +public class MockApacheHttp2Response implements HttpResponse { + List

headers = new ArrayList<>(); + int code = 200; + + @Override + public void setVersion(ProtocolVersion version) { + } + + @Override + public ProtocolVersion getVersion() { + return null; + } + + @Override + public void addHeader(Header header) { + headers.add(header); + } + + @Override + public void addHeader(String name, Object value) { + addHeader(newHeader(name, value)); + } + + private Header newHeader(String key, Object value) { + return new Header() { + @Override + public boolean isSensitive() { + return false; + } + + @Override + public String getName() { + return key; + } + + @Override + public String getValue() { + return value.toString(); + } + }; + } + + @Override + public void setHeader(Header header) { + if (headers.contains(header)) { + int index = headers.indexOf(header); + headers.set(index, header); + } else { + addHeader(header); + } + } + + @Override + public void setHeader(String name, Object value) { + setHeader(newHeader(name, value)); + } + + @Override + public void setHeaders(Header... headers) { + for (Header header : headers) { + setHeader(header); + } + } + + @Override + public boolean removeHeader(Header header) { + if (headers.contains(header)) { + headers.remove(headers.indexOf(header)); + return true; + } + return false; + } + + @Override + public boolean removeHeaders(String name) { + int initialSize = headers.size(); + for (Header header : headers.stream().filter(h -> h.getName() == name) + .collect(Collectors.toList())) { + removeHeader(header); + } + return headers.size() < initialSize; + } + + @Override + public boolean containsHeader(String name) { + return headers.stream().anyMatch(h -> h.getName() == name); + } + + @Override + public int countHeaders(String name) { + return headers.size(); + } + + @Override + public Header getFirstHeader(String name) { + return headers.stream().findFirst().orElse(null); + } + + @Override + public Header getHeader(String name) throws ProtocolException { + return headers.stream().filter(h -> h.getName() == name).findFirst().orElse(null); + } + + @Override + public Header[] getHeaders() { + return headers.toArray(new Header[0]); + } + + @Override + public Header[] getHeaders(String name) { + return headers.stream() + .filter(h -> h.getName() == name) + .collect(Collectors.toList()) + .toArray(new Header[0]); + } + + @Override + public Header getLastHeader(String name) { + return headers.isEmpty() ? null : headers.get(headers.size() - 1); + } + + @Override + public Iterator
headerIterator() { + return headers.iterator(); + } + + @Override + public Iterator
headerIterator(String name) { + return headers.stream().filter(h -> h.getName() == name).iterator(); + } + + @Override + public int getCode() { + return this.code; + } + + @Override + public void setCode(int code) { + this.code = code; + } + + @Override + public String getReasonPhrase() { + return null; + } + + @Override + public void setReasonPhrase(String reason) { + } + + @Override + public Locale getLocale() { + return null; + } + + @Override + public void setLocale(Locale loc) { + } +} diff --git a/src/test/java/com/google/firebase/messaging/FirebaseMessagingClientImplTest.java b/src/test/java/com/google/firebase/messaging/FirebaseMessagingClientImplTest.java index 3180aea01..b75d9a3d6 100644 --- a/src/test/java/com/google/firebase/messaging/FirebaseMessagingClientImplTest.java +++ b/src/test/java/com/google/firebase/messaging/FirebaseMessagingClientImplTest.java @@ -368,7 +368,7 @@ private FirebaseMessagingClientImpl initMessagingClient( .setProjectId("test-project") .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) .setRequestFactory(transport.createRequestFactory()) - .setChildRequestFactory(Utils.getDefaultTransport().createRequestFactory()) + .setChildRequestFactory(ApiClientUtils.getDefaultTransport().createRequestFactory()) .setResponseInterceptor(interceptor) .build(); } @@ -379,7 +379,7 @@ private FirebaseMessagingClientImpl initClientWithFaultyTransport() { .setProjectId("test-project") .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) .setRequestFactory(transport.createRequestFactory()) - .setChildRequestFactory(Utils.getDefaultTransport().createRequestFactory()) + .setChildRequestFactory(ApiClientUtils.getDefaultTransport().createRequestFactory()) .build(); } @@ -405,8 +405,8 @@ private FirebaseMessagingClientImpl.Builder fullyPopulatedBuilder() { return FirebaseMessagingClientImpl.builder() .setProjectId("test-project") .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) - .setRequestFactory(Utils.getDefaultTransport().createRequestFactory()) - .setChildRequestFactory(Utils.getDefaultTransport().createRequestFactory()); + .setRequestFactory(ApiClientUtils.getDefaultTransport().createRequestFactory()) + .setChildRequestFactory(ApiClientUtils.getDefaultTransport().createRequestFactory()); } private void checkExceptionFromHttpResponse( diff --git a/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java b/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java index e7222ccb5..1a140680f 100644 --- a/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java +++ b/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java @@ -358,7 +358,7 @@ public void testRequestFactoryIsNull() { @Test(expected = NullPointerException.class) public void testJsonFactoryIsNull() { - new InstanceIdClientImpl(Utils.getDefaultTransport().createRequestFactory(), null); + new InstanceIdClientImpl(ApiClientUtils.getDefaultTransport().createRequestFactory(), null); } @Test diff --git a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java index f2d5c8126..0a04809cf 100644 --- a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java @@ -1173,7 +1173,7 @@ private FirebaseRemoteConfigClientImpl.Builder fullyPopulatedBuilder() { return FirebaseRemoteConfigClientImpl.builder() .setProjectId("test-project") .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) - .setRequestFactory(Utils.getDefaultTransport().createRequestFactory()); + .setRequestFactory(ApiClientUtils.getDefaultTransport().createRequestFactory()); } private void checkGetRequestHeader(HttpRequest request) { From 686287d7607d484003339a7cc86fbbce1e2e18f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 13:38:48 -0400 Subject: [PATCH 193/269] chore(deps): Bump com.google.cloud:libraries-bom from 26.45.0 to 26.48.0 (#1008) Bumps [com.google.cloud:libraries-bom](https://github.com/googleapis/java-cloud-bom) from 26.45.0 to 26.48.0. - [Release notes](https://github.com/googleapis/java-cloud-bom/releases) - [Changelog](https://github.com/googleapis/java-cloud-bom/blob/main/release-please-config.json) - [Commits](https://github.com/googleapis/java-cloud-bom/compare/v26.45.0...v26.48.0) --- updated-dependencies: - dependency-name: com.google.cloud:libraries-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9fe0bfdd7..c0a217b70 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.45.0 + 26.48.0 pom import From 0cb7af3552d64a084fb00dfbe06b7ac63bfd9681 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 17:40:51 +0000 Subject: [PATCH 194/269] chore(deps): Bump org.apache.maven.plugins:maven-gpg-plugin (#1005) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c0a217b70..cb44a39da 100644 --- a/pom.xml +++ b/pom.xml @@ -157,7 +157,7 @@ maven-gpg-plugin - 3.2.5 + 3.2.7 sign-artifacts From f29579fc15952351250d6ef0c7d8236abdce8685 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 17:43:52 +0000 Subject: [PATCH 195/269] chore(deps): Bump org.codehaus.mojo:exec-maven-plugin (#999) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cb44a39da..2b63fb392 100644 --- a/pom.xml +++ b/pom.xml @@ -215,7 +215,7 @@ org.codehaus.mojo exec-maven-plugin - 3.3.0 + 3.4.1 test From c7005d7a3c463132dcd63920c09a7fb53c5feb54 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:17:47 -0400 Subject: [PATCH 196/269] chore(deps): Bump org.apache.maven.plugins:maven-javadoc-plugin (#1011) Bumps [org.apache.maven.plugins:maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.10.0 to 3.10.1. - [Release notes](https://github.com/apache/maven-javadoc-plugin/releases) - [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.10.0...maven-javadoc-plugin-3.10.1) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-javadoc-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 2b63fb392..ad73afd11 100644 --- a/pom.xml +++ b/pom.xml @@ -89,7 +89,7 @@ maven-javadoc-plugin - 3.10.0 + 3.10.1 site @@ -301,7 +301,7 @@ maven-javadoc-plugin - 3.10.0 + 3.10.1 attach-javadocs From f8699ae4b162521f2816e0a4023de813c369a070 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 16:20:39 +0000 Subject: [PATCH 197/269] chore(deps): Bump netty.version from 4.1.113.Final to 4.1.114.Final (#1010) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ad73afd11..f741396d9 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.113.Final + 4.1.114.Final From c0d87b953e58834c10d63da1eb7dc3d2168aae62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 16:22:54 +0000 Subject: [PATCH 198/269] chore(deps): Bump org.apache.maven.plugins:maven-failsafe-plugin (#1009) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f741396d9..7a9fcbc4c 100644 --- a/pom.xml +++ b/pom.xml @@ -337,7 +337,7 @@ maven-failsafe-plugin - 3.5.0 + 3.5.1 From edfa49c328120d4034dbc00053f92f397c6f05e6 Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Wed, 9 Oct 2024 12:47:48 -0400 Subject: [PATCH 199/269] [chore] Release 9.4.0 (#1013) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7a9fcbc4c..62793211e 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 9.3.0 + 9.4.0 jar firebase-admin From 2a58a9f114641a478053dd1e4937ffed6e75e9ae Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Wed, 9 Oct 2024 14:34:55 -0400 Subject: [PATCH 200/269] [chore] Release 9.4.0 Take 2 (#1014) --- .github/resources/settings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/resources/settings.xml b/.github/resources/settings.xml index 847ea52d0..f3bc754e2 100644 --- a/.github/resources/settings.xml +++ b/.github/resources/settings.xml @@ -21,7 +21,7 @@ gpg - 77A3CB7D41F06A4512BA8EA5AA4E6ADF6A05C58E + 0A05D8FAD4287A36C53BE07714D6B82AEB1DD39C ${env.GPG_PASSPHRASE} From 2469a09f8b8aeff517fceb00734afca281132da5 Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Tue, 15 Oct 2024 09:45:54 -0400 Subject: [PATCH 201/269] fix: Remove mockserver dependency (#1022) --- pom.xml | 5 -- .../internal/ApacheHttp2TransportIT.java | 75 ------------------- 2 files changed, 80 deletions(-) diff --git a/pom.xml b/pom.xml index 62793211e..3732011ea 100644 --- a/pom.xml +++ b/pom.xml @@ -493,10 +493,5 @@ 3.0 test - - org.mock-server - mockserver-junit-rule-no-dependencies - 5.14.0 - diff --git a/src/test/java/com/google/firebase/internal/ApacheHttp2TransportIT.java b/src/test/java/com/google/firebase/internal/ApacheHttp2TransportIT.java index c8fbe5533..047e1de0b 100644 --- a/src/test/java/com/google/firebase/internal/ApacheHttp2TransportIT.java +++ b/src/test/java/com/google/firebase/internal/ApacheHttp2TransportIT.java @@ -21,10 +21,6 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.mockserver.model.Header.header; -import static org.mockserver.model.HttpForward.forward; -import static org.mockserver.model.HttpRequest.request; -import static org.mockserver.model.HttpResponse.response; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpRequestFactory; @@ -59,10 +55,6 @@ import org.junit.BeforeClass; import org.junit.Test; -import org.mockserver.integration.ClientAndServer; -import org.mockserver.model.HttpForward.Scheme; -import org.mockserver.socket.PortFactory; - public class ApacheHttp2TransportIT { private static FirebaseApp app; private static final GoogleCredentials MOCK_CREDENTIALS = new MockGoogleCredentials("test_token"); @@ -79,9 +71,6 @@ public class ApacheHttp2TransportIT { private static Socket fillerSocket; private static int port; - private static ClientAndServer mockProxy; - private static ClientAndServer mockServer; - @BeforeClass public static void setUpClass() throws IOException { // Start server socket with a backlog queue of 1 and a automatically assigned @@ -109,14 +98,6 @@ public void cleanup() { app.delete(); } - if (mockProxy != null && mockProxy.isRunning()) { - mockProxy.close(); - } - - if (mockServer != null && mockServer.isRunning()) { - mockServer.close(); - } - System.clearProperty("http.proxyHost"); System.clearProperty("http.proxyPort"); System.clearProperty("https.proxyHost"); @@ -311,62 +292,6 @@ public void testVerifyProxyIsRespected() { } } - @Test(timeout = 10_000L) - public void testProxyMockHttp() throws Exception { - // Start MockServer - mockProxy = ClientAndServer.startClientAndServer(PortFactory.findFreePort()); - mockServer = ClientAndServer.startClientAndServer(PortFactory.findFreePort()); - - System.setProperty("http.proxyHost", "localhost"); - System.setProperty("http.proxyPort", mockProxy.getPort().toString()); - - // Configure proxy to receieve requests and forward them to a mock destination - // server - mockProxy - .when( - request()) - .forward( - forward() - .withHost("localhost") - .withPort(mockServer.getPort()) - .withScheme(Scheme.HTTP)); - - // Configure server to listen and respond - mockServer - .when( - request()) - .respond( - response() - .withStatusCode(200) - .withBody("Expected server response")); - - // Send a request through the proxy - app = FirebaseApp.initializeApp(FirebaseOptions.builder() - .setCredentials(MOCK_CREDENTIALS) - .setWriteTimeout(100) - .build(), "test-app"); - ErrorHandlingHttpClient httpClient = getHttpClient(true, app); - HttpRequestInfo request = HttpRequestInfo.buildGetRequest("http://www.google.com"); - IncomingHttpResponse response = httpClient.send(request); - - // Verify that the proxy received request with destination host - mockProxy.verify( - request() - .withMethod("GET") - .withPath("/") - .withHeader(header("Host", "www.google.com"))); - - // Verify the forwarded request is received by the server - mockServer.verify( - request() - .withMethod("GET") - .withPath("/")); - - // Verify response - assertEquals(200, response.getStatusCode()); - assertEquals(response.getContent(), "Expected server response"); - } - private static ErrorHandlingHttpClient getHttpClient(boolean authorized, FirebaseApp app) { HttpRequestFactory requestFactory; From 0523993e49d5d8241cb9251f3b488636a2a54f8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:26:25 -0400 Subject: [PATCH 202/269] chore(deps): Bump org.apache.maven.plugins:maven-surefire-plugin (#1021) Bumps [org.apache.maven.plugins:maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.5.0 to 3.5.1. - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.5.0...surefire-3.5.1) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-surefire-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3732011ea..a146c39da 100644 --- a/pom.xml +++ b/pom.xml @@ -280,7 +280,7 @@ maven-surefire-plugin - 3.5.0 + 3.5.1 ${skipUTs} From f581e5325c7595dfa40be348bbf2b169382f7491 Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:58:06 -0400 Subject: [PATCH 203/269] [chore] Release 9.4.1 (#1024) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a146c39da..6136770a4 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 9.4.0 + 9.4.1 jar firebase-admin From 79b880950ac97763fc361fe2a3e7fa085bca1539 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Oct 2024 14:27:03 -0400 Subject: [PATCH 204/269] chore(deps): Bump org.apache.maven.plugins:maven-project-info-reports-plugin (#1026) Bumps [org.apache.maven.plugins:maven-project-info-reports-plugin](https://github.com/apache/maven-project-info-reports-plugin) from 3.7.0 to 3.8.0. - [Commits](https://github.com/apache/maven-project-info-reports-plugin/compare/maven-project-info-reports-plugin-3.7.0...maven-project-info-reports-plugin-3.8.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-project-info-reports-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6136770a4..ce2ff9339 100644 --- a/pom.xml +++ b/pom.xml @@ -367,7 +367,7 @@ maven-project-info-reports-plugin - 3.7.0 + 3.8.0 From feaa092802ab1a7b0170eb601c12eedb39c81eeb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Oct 2024 18:30:12 +0000 Subject: [PATCH 205/269] chore(deps): Bump com.google.cloud:libraries-bom from 26.48.0 to 26.49.0 (#1025) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ce2ff9339..39548b0fa 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.48.0 + 26.49.0 pom import From 88b8e5d5b78d1494541a7ec22759d38a788256a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:46:25 -0500 Subject: [PATCH 206/269] chore(deps): Bump org.apache.maven.plugins:maven-surefire-plugin (#1038) Bumps [org.apache.maven.plugins:maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.5.1 to 3.5.2. - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.5.1...surefire-3.5.2) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-surefire-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 39548b0fa..102764938 100644 --- a/pom.xml +++ b/pom.xml @@ -280,7 +280,7 @@ maven-surefire-plugin - 3.5.1 + 3.5.2 ${skipUTs} From 0dff988c2bd2642ba83472c8110e8573d459a532 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Nov 2024 16:49:34 +0000 Subject: [PATCH 207/269] chore(deps): Bump org.codehaus.mojo:exec-maven-plugin (#1037) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 102764938..e0340a794 100644 --- a/pom.xml +++ b/pom.xml @@ -215,7 +215,7 @@ org.codehaus.mojo exec-maven-plugin - 3.4.1 + 3.5.0 test From b4b2fc12510909b6515cc34ded8b8b5ba34ee056 Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:15:13 -0500 Subject: [PATCH 208/269] chore: Add `X-Goog-Api-Client` metric header to outgoing requests (#1039) --- .../firebase/internal/ErrorHandlingHttpClient.java | 1 + src/main/java/com/google/firebase/internal/SdkUtils.java | 8 ++++++++ .../google/firebase/auth/FirebaseUserManagerTest.java | 2 ++ .../google/firebase/auth/internal/CryptoSignersTest.java | 5 +++++ .../auth/multitenancy/FirebaseTenantClientTest.java | 1 + .../auth/multitenancy/TenantAwareFirebaseAuthTest.java | 9 +++++++++ .../com/google/firebase/iid/FirebaseInstanceIdTest.java | 2 ++ .../firebase/internal/ErrorHandlingHttpClientTest.java | 1 + .../messaging/FirebaseMessagingClientImplTest.java | 1 + .../firebase/messaging/InstanceIdClientImplTest.java | 3 +++ .../FirebaseProjectManagementServiceImplTest.java | 1 + .../remoteconfig/FirebaseRemoteConfigClientImplTest.java | 3 +++ 12 files changed, 37 insertions(+) diff --git a/src/main/java/com/google/firebase/internal/ErrorHandlingHttpClient.java b/src/main/java/com/google/firebase/internal/ErrorHandlingHttpClient.java index 5efdd0ec2..cccd0cedb 100644 --- a/src/main/java/com/google/firebase/internal/ErrorHandlingHttpClient.java +++ b/src/main/java/com/google/firebase/internal/ErrorHandlingHttpClient.java @@ -89,6 +89,7 @@ public void sendAndParse(HttpRequestInfo requestInfo, Object destination) throws } public IncomingHttpResponse send(HttpRequestInfo requestInfo) throws T { + requestInfo.addHeader("X-Goog-Api-Client", SdkUtils.getMetricsHeader()); HttpRequest request = createHttpRequest(requestInfo); HttpResponse response = null; diff --git a/src/main/java/com/google/firebase/internal/SdkUtils.java b/src/main/java/com/google/firebase/internal/SdkUtils.java index 2fa5e5767..cd9619087 100644 --- a/src/main/java/com/google/firebase/internal/SdkUtils.java +++ b/src/main/java/com/google/firebase/internal/SdkUtils.java @@ -39,6 +39,14 @@ public static String getVersion() { return SDK_VERSION; } + public static String getJavaVersion() { + return System.getProperty("java.version"); + } + + public static String getMetricsHeader() { + return String.format("gl-java/%s fire-admin/%s", getJavaVersion(), getVersion()); + } + private static String loadSdkVersion() { try (InputStream in = SdkUtils.class.getClassLoader() .getResourceAsStream(ADMIN_SDK_PROPERTIES)) { diff --git a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java index 41085d181..c8c733ce4 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java @@ -3026,6 +3026,8 @@ private static void checkRequestHeaders(TestResponseInterceptor interceptor) { String clientVersion = "Java/Admin/" + SdkUtils.getVersion(); assertEquals(clientVersion, headers.getFirstHeaderStringValue("X-Client-Version")); + assertEquals(SdkUtils.getMetricsHeader(), headers.get("X-Goog-Api-Client")); + } private static void checkUrl(TestResponseInterceptor interceptor, String method, String url) { 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 679fe5a3c..17af1eb76 100644 --- a/src/test/java/com/google/firebase/auth/internal/CryptoSignersTest.java +++ b/src/test/java/com/google/firebase/auth/internal/CryptoSignersTest.java @@ -39,6 +39,7 @@ import com.google.firebase.auth.FirebaseAuthException; import com.google.firebase.auth.MockGoogleCredentials; import com.google.firebase.internal.ApiClientUtils; +import com.google.firebase.internal.SdkUtils; import com.google.firebase.testing.MultiRequestMockHttpTransport; import com.google.firebase.testing.ServiceAccount; import com.google.firebase.testing.TestResponseInterceptor; @@ -87,6 +88,8 @@ public void testIAMCryptoSigner() throws Exception { final String url = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/" + "test-service-account@iam.gserviceaccount.com:signBlob"; assertEquals(url, interceptor.getResponse().getRequest().getUrl().toString()); + HttpRequest request = interceptor.getResponse().getRequest(); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); } @Test @@ -173,6 +176,7 @@ public void testMetadataService() throws Exception { HttpRequest request = interceptor.getResponse().getRequest(); assertEquals(url, request.getUrl().toString()); assertEquals("Bearer test-token", request.getHeaders().getAuthorization()); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); } @Test @@ -203,6 +207,7 @@ public void testExplicitServiceAccountEmail() throws Exception { HttpRequest request = interceptor.getResponse().getRequest(); assertEquals(url, request.getUrl().toString()); assertEquals("Bearer test-token", request.getHeaders().getAuthorization()); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); } @Test 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 eca55ee66..cdb5852d0 100644 --- a/src/test/java/com/google/firebase/auth/multitenancy/FirebaseTenantClientTest.java +++ b/src/test/java/com/google/firebase/auth/multitenancy/FirebaseTenantClientTest.java @@ -353,6 +353,7 @@ private static void checkRequestHeaders(TestResponseInterceptor interceptor) { String clientVersion = "Java/Admin/" + SdkUtils.getVersion(); assertEquals(clientVersion, headers.getFirstHeaderStringValue("X-Client-Version")); + assertEquals(SdkUtils.getMetricsHeader(), headers.get("X-Goog-Api-Client")); } private static void checkUrl(TestResponseInterceptor interceptor, String method, String url) { 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 68e35492e..4cf36a3d7 100644 --- a/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthTest.java +++ b/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthTest.java @@ -38,6 +38,7 @@ import com.google.firebase.auth.MockTokenVerifier; import com.google.firebase.auth.SessionCookieOptions; import com.google.firebase.internal.ApiClientUtils; +import com.google.firebase.internal.SdkUtils; import com.google.firebase.testing.TestResponseInterceptor; import com.google.firebase.testing.TestUtils; import java.io.ByteArrayOutputStream; @@ -85,6 +86,7 @@ public void testCreateSessionCookieAsync() throws Exception { assertEquals("testToken", parsed.get("idToken")); assertEquals(new BigDecimal(3600), parsed.get("validDuration")); checkUrl(interceptor, AUTH_BASE_URL + ":createSessionCookie"); + checkHeaders(interceptor); } @Test @@ -121,6 +123,7 @@ public void testCreateSessionCookie() throws Exception { assertEquals("testToken", parsed.get("idToken")); assertEquals(new BigDecimal(3600), parsed.get("validDuration")); checkUrl(interceptor, AUTH_BASE_URL + ":createSessionCookie"); + checkHeaders(interceptor); } @Test @@ -212,6 +215,7 @@ public void testVerifySessionCookieWithCheckRevoked() throws FirebaseAuthExcepti assertEquals("uid", token.getUid()); assertEquals("cookie", tokenVerifier.getLastTokenString()); checkUrl(interceptor, AUTH_BASE_URL + "/accounts:lookup"); + checkHeaders(interceptor); } @Test @@ -290,4 +294,9 @@ private static void checkUrl(TestResponseInterceptor interceptor, String url) { assertEquals(HttpMethods.POST, request.getRequestMethod()); assertEquals(url, request.getUrl().getRawPath()); } + + private static void checkHeaders(TestResponseInterceptor interceptor) { + HttpRequest request = interceptor.getResponse().getRequest(); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); + } } diff --git a/src/test/java/com/google/firebase/iid/FirebaseInstanceIdTest.java b/src/test/java/com/google/firebase/iid/FirebaseInstanceIdTest.java index 27864d992..f563d87dd 100644 --- a/src/test/java/com/google/firebase/iid/FirebaseInstanceIdTest.java +++ b/src/test/java/com/google/firebase/iid/FirebaseInstanceIdTest.java @@ -38,6 +38,7 @@ import com.google.firebase.OutgoingHttpRequest; import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.auth.MockGoogleCredentials; +import com.google.firebase.internal.SdkUtils; import com.google.firebase.testing.GenericFunction; import com.google.firebase.testing.TestResponseInterceptor; import com.google.firebase.testing.TestUtils; @@ -174,6 +175,7 @@ public Void call(Object... args) throws Exception { assertEquals(HttpMethods.DELETE, request.getRequestMethod()); assertEquals(TEST_URL, request.getUrl().toString()); assertEquals("Bearer test-token", request.getHeaders().getAuthorization()); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); } } diff --git a/src/test/java/com/google/firebase/internal/ErrorHandlingHttpClientTest.java b/src/test/java/com/google/firebase/internal/ErrorHandlingHttpClientTest.java index 8b3e75efd..6d7fb2a9e 100644 --- a/src/test/java/com/google/firebase/internal/ErrorHandlingHttpClientTest.java +++ b/src/test/java/com/google/firebase/internal/ErrorHandlingHttpClientTest.java @@ -127,6 +127,7 @@ public void testSuccessfulRequestWithHeadersAndBody() throws FirebaseException, assertEquals("v1", last.getHeaders().get("h1")); assertEquals("v2", last.getHeaders().get("h2")); assertEquals("v3", last.getHeaders().get("h3")); + assertEquals(SdkUtils.getMetricsHeader(), last.getHeaders().get("x-goog-api-client")); ByteArrayOutputStream out = new ByteArrayOutputStream(); last.getContent().writeTo(out); diff --git a/src/test/java/com/google/firebase/messaging/FirebaseMessagingClientImplTest.java b/src/test/java/com/google/firebase/messaging/FirebaseMessagingClientImplTest.java index b75d9a3d6..805d409a0 100644 --- a/src/test/java/com/google/firebase/messaging/FirebaseMessagingClientImplTest.java +++ b/src/test/java/com/google/firebase/messaging/FirebaseMessagingClientImplTest.java @@ -389,6 +389,7 @@ private void checkRequestHeader(HttpRequest request) { HttpHeaders headers = request.getHeaders(); assertEquals("2", headers.get("X-GOOG-API-FORMAT-VERSION")); assertEquals("fire-admin-java/" + SdkUtils.getVersion(), headers.get("X-Firebase-Client")); + assertEquals(SdkUtils.getMetricsHeader(), headers.get("X-Goog-Api-Client")); } private void checkRequest( diff --git a/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java b/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java index 1a140680f..d0151bb94 100644 --- a/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java +++ b/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java @@ -41,6 +41,7 @@ import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.auth.MockGoogleCredentials; import com.google.firebase.internal.ApiClientUtils; +import com.google.firebase.internal.SdkUtils; import com.google.firebase.testing.TestResponseInterceptor; import com.google.firebase.testing.TestUtils; import java.io.ByteArrayOutputStream; @@ -447,6 +448,8 @@ private void checkTopicManagementRequestHeader( HttpRequest request, String expectedUrl) { assertEquals("POST", request.getRequestMethod()); assertEquals(expectedUrl, request.getUrl().toString()); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); + } private void checkExceptionFromHttpResponse( diff --git a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java index 853074e65..4221adc6c 100644 --- a/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java +++ b/src/test/java/com/google/firebase/projectmanagement/FirebaseProjectManagementServiceImplTest.java @@ -1124,6 +1124,7 @@ private void checkRequestHeader(int index, String expectedUrl, HttpMethod httpMe assertEquals(expectedUrl, request.getUrl().toString()); assertEquals("Bearer test-token", request.getHeaders().getAuthorization()); assertEquals(CLIENT_VERSION, request.getHeaders().get("X-Client-Version")); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); } private void checkRequestPayload(Map expected) throws IOException { diff --git a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java index 0a04809cf..edc52a19d 100644 --- a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java @@ -1185,6 +1185,7 @@ private void checkGetRequestHeader(HttpRequest request, String urlSuffix) { assertEquals(TEST_REMOTE_CONFIG_URL + urlSuffix, request.getUrl().toString()); HttpHeaders headers = request.getHeaders(); assertEquals("fire-admin-java/" + SdkUtils.getVersion(), headers.get("X-Firebase-Client")); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); assertEquals("gzip", headers.getAcceptEncoding()); } @@ -1197,6 +1198,7 @@ private void checkPutRequestHeader(HttpRequest request, String urlSuffix, String assertEquals(TEST_REMOTE_CONFIG_URL + urlSuffix, request.getUrl().toString()); HttpHeaders headers = request.getHeaders(); assertEquals("fire-admin-java/" + SdkUtils.getVersion(), headers.get("X-Firebase-Client")); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); assertEquals("gzip", headers.getAcceptEncoding()); assertEquals(ifMatch, headers.getIfMatch()); } @@ -1206,6 +1208,7 @@ private void checkPostRequestHeader(HttpRequest request, String urlSuffix) { assertEquals(TEST_REMOTE_CONFIG_URL + urlSuffix, request.getUrl().toString()); HttpHeaders headers = request.getHeaders(); assertEquals("fire-admin-java/" + SdkUtils.getVersion(), headers.get("X-Firebase-Client")); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); assertEquals("gzip", headers.getAcceptEncoding()); } From 724b9eee8d0541b2c984eee4b56eb7f6c1314944 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:27:59 +0000 Subject: [PATCH 209/269] chore(deps): Bump com.google.cloud:libraries-bom from 26.49.0 to 26.50.0 (#1040) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e0340a794..02e1b5967 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.49.0 + 26.50.0 pom import From 5e13dab4f1a602e057c944300468e1d3dc5a1b86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:32:50 +0000 Subject: [PATCH 210/269] chore(deps): Bump org.apache.maven.plugins:maven-javadoc-plugin (#1041) --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 02e1b5967..ac9236309 100644 --- a/pom.xml +++ b/pom.xml @@ -89,7 +89,7 @@ maven-javadoc-plugin - 3.10.1 + 3.11.1 site @@ -301,7 +301,7 @@ maven-javadoc-plugin - 3.10.1 + 3.11.1 attach-javadocs From 730f794611f2c737c8fe43701ca4d41e63b1fb62 Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:28:53 -0500 Subject: [PATCH 211/269] fix: Catch `StorageException` in IT to temporarily unblock release (#1043) --- src/test/java/com/google/firebase/cloud/StorageClientIT.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/google/firebase/cloud/StorageClientIT.java b/src/test/java/com/google/firebase/cloud/StorageClientIT.java index 57fe58b82..4542e9735 100644 --- a/src/test/java/com/google/firebase/cloud/StorageClientIT.java +++ b/src/test/java/com/google/firebase/cloud/StorageClientIT.java @@ -23,6 +23,7 @@ import com.google.cloud.storage.Blob; import com.google.cloud.storage.Bucket; +import com.google.cloud.storage.StorageException; import com.google.common.io.CharStreams; import com.google.firebase.testing.IntegrationTestUtils; import java.io.IOException; @@ -52,7 +53,7 @@ public void testCloudStorageNonExistingBucket() { try { storage.bucket("non-existing"); fail("No error thrown for non-existing bucket"); - } catch (IllegalArgumentException expected) { + } catch (IllegalArgumentException | StorageException expected) { // ignore } } From c47d657da544a71376ce3189513d2fea627b1941 Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Thu, 5 Dec 2024 12:21:10 -0500 Subject: [PATCH 212/269] [chore] Release 9.4.2 (#1042) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ac9236309..026f70b9d 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 9.4.1 + 9.4.2 jar firebase-admin From afea3c39b010e0424f1fe1e244ed7fe3eab9752b Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:43:30 -0500 Subject: [PATCH 213/269] chore: Adding delayed response message for holidays (#1049) * Adding delayed response message for holidays * fix date --- .github/ISSUE_TEMPLATE/bug_report.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 36eea6edb..59ba833f5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,6 +7,11 @@ assignees: '' --- +--- +**Thank you for submitting your issue. We are operating at reduced capacity from Dec 23 2024 to Jan 6 2025. Please expect delayed responses. For more urgent requests please reach us via our support channels https://firebase.google.com/support** + +--- + ### [READ] Step 1: Are you in the right place? * For issues or feature requests related to __the code in this repository__ From d6d9b865650cec5457498a4b7ea620721fea8985 Mon Sep 17 00:00:00 2001 From: egilmorez Date: Thu, 2 Jan 2025 08:52:09 -0800 Subject: [PATCH 214/269] Fix typo in FirebaseToken.java (#1051) Fixing typo noted in b/181365473. --- src/main/java/com/google/firebase/auth/FirebaseToken.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/google/firebase/auth/FirebaseToken.java b/src/main/java/com/google/firebase/auth/FirebaseToken.java index 835fa41c9..6950bf16f 100644 --- a/src/main/java/com/google/firebase/auth/FirebaseToken.java +++ b/src/main/java/com/google/firebase/auth/FirebaseToken.java @@ -37,12 +37,12 @@ public final class FirebaseToken { this.claims = ImmutableMap.copyOf(claims); } - /** Returns the Uid for the this token. */ + /** Returns the Uid for this token. */ public String getUid() { return (String) claims.get("sub"); } - /** Returns the tenant ID for the this token. */ + /** Returns the tenant ID for this token. */ public String getTenantId() { Map firebase = (Map) claims.get("firebase"); if (firebase == null) { @@ -51,7 +51,7 @@ public String getTenantId() { return (String) firebase.get("tenant"); } - /** Returns the Issuer for the this token. */ + /** Returns the Issuer for this token. */ public String getIssuer() { return (String) claims.get("iss"); } From 71d324b229ebf6de4e0f45799b3c9a9751ab80d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 15:34:42 +0000 Subject: [PATCH 215/269] chore(deps): Bump com.google.cloud:libraries-bom from 26.50.0 to 26.52.0 (#1050) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 026f70b9d..823cf3cb8 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.50.0 + 26.52.0 pom import From 338e8642d2796308fc109d0389d6db556397ec90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 15:38:25 +0000 Subject: [PATCH 216/269] chore(deps): Bump org.apache.maven.plugins:maven-failsafe-plugin (#1047) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 823cf3cb8..68051551b 100644 --- a/pom.xml +++ b/pom.xml @@ -337,7 +337,7 @@ maven-failsafe-plugin - 3.5.1 + 3.5.2 From 2c203e2b4f2276f7ebc02e2cedeeaa619fad16ff Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Mon, 6 Jan 2025 14:48:51 -0500 Subject: [PATCH 217/269] Revert "chore: Adding delayed response message for holidays (#1049)" (#1052) This reverts commit afea3c39b010e0424f1fe1e244ed7fe3eab9752b. --- .github/ISSUE_TEMPLATE/bug_report.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 59ba833f5..36eea6edb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,11 +7,6 @@ assignees: '' --- ---- -**Thank you for submitting your issue. We are operating at reduced capacity from Dec 23 2024 to Jan 6 2025. Please expect delayed responses. For more urgent requests please reach us via our support channels https://firebase.google.com/support** - ---- - ### [READ] Step 1: Are you in the right place? * For issues or feature requests related to __the code in this repository__ From 6b73abaa1a311042a60937a19c66696f0ca4a7b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Jan 2025 11:10:56 -0500 Subject: [PATCH 218/269] chore(deps): Bump org.apache.maven.plugins:maven-javadoc-plugin (#1054) Bumps [org.apache.maven.plugins:maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.11.1 to 3.11.2. - [Release notes](https://github.com/apache/maven-javadoc-plugin/releases) - [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.11.1...maven-javadoc-plugin-3.11.2) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-javadoc-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 68051551b..580cc8459 100644 --- a/pom.xml +++ b/pom.xml @@ -89,7 +89,7 @@ maven-javadoc-plugin - 3.11.1 + 3.11.2 site @@ -301,7 +301,7 @@ maven-javadoc-plugin - 3.11.1 + 3.11.2 attach-javadocs From 4f4979dcccbf973b9821f9ec9d0414a6b5bcca37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Jan 2025 18:19:31 +0000 Subject: [PATCH 219/269] chore(deps): Bump netty.version from 4.1.114.Final to 4.1.116.Final (#1053) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 580cc8459..d265f63aa 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.114.Final + 4.1.116.Final From fca419ed0c28f6777d455c5419f011fc54855689 Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Thu, 9 Jan 2025 13:25:26 -0500 Subject: [PATCH 220/269] chore: Fix non-existing bucket test (#1056) --- src/test/java/com/google/firebase/cloud/StorageClientIT.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/google/firebase/cloud/StorageClientIT.java b/src/test/java/com/google/firebase/cloud/StorageClientIT.java index 4542e9735..acbaa8e46 100644 --- a/src/test/java/com/google/firebase/cloud/StorageClientIT.java +++ b/src/test/java/com/google/firebase/cloud/StorageClientIT.java @@ -23,7 +23,6 @@ import com.google.cloud.storage.Blob; import com.google.cloud.storage.Bucket; -import com.google.cloud.storage.StorageException; import com.google.common.io.CharStreams; import com.google.firebase.testing.IntegrationTestUtils; import java.io.IOException; @@ -51,9 +50,9 @@ public void testCloudStorageCustomBucket() { public void testCloudStorageNonExistingBucket() { StorageClient storage = StorageClient.getInstance(IntegrationTestUtils.ensureDefaultApp()); try { - storage.bucket("non-existing"); + storage.bucket("non.existing"); fail("No error thrown for non-existing bucket"); - } catch (IllegalArgumentException | StorageException expected) { + } catch (IllegalArgumentException expected) { // ignore } } From d6c5112d29d6e01c48c9c5646ec4ac13ea9c6fb2 Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Thu, 9 Jan 2025 16:12:06 -0500 Subject: [PATCH 221/269] [chore] Release 9.4.3 (#1057) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d265f63aa..b9ca166e4 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 9.4.2 + 9.4.3 jar firebase-admin From 20e2b8bb6874d2cb03efecd6d4107499dc63b16c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 12:37:58 -0500 Subject: [PATCH 222/269] chore(deps): Bump com.google.cloud:libraries-bom from 26.52.0 to 26.53.0 (#1060) Bumps [com.google.cloud:libraries-bom](https://github.com/googleapis/java-cloud-bom) from 26.52.0 to 26.53.0. - [Release notes](https://github.com/googleapis/java-cloud-bom/releases) - [Changelog](https://github.com/googleapis/java-cloud-bom/blob/main/release-please-config.json) - [Commits](https://github.com/googleapis/java-cloud-bom/compare/v26.52.0...v26.53.0) --- updated-dependencies: - dependency-name: com.google.cloud:libraries-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b9ca166e4..2842b4c7a 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.52.0 + 26.53.0 pom import From 7bc8c12270526a85b4a17783fd4aaef1cc711a8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:30:14 +0000 Subject: [PATCH 223/269] chore(deps): Bump com.google.api-client:google-api-client-bom (#1063) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2842b4c7a..96dfe66d8 100644 --- a/pom.xml +++ b/pom.xml @@ -392,7 +392,7 @@ com.google.api-client google-api-client-bom - 2.7.0 + 2.7.2 pom import From af8eed95b3d49fc7b31d1462c86ecad924a2ffb7 Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Thu, 23 Jan 2025 13:56:52 -0500 Subject: [PATCH 224/269] fix: `ApiClientUtils.getDefaultTransport()` should return a cached instance to an `ApacheHttp2Transport` (#1059) * fix: `ApiClientUtils.getDefaultTransport()` should return cached instance to an `ApacheHttp2Transport` * fix: lint * reduce timeout time * fix: use new transport to trigger writeTimeout --- .../firebase/internal/ApiClientUtils.java | 6 +++++- .../internal/ApacheHttp2TransportIT.java | 21 +++++++++++++++++++ .../firebase/internal/ApiClientUtilsTest.java | 9 ++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/google/firebase/internal/ApiClientUtils.java b/src/main/java/com/google/firebase/internal/ApiClientUtils.java index 339726105..9d501fae3 100644 --- a/src/main/java/com/google/firebase/internal/ApiClientUtils.java +++ b/src/main/java/com/google/firebase/internal/ApiClientUtils.java @@ -88,6 +88,10 @@ public static JsonFactory getDefaultJsonFactory() { } public static HttpTransport getDefaultTransport() { - return new ApacheHttp2Transport(); + return TransportInstanceHolder.INSTANCE; + } + + private static class TransportInstanceHolder { + static final HttpTransport INSTANCE = new ApacheHttp2Transport(); } } diff --git a/src/test/java/com/google/firebase/internal/ApacheHttp2TransportIT.java b/src/test/java/com/google/firebase/internal/ApacheHttp2TransportIT.java index 047e1de0b..0beb1b3b4 100644 --- a/src/test/java/com/google/firebase/internal/ApacheHttp2TransportIT.java +++ b/src/test/java/com/google/firebase/internal/ApacheHttp2TransportIT.java @@ -198,9 +198,12 @@ public void testReadTimeoutAuthorizedPost() throws FirebaseException { @Test(timeout = 10_000L) public void testWriteTimeoutAuthorizedGet() throws FirebaseException { + // Use a fresh transport so that writeTimeout triggers while waiting for the transport to + // be ready to receive data. app = FirebaseApp.initializeApp(FirebaseOptions.builder() .setCredentials(MOCK_CREDENTIALS) .setWriteTimeout(100) + .setHttpTransport(new ApacheHttp2Transport()) .build(), "test-app"); ErrorHandlingHttpClient httpClient = getHttpClient(true, app); HttpRequestInfo request = HttpRequestInfo.buildGetRequest(GET_URL); @@ -217,9 +220,12 @@ public void testWriteTimeoutAuthorizedGet() throws FirebaseException { @Test(timeout = 10_000L) public void testWriteTimeoutAuthorizedPost() throws FirebaseException { + // Use a fresh transport so that writeTimeout triggers while waiting for the transport to + // be ready to receive data. app = FirebaseApp.initializeApp(FirebaseOptions.builder() .setCredentials(MOCK_CREDENTIALS) .setWriteTimeout(100) + .setHttpTransport(new ApacheHttp2Transport()) .build(), "test-app"); ErrorHandlingHttpClient httpClient = getHttpClient(true, app); HttpRequestInfo request = HttpRequestInfo.buildJsonPostRequest(POST_URL, payload); @@ -292,6 +298,21 @@ public void testVerifyProxyIsRespected() { } } + @Test + public void testVerifyDefaultTransportReused() { + FirebaseOptions o1 = FirebaseOptions.builder() + .setCredentials(MOCK_CREDENTIALS) + .build(); + + FirebaseOptions o2 = FirebaseOptions.builder() + .setCredentials(MOCK_CREDENTIALS) + .build(); + + HttpTransport t1 = o1.getHttpTransport(); + HttpTransport t2 = o2.getHttpTransport(); + assertEquals(t1, t2); + } + private static ErrorHandlingHttpClient getHttpClient(boolean authorized, FirebaseApp app) { HttpRequestFactory requestFactory; diff --git a/src/test/java/com/google/firebase/internal/ApiClientUtilsTest.java b/src/test/java/com/google/firebase/internal/ApiClientUtilsTest.java index f23c7a34d..e0e619f3c 100644 --- a/src/test/java/com/google/firebase/internal/ApiClientUtilsTest.java +++ b/src/test/java/com/google/firebase/internal/ApiClientUtilsTest.java @@ -25,6 +25,7 @@ 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.HttpTransport; import com.google.api.client.http.HttpUnsuccessfulResponseHandler; import com.google.api.client.testing.http.MockHttpTransport; import com.google.api.client.testing.http.MockLowLevelHttpResponse; @@ -128,4 +129,12 @@ public void disconnect() throws IOException { assertTrue(lowLevelResponse.isDisconnected()); } + + @Test + public void testVerifyDefaultTransportReused() { + HttpTransport t1 = ApiClientUtils.getDefaultTransport(); + HttpTransport t2 = ApiClientUtils.getDefaultTransport(); + assertEquals(t1, t2); + } + } From dcd8b9ed276dbb7d02a5e3725c2ec7e0d258c3d9 Mon Sep 17 00:00:00 2001 From: Yani Kapitanov Date: Tue, 28 Jan 2025 22:46:35 +0100 Subject: [PATCH 225/269] fix: use .equals() instead of == for strings (#1065) --- .../java/com/google/firebase/auth/AbstractFirebaseAuth.java | 5 +++-- src/main/java/com/google/firebase/auth/UserRecord.java | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java b/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java index d1b4a513e..44fe9b7d2 100644 --- a/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java +++ b/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java @@ -600,9 +600,10 @@ private CallableOperation getUserByProviderUi // Although we don't really advertise it, we want to also handle // non-federated idps with this call. So if we detect one of them, we'll // reroute this request appropriately. - if (providerId == "phone") { + if ("phone".equals(providerId)) { return this.getUserByPhoneNumberOp(uid); - } else if (providerId == "email") { + } + if ("email".equals(providerId)) { return this.getUserByEmailOp(uid); } diff --git a/src/main/java/com/google/firebase/auth/UserRecord.java b/src/main/java/com/google/firebase/auth/UserRecord.java index ac962c52d..33c5fbe74 100644 --- a/src/main/java/com/google/firebase/auth/UserRecord.java +++ b/src/main/java/com/google/firebase/auth/UserRecord.java @@ -487,7 +487,7 @@ public UpdateRequest setPhoneNumber(@Nullable String phone) { // *and* by setting providersToUnlink to include 'phone', then we'll reject that. Though // it might also be reasonable to relax this restriction and just unlink it. for (String dp : deleteProviderIterable) { - if (dp == "phone") { + if ("phone".equals(dp)) { throw new IllegalArgumentException( "Both UpdateRequest.setPhoneNumber(null) and " + "UpdateRequest.setProvidersToUnlink(['phone']) were set. To unlink from a " @@ -601,7 +601,7 @@ public UpdateRequest setProvidersToUnlink(Iterable providerIds) { for (String id : providerIds) { checkArgument(!Strings.isNullOrEmpty(id), "providerIds must not be null or empty"); - if (id == "phone" && properties.containsKey("phoneNumber") + if ("phone".equals(id) && properties.containsKey("phoneNumber") && properties.get("phoneNumber") == null) { // If we've been told to unlink the phone provider both via setting phoneNumber to null // *and* by setting providersToUnlink to include 'phone', then we'll reject that. Though From 7b360f2a01745186550c70bd830b50d7c6301dee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Jan 2025 19:35:17 +0000 Subject: [PATCH 226/269] chore(deps): Bump netty.version from 4.1.116.Final to 4.1.117.Final (#1067) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 96dfe66d8..6781d3c64 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.116.Final + 4.1.117.Final From c81173366c8491de64123ca848bd0d63ac062e08 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Mon, 3 Feb 2025 09:53:54 -0500 Subject: [PATCH 227/269] chore: Rearrange firebase admin pom deps (#1066) Co-authored-by: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> --- pom.xml | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 6781d3c64..ed3fa87ad 100644 --- a/pom.xml +++ b/pom.xml @@ -389,18 +389,15 @@ pom import - - com.google.api-client - google-api-client-bom - 2.7.2 - pom - import - + + com.google.auth + google-auth-library-oauth2-http + com.google.api-client google-api-client @@ -417,10 +414,6 @@ com.google.api api-common - - com.google.auth - google-auth-library-oauth2-http - com.google.cloud google-cloud-storage From 6e505832d0fed0288440b8ad657bd568fd36247f Mon Sep 17 00:00:00 2001 From: vitvvv1 <36170346+vitvvv1@users.noreply.github.com> Date: Fri, 21 Mar 2025 16:08:37 +0200 Subject: [PATCH 228/269] feat(fcm): Support `proxy` field in FCM `AndroidNotification` (#1084) * add 'proxy' to AndroidNotification * fix: removed PROXY_UNSPECIFIED from AndroidNotification.Proxy * Update src/main/java/com/google/firebase/messaging/AndroidNotification.java fix comment AndroidNotification.setProxy Co-authored-by: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> --------- Co-authored-by: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> --- .../messaging/AndroidNotification.java | 29 ++++++++++++++++++- .../firebase/messaging/MessageTest.java | 2 ++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/google/firebase/messaging/AndroidNotification.java b/src/main/java/com/google/firebase/messaging/AndroidNotification.java index 05e680910..b509a9843 100644 --- a/src/main/java/com/google/firebase/messaging/AndroidNotification.java +++ b/src/main/java/com/google/firebase/messaging/AndroidNotification.java @@ -112,6 +112,9 @@ public class AndroidNotification { @Key("notification_count") private final Integer notificationCount; + @Key("proxy") + private final String proxy; + private static final Map PRIORITY_MAP = ImmutableMap.builder() .put(Priority.MIN, "PRIORITY_MIN") @@ -179,6 +182,11 @@ private AndroidNotification(Builder builder) { checkArgument(builder.notificationCount >= 0, "notificationCount if specified must be zero or positive valued"); } + if (builder.proxy != null) { + this.proxy = builder.proxy.name(); + } else { + this.proxy = null; + } this.notificationCount = builder.notificationCount; } @@ -194,13 +202,19 @@ public String toString() { return PRIORITY_MAP.get(this); } } - + public enum Visibility { PRIVATE, PUBLIC, SECRET, } + public enum Proxy { + ALLOW, + DENY, + IF_PRIORITY_LOWERED + } + /** * Creates a new {@link AndroidNotification.Builder}. * @@ -237,6 +251,7 @@ public static class Builder { private LightSettings lightSettings; private Boolean defaultLightSettings; private Visibility visibility; + private Proxy proxy; private Builder() {} @@ -602,6 +617,18 @@ public Builder setNotificationCount(int notificationCount) { return this; } + /** + * Sets the proxy of this notification. + * + * @param proxy The proxy value, one of the values in {ALLOW, DENY, IF_PRIORITY_LOWERED} + * + * @return This builder. + */ + public Builder setProxy(Proxy proxy) { + this.proxy = proxy; + return this; + } + /** * Creates a new {@link AndroidNotification} instance from the parameters set on this builder. * diff --git a/src/test/java/com/google/firebase/messaging/MessageTest.java b/src/test/java/com/google/firebase/messaging/MessageTest.java index 992644b35..cea440cca 100644 --- a/src/test/java/com/google/firebase/messaging/MessageTest.java +++ b/src/test/java/com/google/firebase/messaging/MessageTest.java @@ -890,6 +890,7 @@ public void testExtendedAndroidNotificationParameters() throws IOException { .setDefaultLightSettings(false) .setVisibility(AndroidNotification.Visibility.PUBLIC) .setNotificationCount(10) + .setProxy(AndroidNotification.Proxy.DENY) .build()) .build()) .setTopic("test-topic") @@ -923,6 +924,7 @@ public void testExtendedAndroidNotificationParameters() throws IOException { .put("default_light_settings", false) .put("visibility", "public") .put("notification_count", new BigDecimal(10)) + .put("proxy", "DENY") .build()) .build(); assertJsonEquals(ImmutableMap.of( From ca953d82a950c1113e2e312f79e132f4d0daedc0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Apr 2025 14:51:53 +0000 Subject: [PATCH 229/269] chore(deps): Bump com.google.cloud:libraries-bom from 26.53.0 to 26.59.0 (#1087) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ed3fa87ad..0ae84529c 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.53.0 + 26.59.0 pom import From 1cf554545b6071e6e773b75398e32f4bf9700bd9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Apr 2025 14:54:48 +0000 Subject: [PATCH 230/269] chore(deps): Bump org.apache.maven.plugins:maven-compiler-plugin (#1079) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0ae84529c..4235f2462 100644 --- a/pom.xml +++ b/pom.xml @@ -270,7 +270,7 @@ maven-compiler-plugin - 3.13.0 + 3.14.0 1.8 1.8 From 9ac395de94be9a03611e23cacd391d4b592667cc Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Tue, 22 Apr 2025 14:42:53 -0400 Subject: [PATCH 231/269] chore: Updated release GHA to have write permissions (#1088) --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ca1f042c4..d6f744c69 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -78,6 +78,8 @@ jobs: startsWith(github.event.pull_request.title, '[chore] Release ') runs-on: ubuntu-latest + permissions: + contents: write steps: - name: Checkout source for publish From e64da06a7e55a08476a6aba8e191f0481e954c0b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 21:57:37 +0000 Subject: [PATCH 232/269] chore(deps): Bump netty.version from 4.1.117.Final to 4.1.118.Final (#1074) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4235f2462..81cce5d31 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.117.Final + 4.2.0.Final From f784855176d278f29b1d2e78a421c016bb07f994 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 22:00:08 +0000 Subject: [PATCH 233/269] chore(deps): Bump org.apache.maven.plugins:maven-failsafe-plugin (#1090) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 81cce5d31..67eb4119c 100644 --- a/pom.xml +++ b/pom.xml @@ -337,7 +337,7 @@ maven-failsafe-plugin - 3.5.2 + 3.5.3 From ac4a8ad337c37cc0b15f3d1154607681f6e4d9e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 22:08:04 +0000 Subject: [PATCH 234/269] chore(deps): Bump org.slf4j:slf4j-api from 2.0.16 to 2.0.17 (#1089) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 67eb4119c..c2290b86c 100644 --- a/pom.xml +++ b/pom.xml @@ -431,7 +431,7 @@ org.slf4j slf4j-api - 2.0.16 + 2.0.17 io.netty From c6970a5312e54e1df29efaec537b0572bdbaaff3 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Wed, 28 May 2025 16:26:57 -0400 Subject: [PATCH 235/269] feat(fcm): Add `liveActivityToken` to `ApnsConfig` (#1094) * feat: Add liveActivityToken to ApnsConfig Adds a new `liveActivityToken` field to the `ApnsConfig` class, allowing you to specify a Live Activity token for APNS messages. The changes include: - Addition of the `liveActivityToken` field in `ApnsConfig.java` and its associated builder. - Updates to unit tests in `MessageTest.java` to verify the correct serialization of the new field and to include it in existing test cases. A new test method `testApnsMessageWithOnlyLiveActivityToken` was added for specific testing. - Update to the integration test in `FirebaseMessagingIT.java` to include the `liveActivityToken` in a test message. An unrelated fix was also included in `MessageTest.java` to correctly use `new BigDecimal(long)` instead of `BigDecimal.valueOf(long)` for `notification_count`. * fix: Correct ApnsConfig liveActivityToken JSON key Corrects the `@Key` annotation for the `liveActivityToken` field in `ApnsConfig.java` from "live-activity-token" to "live_activity_token" to follow snake_case convention for JSON mapping. Unit tests in `MessageTest.java` have been updated to reflect this change in the expected JSON output. * remove unrelated changes --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> --- .../google/firebase/messaging/ApnsConfig.java | 16 +++++++ .../messaging/FirebaseMessagingIT.java | 1 + .../firebase/messaging/MessageTest.java | 46 +++++++++++++++++-- 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/google/firebase/messaging/ApnsConfig.java b/src/main/java/com/google/firebase/messaging/ApnsConfig.java index 0e5de8619..39092ac17 100644 --- a/src/main/java/com/google/firebase/messaging/ApnsConfig.java +++ b/src/main/java/com/google/firebase/messaging/ApnsConfig.java @@ -41,6 +41,9 @@ public class ApnsConfig { @Key("fcm_options") private final ApnsFcmOptions fcmOptions; + @Key("live_activity_token") + private final String liveActivityToken; + private ApnsConfig(Builder builder) { checkArgument(builder.aps != null, "aps must be specified"); checkArgument(!builder.customData.containsKey("aps"), @@ -51,6 +54,7 @@ private ApnsConfig(Builder builder) { .put("aps", builder.aps.getFields()) .build(); this.fcmOptions = builder.fcmOptions; + this.liveActivityToken = builder.liveActivityToken; } /** @@ -68,6 +72,7 @@ public static class Builder { private final Map customData = new HashMap<>(); private Aps aps; private ApnsFcmOptions fcmOptions; + private String liveActivityToken; private Builder() {} @@ -137,6 +142,17 @@ public Builder setFcmOptions(ApnsFcmOptions apnsFcmOptions) { return this; } + /** + * Sets the Live Activity token. + * + * @param liveActivityToken Live Activity token. + * @return This builder. + */ + public Builder setLiveActivityToken(String liveActivityToken) { + this.liveActivityToken = liveActivityToken; + return this; + } + /** * Creates a new {@link ApnsConfig} instance from the parameters set on this builder. * diff --git a/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java b/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java index d9375781c..91bfc5b9b 100644 --- a/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java +++ b/src/test/java/com/google/firebase/messaging/FirebaseMessagingIT.java @@ -63,6 +63,7 @@ public void testSend() throws Exception { .setBody("Body") .build()) .build()) + .setLiveActivityToken("integration-test-live-activity-token") .build()) .setWebpushConfig(WebpushConfig.builder() .putHeader("X-Custom-Val", "Foo") diff --git a/src/test/java/com/google/firebase/messaging/MessageTest.java b/src/test/java/com/google/firebase/messaging/MessageTest.java index cea440cca..4ac1782ba 100644 --- a/src/test/java/com/google/firebase/messaging/MessageTest.java +++ b/src/test/java/com/google/firebase/messaging/MessageTest.java @@ -411,6 +411,7 @@ public void testApnsMessageWithPayload() throws IOException { .putCustomData("cd1", "cd-v1") .putAllCustomData(ImmutableMap.of("cd2", "cd-v2", "cd3", true)) .setAps(Aps.builder().build()) + .setLiveActivityToken("test-live-activity-token") .build()) .setTopic("test-topic") .build(); @@ -421,10 +422,11 @@ public void testApnsMessageWithPayload() throws IOException { .put("cd3", true) .put("aps", ImmutableMap.of()) .build(); - Map data = ImmutableMap.of( - "headers", ImmutableMap.of("k1", "v1", "k2", "v2", "k3", "v3"), - "payload", payload - ); + Map data = ImmutableMap.builder() + .put("headers", ImmutableMap.of("k1", "v1", "k2", "v2", "k3", "v3")) + .put("payload", payload) + .put("live_activity_token", "test-live-activity-token") + .build(); assertJsonEquals(ImmutableMap.of("topic", "test-topic", "apns", data), message); } @@ -442,6 +444,7 @@ public void testApnsMessageWithPayloadAndAps() throws IOException { .setSound("test-sound") .setThreadId("test-thread-id") .build()) + .setLiveActivityToken("test-live-activity-token-aps") .build()) .setTopic("test-topic") .build(); @@ -459,7 +462,10 @@ public void testApnsMessageWithPayloadAndAps() throws IOException { assertJsonEquals( ImmutableMap.of( "topic", "test-topic", - "apns", ImmutableMap.of("payload", payload)), + "apns", ImmutableMap.builder() + .put("payload", payload) + .put("live_activity_token", "test-live-activity-token-aps") + .build()), message); message = Message.builder() @@ -825,6 +831,7 @@ public void testImageInApnsNotification() throws IOException { .setApnsConfig( ApnsConfig.builder().setAps(Aps.builder().build()) .setFcmOptions(ApnsFcmOptions.builder().setImage(TEST_IMAGE_URL_APNS).build()) + .setLiveActivityToken("test-live-activity-token-image") .build()).build(); ImmutableMap notification = @@ -837,6 +844,7 @@ public void testImageInApnsNotification() throws IOException { ImmutableMap.builder() .put("fcm_options", ImmutableMap.of("image", TEST_IMAGE_URL_APNS)) .put("payload", ImmutableMap.of("aps", ImmutableMap.of())) + .put("live_activity_token", "test-live-activity-token-image") .build(); ImmutableMap expected = ImmutableMap.builder() @@ -847,6 +855,34 @@ public void testImageInApnsNotification() throws IOException { assertJsonEquals(expected, message); } + @Test + public void testApnsMessageWithOnlyLiveActivityToken() throws IOException { + Message message = Message.builder() + .setApnsConfig(ApnsConfig.builder() + .setAps(Aps.builder().build()) + .setLiveActivityToken("only-live-activity") + .build()) + .setTopic("test-topic") + .build(); + Map expectedApns = ImmutableMap.builder() + .put("live_activity_token", "only-live-activity") + .put("payload", ImmutableMap.of("aps", ImmutableMap.of())) + .build(); + assertJsonEquals(ImmutableMap.of("topic", "test-topic", "apns", expectedApns), message); + + // Test without live activity token + message = Message.builder() + .setApnsConfig(ApnsConfig.builder() + .setAps(Aps.builder().build()) + .build()) + .setTopic("test-topic") + .build(); + expectedApns = ImmutableMap.builder() + .put("payload", ImmutableMap.of("aps", ImmutableMap.of())) + .build(); + assertJsonEquals(ImmutableMap.of("topic", "test-topic", "apns", expectedApns), message); + } + @Test public void testInvalidColorInAndroidNotificationLightSettings() { try { From 4f4476cf06c12bcbe1d831750c060e242d0dcef3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 May 2025 20:57:28 +0000 Subject: [PATCH 236/269] chore(deps): Bump org.jacoco:jacoco-maven-plugin from 0.8.12 to 0.8.13 (#1096) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c2290b86c..b12609f6a 100644 --- a/pom.xml +++ b/pom.xml @@ -189,7 +189,7 @@ org.jacoco jacoco-maven-plugin - 0.8.12 + 0.8.13 pre-unit-test From 817dba21bef7834efce7024dbd2234531345c0a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 May 2025 21:01:42 +0000 Subject: [PATCH 237/269] chore(deps): Bump netty.version from 4.2.0.Final to 4.2.1.Final (#1097) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b12609f6a..0310de19c 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.2.0.Final + 4.2.1.Final From 4f73761203316c3d84b9993aebb960c784f561bb Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Thu, 29 May 2025 13:20:46 -0400 Subject: [PATCH 238/269] [chore] Release 9.5.0 (#1099) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0310de19c..96c5ec223 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 9.4.3 + 9.5.0 jar firebase-admin From ac183c020a2fdbe253bea528aa685bfffde01f84 Mon Sep 17 00:00:00 2001 From: joefspiro <97258781+joefspiro@users.noreply.github.com> Date: Fri, 13 Jun 2025 16:39:15 -0400 Subject: [PATCH 239/269] Adds sendEach based snippets for FCM. (#1104) * Adds sendEach based snippets for FCM. * Corrects getting the messaging instance. --- .../snippets/FirebaseMessagingSnippets.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/test/java/com/google/firebase/snippets/FirebaseMessagingSnippets.java b/src/test/java/com/google/firebase/snippets/FirebaseMessagingSnippets.java index 4a4a25472..b17a5d58a 100644 --- a/src/test/java/com/google/firebase/snippets/FirebaseMessagingSnippets.java +++ b/src/test/java/com/google/firebase/snippets/FirebaseMessagingSnippets.java @@ -149,6 +149,36 @@ public void sendAll() throws FirebaseMessagingException { // [END send_all] } + public void sendEach() throws FirebaseMessagingException { + String registrationToken = "YOUR_REGISTRATION_TOKEN"; + + // [START send_each] + // Create a list containing up to 500 messages. + List messages = Arrays.asList( + Message.builder() + .setNotification(Notification.builder() + .setTitle("Price drop") + .setBody("5% off all electronics") + .build()) + .setToken(registrationToken) + .build(), + // ... + Message.builder() + .setNotification(Notification.builder() + .setTitle("Price drop") + .setBody("2% off all books") + .build()) + .setTopic("readers-club") + .build() + ); + + BatchResponse response = FirebaseMessaging.getInstance().sendEach(messages); + // See the BatchResponse reference documentation + // for the contents of response. + System.out.println(response.getSuccessCount() + " messages were sent successfully"); + // [END send_each] + } + public void sendMulticast() throws FirebaseMessagingException { // [START send_multicast] // Create a list containing up to 500 registration tokens. @@ -201,6 +231,36 @@ public void sendMulticastAndHandleErrors() throws FirebaseMessagingException { // [END send_multicast_error] } + public void sendEachForMulticastAndHandleErrors() throws FirebaseMessagingException { + // [START send_each_for_multicast_error] + // These registration tokens come from the client FCM SDKs. + List registrationTokens = Arrays.asList( + "YOUR_REGISTRATION_TOKEN_1", + // ... + "YOUR_REGISTRATION_TOKEN_n" + ); + + MulticastMessage message = MulticastMessage.builder() + .putData("score", "850") + .putData("time", "2:45") + .addAllTokens(registrationTokens) + .build(); + BatchResponse response = FirebaseMessaging.getInstance().sendEachForMulticast(message); + if (response.getFailureCount() > 0) { + List responses = response.getResponses(); + List failedTokens = new ArrayList<>(); + for (int i = 0; i < responses.size(); i++) { + if (!responses.get(i).isSuccessful()) { + // The order of responses corresponds to the order of the registration tokens. + failedTokens.add(registrationTokens.get(i)); + } + } + + System.out.println("List of tokens that caused failures: " + failedTokens); + } + // [END send_each_for_multicast_error] + } + public Message androidMessage() { // [START android_message] Message message = Message.builder() From 82b09a6bfd6d8f27c1991f31623f398439f311c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 20:34:41 +0000 Subject: [PATCH 240/269] chore(deps): Bump org.apache.maven.plugins:maven-project-info-reports-plugin (#1102) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 96c5ec223..15bf121ba 100644 --- a/pom.xml +++ b/pom.xml @@ -367,7 +367,7 @@ maven-project-info-reports-plugin - 3.8.0 + 3.9.0 From 316082b9f3ccf5b97a053a581e79ca8f911c3e96 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:37:19 -0400 Subject: [PATCH 241/269] chore(deps): Bump org.apache.maven.plugins:maven-surefire-plugin (#1101) Bumps [org.apache.maven.plugins:maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.5.2 to 3.5.3. - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.5.2...surefire-3.5.3) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-surefire-plugin dependency-version: 3.5.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 15bf121ba..cec632146 100644 --- a/pom.xml +++ b/pom.xml @@ -280,7 +280,7 @@ maven-surefire-plugin - 3.5.2 + 3.5.3 ${skipUTs} From 4aba7f5be04ca54418caa9393621989ee585d549 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 14:05:59 +0000 Subject: [PATCH 242/269] chore(deps): Bump netty.version from 4.2.1.Final to 4.2.2.Final (#1105) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cec632146..f2ae9d1b4 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.2.1.Final + 4.2.2.Final From 7611f56e8ff06b3f0871f551f6973916bccc0404 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 14:11:26 +0000 Subject: [PATCH 243/269] chore(deps): Bump com.google.cloud:libraries-bom from 26.59.0 to 26.62.0 (#1106) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f2ae9d1b4..60b450481 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.59.0 + 26.62.0 pom import From 46117439d7b81beb8bc1b61b6bb4d0068affe356 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Jul 2025 13:57:19 +0000 Subject: [PATCH 244/269] chore(deps): Bump org.codehaus.mojo:exec-maven-plugin (#1109) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 60b450481..8b1cef4b3 100644 --- a/pom.xml +++ b/pom.xml @@ -215,7 +215,7 @@ org.codehaus.mojo exec-maven-plugin - 3.5.0 + 3.5.1 test From 6b105cc065f2b0a8b863082900a14648f2921543 Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Tue, 8 Jul 2025 12:48:51 -0400 Subject: [PATCH 245/269] chore: Update tests for getting and deleting provider uid via phone number and email. (#1068) --- .../firebase/auth/FirebaseUserManagerTest.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java index c8c733ce4..9369eae8d 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java @@ -402,9 +402,11 @@ public void testGetUserByProviderUidWithPhone() throws Exception { TestResponseInterceptor interceptor = initializeAppForUserManagement( TestUtils.loadResource("getUser.json")); UserRecord userRecord = FirebaseAuth.getInstance() - .getUserByProviderUidAsync("phone", "+1234567890").get(); + .getUserByProviderUidAsync(new String("phone"), "+1234567890").get(); checkUserRecord(userRecord); checkRequestHeaders(interceptor); + GenericJson parsed = parseRequestContent(interceptor); + assertEquals(ImmutableList.of("+1234567890"), parsed.get("phoneNumber")); } @Test @@ -412,9 +414,11 @@ public void testGetUserByProviderUidWithEmail() throws Exception { TestResponseInterceptor interceptor = initializeAppForUserManagement( TestUtils.loadResource("getUser.json")); UserRecord userRecord = FirebaseAuth.getInstance() - .getUserByProviderUidAsync("email", "testuser@example.com").get(); + .getUserByProviderUidAsync(new String("email"), "testuser@example.com").get(); checkUserRecord(userRecord); checkRequestHeaders(interceptor); + GenericJson parsed = parseRequestContent(interceptor); + assertEquals(ImmutableList.of("testuser@example.com"), parsed.get("email")); } @Test @@ -1248,11 +1252,12 @@ public void testDeleteProviderAndPhone() { @Test public void testDoubleDeletePhoneProvider() throws Exception { + String providerId = new String("phone"); UserRecord.UpdateRequest update = new UserRecord.UpdateRequest("uid") .setPhoneNumber(null); try { - update.setProvidersToUnlink(ImmutableList.of("phone")); + update.setProvidersToUnlink(ImmutableList.of(providerId)); fail("No error thrown for double delete phone provider"); } catch (IllegalArgumentException expected) { } @@ -1260,8 +1265,9 @@ public void testDoubleDeletePhoneProvider() throws Exception { @Test public void testDoubleDeletePhoneProviderReverseOrder() throws Exception { + String providerId = new String("phone"); UserRecord.UpdateRequest update = new UserRecord.UpdateRequest("uid") - .setProvidersToUnlink(ImmutableList.of("phone")); + .setProvidersToUnlink(ImmutableList.of(providerId)); try { update.setPhoneNumber(null); From 813585c970b27f9af718ee00b0e8a918a04459d2 Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Wed, 9 Jul 2025 11:14:54 -0400 Subject: [PATCH 246/269] chore: fixed broken google cloud doc reference links (#1112) --- .../com/google/firebase/cloud/FirestoreClient.java | 10 +++++----- .../java/com/google/firebase/cloud/StorageClient.java | 4 ++-- .../firebase/snippets/FirebaseStorageSnippets.java | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/google/firebase/cloud/FirestoreClient.java b/src/main/java/com/google/firebase/cloud/FirestoreClient.java index e55d5c7e9..682c39f07 100644 --- a/src/main/java/com/google/firebase/cloud/FirestoreClient.java +++ b/src/main/java/com/google/firebase/cloud/FirestoreClient.java @@ -19,7 +19,7 @@ /** * {@code FirestoreClient} provides access to Google Cloud Firestore. Use this API to obtain a - * {@code Firestore} + * {@code Firestore} * instance, which provides methods for updating and querying data in Firestore. * *

A Google Cloud project ID is required to access Firestore. FirestoreClient determines the @@ -60,7 +60,7 @@ private FirestoreClient(FirebaseApp app, String databaseId) { * same instance for all invocations. The Firestore instance and all references obtained from it * becomes unusable, once the default app is deleted. * - * @return A non-null {@code Firestore} + * @return A non-null {@code Firestore} * instance. */ @NonNull @@ -74,7 +74,7 @@ public static Firestore getFirestore() { * obtained from it becomes unusable, once the specified app is deleted. * * @param app A non-null {@link FirebaseApp}. - * @return A non-null {@code Firestore} + * @return A non-null {@code Firestore} * instance. */ @NonNull @@ -90,7 +90,7 @@ public static Firestore getFirestore(FirebaseApp app) { * * @param app A non-null {@link FirebaseApp}. * @param database - The name of database. - * @return A non-null {@code Firestore} + * @return A non-null {@code Firestore} * instance. */ @NonNull @@ -104,7 +104,7 @@ public static Firestore getFirestore(FirebaseApp app, String database) { * references obtained from it becomes unusable, once the default app is deleted. * * @param database - The name of database. - * @return A non-null {@code Firestore} + * @return A non-null {@code Firestore} * instance. */ @NonNull diff --git a/src/main/java/com/google/firebase/cloud/StorageClient.java b/src/main/java/com/google/firebase/cloud/StorageClient.java index 955abd7e2..172279990 100644 --- a/src/main/java/com/google/firebase/cloud/StorageClient.java +++ b/src/main/java/com/google/firebase/cloud/StorageClient.java @@ -71,7 +71,7 @@ public static synchronized StorageClient getInstance(FirebaseApp app) { * configured via {@link com.google.firebase.FirebaseOptions} when initializing the app. If * no bucket was configured via options, this method throws an exception. * - * @return a cloud storage {@code Bucket} + * @return a cloud storage {@code Bucket} * instance. * @throws IllegalArgumentException If no bucket is configured via FirebaseOptions, * or if the bucket does not exist. @@ -84,7 +84,7 @@ public Bucket bucket() { * Returns a cloud storage Bucket instance for the specified bucket name. * * @param name a non-null, non-empty bucket name. - * @return a cloud storage {@code Bucket} + * @return a cloud storage {@code Bucket} * instance. * @throws IllegalArgumentException If the bucket name is null, empty, or if the specified * bucket does not exist. diff --git a/src/test/java/com/google/firebase/snippets/FirebaseStorageSnippets.java b/src/test/java/com/google/firebase/snippets/FirebaseStorageSnippets.java index 706cc1b16..0c41dbd6a 100644 --- a/src/test/java/com/google/firebase/snippets/FirebaseStorageSnippets.java +++ b/src/test/java/com/google/firebase/snippets/FirebaseStorageSnippets.java @@ -42,7 +42,7 @@ public void initializeAppForStorage() throws IOException { Bucket bucket = StorageClient.getInstance().bucket(); // 'bucket' is an object defined in the google-cloud-storage Java library. - // See http://googlecloudplatform.github.io/google-cloud-java/latest/apidocs/com/google/cloud/storage/Bucket.html + // See https://cloud.google.com/java/docs/reference/google-cloud-storage/latest/com.google.cloud.storage.Bucket // for more details. // [END init_admin_sdk_for_storage] System.out.println("Retrieved bucket: " + bucket.getName()); From df5cd667809e3eb33bdfb5a356c33adb7d93cd3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 16:34:48 +0000 Subject: [PATCH 247/269] chore(deps): Bump org.apache.maven.plugins:maven-gpg-plugin (#1111) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8b1cef4b3..fac974972 100644 --- a/pom.xml +++ b/pom.xml @@ -157,7 +157,7 @@ maven-gpg-plugin - 3.2.7 + 3.2.8 sign-artifacts From 51258b524a7097cd183497e634e0cbfd20ee52f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 16:41:55 +0000 Subject: [PATCH 248/269] chore(deps): Bump com.google.cloud:libraries-bom from 26.62.0 to 26.63.0 (#1110) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fac974972..d04209790 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.62.0 + 26.63.0 pom import From 91c1400aa24995e61d22357cc26aebd0d48594e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 20:25:57 +0000 Subject: [PATCH 249/269] chore(deps): Bump com.google.cloud:libraries-bom from 26.63.0 to 26.65.0 (#1116) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d04209790..3861679f7 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.63.0 + 26.65.0 pom import From 212ecea4bcccf4f6a9df42d21f70f66ebefe809b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 20:29:22 +0000 Subject: [PATCH 250/269] chore(deps): Bump netty.version from 4.2.2.Final to 4.2.3.Final (#1113) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3861679f7..8729a2b34 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.2.2.Final + 4.2.3.Final From e406aca8560f8509e3021075a66b34ac5167a667 Mon Sep 17 00:00:00 2001 From: Liubin Jiang <56564857+Xiaoshouzi-gh@users.noreply.github.com> Date: Tue, 2 Sep 2025 08:05:15 -0700 Subject: [PATCH 251/269] feat(auth): Firebase Auth add link domain to actionCodeSetting (#1115) * add link domain to actionCodeSetting * fix: Correct deprecation tag * fix(auth): Fix doc string and use correct error message --------- Co-authored-by: jonathanedey Co-authored-by: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> --- .../firebase/auth/ActionCodeSettings.java | 20 +++++++++++++++++++ .../google/firebase/auth/AuthErrorCode.java | 5 +++++ .../auth/internal/AuthErrorHandler.java | 7 +++++++ .../firebase/auth/ActionCodeSettingsTest.java | 2 ++ 4 files changed, 34 insertions(+) diff --git a/src/main/java/com/google/firebase/auth/ActionCodeSettings.java b/src/main/java/com/google/firebase/auth/ActionCodeSettings.java index 0b102b7a3..8ffccd542 100644 --- a/src/main/java/com/google/firebase/auth/ActionCodeSettings.java +++ b/src/main/java/com/google/firebase/auth/ActionCodeSettings.java @@ -51,6 +51,9 @@ private ActionCodeSettings(Builder builder) { if (!Strings.isNullOrEmpty(builder.dynamicLinkDomain)) { properties.put("dynamicLinkDomain", builder.dynamicLinkDomain); } + if (!Strings.isNullOrEmpty(builder.linkDomain)) { + properties.put("linkDomain", builder.linkDomain); + } if (!Strings.isNullOrEmpty(builder.iosBundleId)) { properties.put("iOSBundleId", builder.iosBundleId); } @@ -84,6 +87,7 @@ public static final class Builder { private String url; private boolean handleCodeInApp; private String dynamicLinkDomain; + private String linkDomain; private String iosBundleId; private String androidPackageName; private String androidMinimumVersion; @@ -135,12 +139,28 @@ public Builder setHandleCodeInApp(boolean handleCodeInApp) { * * @param dynamicLinkDomain Firebase Dynamic Link domain string. * @return This builder. + * @deprecated Use {@link #setLinkDomain(String)} instead. */ + @Deprecated public Builder setDynamicLinkDomain(String dynamicLinkDomain) { this.dynamicLinkDomain = dynamicLinkDomain; return this; } + /** + * Sets the link domain to use for the current link if it is to be opened using + * {@code handleCodeInApp}, as multiple link domains can be configured per project. This + * setting provides the ability to explicitly choose one. If none is provided, the default + * Firebase Hosting domain will be used. + * + * @param linkDomain Link domain string. + * @return This builder. + */ + public Builder setLinkDomain(String linkDomain) { + this.linkDomain = linkDomain; + return this; + } + /** * Sets the bundle ID of the iOS app where the link should be handled if the * application is already installed on the device. diff --git a/src/main/java/com/google/firebase/auth/AuthErrorCode.java b/src/main/java/com/google/firebase/auth/AuthErrorCode.java index 90b5da1a2..9f7ecebf1 100644 --- a/src/main/java/com/google/firebase/auth/AuthErrorCode.java +++ b/src/main/java/com/google/firebase/auth/AuthErrorCode.java @@ -58,6 +58,11 @@ public enum AuthErrorCode { */ INVALID_DYNAMIC_LINK_DOMAIN, + /** + * The provided hosting link domain is not configured or authorized for the current project. + */ + INVALID_HOSTING_LINK_DOMAIN, + /** * The specified ID token is invalid. */ diff --git a/src/main/java/com/google/firebase/auth/internal/AuthErrorHandler.java b/src/main/java/com/google/firebase/auth/internal/AuthErrorHandler.java index 4b9523c83..346bd6a84 100644 --- a/src/main/java/com/google/firebase/auth/internal/AuthErrorHandler.java +++ b/src/main/java/com/google/firebase/auth/internal/AuthErrorHandler.java @@ -73,6 +73,13 @@ final class AuthErrorHandler extends AbstractHttpErrorHandler Date: Tue, 2 Sep 2025 15:23:53 +0000 Subject: [PATCH 252/269] chore(deps): Bump com.google.cloud:libraries-bom from 26.65.0 to 26.66.0 (#1119) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8729a2b34..aa2c74f5c 100644 --- a/pom.xml +++ b/pom.xml @@ -385,7 +385,7 @@ com.google.cloud libraries-bom - 26.65.0 + 26.67.0 pom import From ae5537ebe65536e73e6d93e0a42164205dedf9e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 15:26:47 +0000 Subject: [PATCH 253/269] chore(deps): Bump org.apache.maven.plugins:maven-javadoc-plugin (#1118) --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index aa2c74f5c..1a7c778f2 100644 --- a/pom.xml +++ b/pom.xml @@ -89,7 +89,7 @@ maven-javadoc-plugin - 3.11.2 + 3.11.3 site @@ -301,7 +301,7 @@ maven-javadoc-plugin - 3.11.2 + 3.11.3 attach-javadocs From 0406ed57ed17cd83030a50b43022622b684c2f66 Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Tue, 2 Sep 2025 14:55:56 -0400 Subject: [PATCH 254/269] fix(auth): Fixed error code mapping for Auth errors (#1121) --- .../auth/internal/AuthErrorHandler.java | 4 +-- .../google/firebase/auth/FirebaseAuthIT.java | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/google/firebase/auth/internal/AuthErrorHandler.java b/src/main/java/com/google/firebase/auth/internal/AuthErrorHandler.java index 346bd6a84..cd570c64d 100644 --- a/src/main/java/com/google/firebase/auth/internal/AuthErrorHandler.java +++ b/src/main/java/com/google/firebase/auth/internal/AuthErrorHandler.java @@ -187,7 +187,7 @@ String buildMessage(AuthServiceErrorResponse response) { /** * JSON data binding for JSON error messages sent by Google identity toolkit service. These - * error messages take the form `{"error": {"message": "CODE: OPTIONAL DETAILS"}}`. + * error messages take the form `{"error": {"message": "CODE : OPTIONAL DETAILS"}}`. */ private static class AuthServiceErrorResponse { @@ -203,7 +203,7 @@ public String getCode() { int separator = message.indexOf(':'); if (separator != -1) { - return message.substring(0, separator); + return message.substring(0, separator).trim(); } return message; diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java index 5862450ae..41ccc5c20 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java +++ b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java @@ -82,6 +82,7 @@ public class FirebaseAuthIT { private static final JsonFactory jsonFactory = ApiClientUtils.getDefaultJsonFactory(); private static final HttpTransport transport = ApiClientUtils.getDefaultTransport(); private static final String ACTION_LINK_CONTINUE_URL = "http://localhost/?a=1&b=2#c=3"; + private static final String INVALID_ACTION_LINK_CONTINUE_URL = "http://www.localhost/?a=1&b=2#c=3"; private static final FirebaseAuth auth = FirebaseAuth.getInstance( IntegrationTestUtils.ensureDefaultApp()); @@ -868,6 +869,31 @@ public void testGenerateSignInWithEmailLink() throws Exception { assertTrue(auth.getUser(user.getUid()).isEmailVerified()); } + @Test + public void testAuthErrorCodeParse() throws Exception { + RandomUser user = UserTestUtils.generateRandomUserInfo(); + temporaryUser.create(new UserRecord.CreateRequest() + .setUid(user.getUid()) + .setEmail(user.getEmail()) + .setEmailVerified(false) + .setPassword("password")); + try { + auth.generateSignInWithEmailLink(user.getEmail(), ActionCodeSettings.builder() + .setUrl(INVALID_ACTION_LINK_CONTINUE_URL) + .build()); + fail("No error thrown for invlaid custom hosting domain"); + } catch (FirebaseAuthException e) { + assertEquals( + "The domain of the continue URL is not whitelisted (UNAUTHORIZED_DOMAIN): Domain not " + + "allowlisted by project", + e.getMessage()); + assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode()); + assertNotNull(e.getCause()); + assertNotNull(e.getHttpResponse()); + assertEquals(AuthErrorCode.UNAUTHORIZED_CONTINUE_URL, e.getAuthErrorCode()); + } + } + @Test public void testOidcProviderConfigLifecycle() throws Exception { // Create provider config From 8fe1544372faa884058639d53a36744a04777832 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 19:09:36 +0000 Subject: [PATCH 255/269] chore(deps): Bump netty.version from 4.2.3.Final to 4.2.4.Final (#1123) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1a7c778f2..28a127857 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.2.3.Final + 4.2.4.Final From a9ff3f7332a731e5d63d6f2d33c7ddb0372583bb Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Wed, 3 Sep 2025 16:06:58 -0400 Subject: [PATCH 256/269] [chore] Release 9.6.0 (#1125) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 28a127857..b353743ee 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 9.5.0 + 9.6.0 jar firebase-admin From 353fc53740244ffc5b3f3b496d4b6d351c3f805a Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Wed, 3 Sep 2025 17:18:16 -0400 Subject: [PATCH 257/269] Revert "[chore] Release 9.6.0 (#1125)" (#1126) This reverts commit a9ff3f7332a731e5d63d6f2d33c7ddb0372583bb. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b353743ee..28a127857 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 9.6.0 + 9.5.0 jar firebase-admin From 1b5cf37f068fdb9778de54c44744e698235cfc5b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 13:39:39 -0400 Subject: [PATCH 258/269] chore(deps): Bump io.netty:netty-codec-http (#1127) Bumps [io.netty:netty-codec-http](https://github.com/netty/netty) from 4.2.4.Final to 4.2.5.Final. - [Commits](https://github.com/netty/netty/compare/netty-4.2.4.Final...netty-4.2.5.Final) --- updated-dependencies: - dependency-name: io.netty:netty-codec-http dependency-version: 4.2.5.Final dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 28a127857..990e4d6dc 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.2.4.Final + 4.2.5.Final From 304a274fadf44792503e50bbbdd9774acc0e6026 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Fri, 5 Sep 2025 12:25:44 -0400 Subject: [PATCH 259/269] feat: Migrate to Central Publisher Portal (#1128) Updates the release process to use the new Central Publisher Portal for deploying artifacts to Maven Central. - Replaces the nexus-staging-maven-plugin with the central-publishing-maven-plugin. - Updates the distributionManagement repository URL in pom.xml. - Modifies the publish script to use the central-publishing:publish goal. - Updates the release workflow to use token-based authentication secrets (CENTRAL_USERNAME, CENTRAL_TOKEN). - Adjusts settings.xml to use the new authentication tokens. --- .github/resources/settings.xml | 6 +++--- .github/scripts/publish_artifacts.sh | 2 +- .github/workflows/release.yml | 4 ++-- pom.xml | 19 ++++++------------- 4 files changed, 12 insertions(+), 19 deletions(-) diff --git a/.github/resources/settings.xml b/.github/resources/settings.xml index f3bc754e2..da1d3057d 100644 --- a/.github/resources/settings.xml +++ b/.github/resources/settings.xml @@ -7,9 +7,9 @@ - ossrh - ${env.NEXUS_OSSRH_USERNAME} - ${env.NEXUS_OSSRH_PASSWORD} + central + ${env.CENTRAL_USERNAME} + ${env.CENTRAL_TOKEN} diff --git a/.github/scripts/publish_artifacts.sh b/.github/scripts/publish_artifacts.sh index cd1a5b75c..8e20af88f 100755 --- a/.github/scripts/publish_artifacts.sh +++ b/.github/scripts/publish_artifacts.sh @@ -26,7 +26,7 @@ gpg --import --no-tty --batch --yes firebase.asc # 1. Compiles the source (compile phase) # 2. Packages the artifacts - src, bin, javadocs (package phase) # 3. Signs the artifacts (verify phase) -# 4. Publishes artifacts via Nexus (deploy phase) +# 4. Publishes artifacts via Central Publisher Portal (deploy phase) mvn -B clean deploy \ -Dcheckstyle.skip \ -DskipTests \ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d6f744c69..86774e50c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -100,8 +100,8 @@ jobs: env: GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - NEXUS_OSSRH_USERNAME: ${{ secrets.NEXUS_OSSRH_USERNAME }} - NEXUS_OSSRH_PASSWORD: ${{ secrets.NEXUS_OSSRH_PASSWORD }} + CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }} + CENTRAL_TOKEN: ${{ secrets.CENTRAL_TOKEN }} # See: https://cli.github.com/manual/gh_release_create - name: Create release tag diff --git a/pom.xml b/pom.xml index 990e4d6dc..ff3a085f3 100644 --- a/pom.xml +++ b/pom.xml @@ -69,13 +69,6 @@ HEAD - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - devsite-apidocs @@ -350,14 +343,14 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.7.0 + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 true - ossrh - https://oss.sonatype.org/ - true + central + true + published From 450fe3cd979d60d067a8d89cbc2530f703ac77a5 Mon Sep 17 00:00:00 2001 From: Jonathan Edey <145066863+jonathanedey@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:03:19 -0400 Subject: [PATCH 260/269] [chore] Release 9.6.0 Take 2 (#1130) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ff3a085f3..91c203218 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 9.5.0 + 9.6.0 jar firebase-admin From 026db9fec98ca1f66a9b16be0a37ac4e127a024e Mon Sep 17 00:00:00 2001 From: varun rathore <35365856+rathovarun1032@users.noreply.github.com> Date: Tue, 23 Sep 2025 20:48:26 +0530 Subject: [PATCH 261/269] feat(rc): Add support for Server-Side Remote Config (#1122) * Implementation for Fetching and Caching Server Side Remote Config (#1107)009 * Implementation for Fetching and Caching Server Side Remote Config * implementation of fetch , cache and load of template --------- Co-authored-by: Varun Rathore * Implement custom signal targeting for server side RC (#1108) Co-authored-by: Athira M * Implement percent evaluation for server side RC (#1114) * [feat] Implement percent evaluation for server side RC * Ssrc bugbash fix (#1117) * Handle empty context * fix issue related to update time * fix string equality * fix textcase * Fix lint errors * Add unit tests * fix for [438426692](getDouble() logs a malformed warning on type conversion failure) Using getDouble on a string parameter value, returns the appropriate default static value but logs a warning which looks incorrect ("%s" in the warning message?). * Update ServerTemplateResponse.java to fix b/438607881 In the server template builder flow using cached template, evaluation using custom signals is not working as intended. * Update getServerRemoteConfig.json to fix b/438607881 * Update getServerTemplateData.json to fix b/438607881 * fix for bugs * Resolve comment related to revert of ServerVersion Class * remove serverVersion * Resolve comments related to Evaluator * fix indentation * fix indentation * fix indentations * fix multi line indent * fix multi line indents * Update ConditionEvaluator.java * Update ConditionEvaluator.java --------- Co-authored-by: Athira M Co-authored-by: Varun Rathore * Create ParameterValueTest.java * Fix typo errors * Change return type and cache regex * Addressed comment to make cache atomic * Trigger CI --------- Co-authored-by: Varun Rathore Co-authored-by: Athira M Co-authored-by: Athira M --- .../firebase/remoteconfig/AndCondition.java | 61 ++ .../remoteconfig/ConditionEvaluator.java | 346 ++++++++ .../remoteconfig/CustomSignalCondition.java | 140 +++ .../remoteconfig/CustomSignalOperator.java | 54 ++ .../remoteconfig/FirebaseRemoteConfig.java | 68 ++ .../FirebaseRemoteConfigClient.java | 3 + .../FirebaseRemoteConfigClientImpl.java | 24 + .../firebase/remoteconfig/KeysAndValues.java | 135 +++ .../remoteconfig/MicroPercentRange.java | 46 + .../firebase/remoteconfig/OneOfCondition.java | 140 +++ .../firebase/remoteconfig/OrCondition.java | 58 ++ .../remoteconfig/PercentCondition.java | 163 ++++ .../PercentConditionOperator.java | 55 ++ .../remoteconfig/ServerCondition.java | 90 ++ .../firebase/remoteconfig/ServerConfig.java | 102 +++ .../firebase/remoteconfig/ServerTemplate.java | 45 + .../remoteconfig/ServerTemplateData.java | 215 +++++ .../remoteconfig/ServerTemplateImpl.java | 195 ++++ .../google/firebase/remoteconfig/Value.java | 135 +++ .../firebase/remoteconfig/ValueSource.java | 31 + .../internal/ServerTemplateResponse.java | 322 +++++++ .../remoteconfig/ConditionEvaluatorTest.java | 832 ++++++++++++++++++ .../FirebaseRemoteConfigClientImplTest.java | 823 ++++++++++++----- .../FirebaseRemoteConfigTest.java | 157 +++- .../remoteconfig/MockRemoteConfigClient.java | 25 +- .../remoteconfig/ServerConditionTest.java | 218 +++++ .../remoteconfig/ServerTemplateImplTest.java | 421 +++++++++ .../firebase/remoteconfig/ValueTest.java | 104 +++ .../firebase/remoteconfig/VersionTest.java | 12 +- src/test/resources/getServerRemoteConfig.json | 142 +++ src/test/resources/getServerTemplateData.json | 181 ++++ 31 files changed, 5094 insertions(+), 249 deletions(-) create mode 100644 src/main/java/com/google/firebase/remoteconfig/AndCondition.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/OrCondition.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/PercentCondition.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ServerCondition.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ServerConfig.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ServerTemplateImpl.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/Value.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ValueSource.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java create mode 100644 src/test/java/com/google/firebase/remoteconfig/ConditionEvaluatorTest.java create mode 100644 src/test/java/com/google/firebase/remoteconfig/ServerConditionTest.java create mode 100644 src/test/java/com/google/firebase/remoteconfig/ServerTemplateImplTest.java create mode 100644 src/test/java/com/google/firebase/remoteconfig/ValueTest.java create mode 100644 src/test/resources/getServerRemoteConfig.json create mode 100644 src/test/resources/getServerTemplateData.json diff --git a/src/main/java/com/google/firebase/remoteconfig/AndCondition.java b/src/main/java/com/google/firebase/remoteconfig/AndCondition.java new file mode 100644 index 000000000..ec118cef2 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/AndCondition.java @@ -0,0 +1,61 @@ +/* + * Copyright 2025 Google LLC + * + * 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.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import com.google.firebase.internal.NonNull; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.AndConditionResponse; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.OneOfConditionResponse; + +import java.util.List; +import java.util.stream.Collectors; + +final class AndCondition { + private final ImmutableList conditions; + + AndCondition(@NonNull List conditions) { + checkNotNull(conditions, "List of conditions for AND operation must not be null."); + checkArgument(!conditions.isEmpty(), + "List of conditions for AND operation must not be empty."); + this.conditions = ImmutableList.copyOf(conditions); + } + + AndCondition(AndConditionResponse andConditionResponse) { + List conditionList = andConditionResponse.getConditions(); + checkNotNull(conditionList, "List of conditions for AND operation must not be null."); + checkArgument(!conditionList.isEmpty(), + "List of conditions for AND operation must not be empty"); + this.conditions = conditionList.stream() + .map(OneOfCondition::new) + .collect(ImmutableList.toImmutableList()); + } + + @NonNull + ImmutableList getConditions() { + return conditions; + } + + AndConditionResponse toAndConditionResponse() { + return new AndConditionResponse() + .setConditions(this.conditions.stream() + .map(OneOfCondition::toOneOfConditionResponse) + .collect(Collectors.toList())); + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java b/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java new file mode 100644 index 000000000..63718b32b --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ConditionEvaluator.java @@ -0,0 +1,346 @@ +/* + * Copyright 2025 Google LLC + * + * 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.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableMap.toImmutableMap; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.firebase.internal.NonNull; +import com.google.firebase.internal.Nullable; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.BiPredicate; +import java.util.function.IntPredicate; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class ConditionEvaluator { + private static final int MAX_CONDITION_RECURSION_DEPTH = 10; + private static final Logger logger = LoggerFactory.getLogger(ConditionEvaluator.class); + private static final BigInteger MICRO_PERCENT_MODULO = BigInteger.valueOf(100_000_000L); + private static final Pattern SEMVER_PATTERN = Pattern.compile("^[0-9]+(?:\\.[0-9]+){0,4}$"); + + /** + * Evaluates server conditions and assigns a boolean value to each condition. + * + * @param conditions List of conditions which are to be evaluated. + * @param context A map with additional metadata used during evaluation. + * @return A map of condition to evaluated value. + */ + @NonNull + Map evaluateConditions( + @NonNull List conditions, @Nullable KeysAndValues context) { + checkNotNull(conditions, "List of conditions must not be null."); + checkArgument(!conditions.isEmpty(), "List of conditions must not be empty."); + if (context == null || conditions.isEmpty()) { + return ImmutableMap.of(); + } + KeysAndValues evaluationContext = + context != null ? context : new KeysAndValues.Builder().build(); + + Map evaluatedConditions = + conditions.stream() + .collect( + toImmutableMap( + ServerCondition::getName, + condition -> + evaluateCondition( + condition.getCondition(), evaluationContext, /* nestingLevel= */ 0))); + + return evaluatedConditions; + } + + private boolean evaluateCondition( + OneOfCondition condition, KeysAndValues context, int nestingLevel) { + if (nestingLevel > MAX_CONDITION_RECURSION_DEPTH) { + logger.warn("Maximum condition recursion depth exceeded."); + return false; + } + + if (condition.getOrCondition() != null) { + return evaluateOrCondition(condition.getOrCondition(), context, nestingLevel + 1); + } else if (condition.getAndCondition() != null) { + return evaluateAndCondition(condition.getAndCondition(), context, nestingLevel + 1); + } else if (condition.isTrue() != null) { + return true; + } else if (condition.isFalse() != null) { + return false; + } else if (condition.getCustomSignal() != null) { + return evaluateCustomSignalCondition(condition.getCustomSignal(), context); + } else if (condition.getPercent() != null) { + return evaluatePercentCondition(condition.getPercent(), context); + } + logger.atWarn().log("Received invalid condition for evaluation."); + return false; + } + + private boolean evaluateOrCondition( + OrCondition condition, KeysAndValues context, int nestingLevel) { + return condition.getConditions().stream() + .anyMatch(subCondition -> evaluateCondition(subCondition, context, nestingLevel + 1)); + } + + private boolean evaluateAndCondition( + AndCondition condition, KeysAndValues context, int nestingLevel) { + return condition.getConditions().stream() + .allMatch(subCondition -> evaluateCondition(subCondition, context, nestingLevel + 1)); + } + + private boolean evaluateCustomSignalCondition( + CustomSignalCondition condition, KeysAndValues context) { + CustomSignalOperator customSignalOperator = condition.getCustomSignalOperator(); + String customSignalKey = condition.getCustomSignalKey(); + ImmutableList targetCustomSignalValues = + ImmutableList.copyOf(condition.getTargetCustomSignalValues()); + + if (targetCustomSignalValues.isEmpty()) { + logger.warn( + String.format( + "Values must be assigned to all custom signal fields. Operator:%s, Key:%s, Values:%s", + customSignalOperator, customSignalKey, targetCustomSignalValues)); + return false; + } + + String customSignalValue = context.get(customSignalKey); + if (customSignalValue == null) { + return false; + } + + switch (customSignalOperator) { + // String operations. + case STRING_CONTAINS: + return compareStrings( + targetCustomSignalValues, + customSignalValue, + (customSignal, targetSignal) -> customSignal.contains(targetSignal)); + case STRING_DOES_NOT_CONTAIN: + return !compareStrings( + targetCustomSignalValues, + customSignalValue, + (customSignal, targetSignal) -> customSignal.contains(targetSignal)); + case STRING_EXACTLY_MATCHES: + return compareStrings( + targetCustomSignalValues, + customSignalValue, + (customSignal, targetSignal) -> customSignal.equals(targetSignal)); + case STRING_CONTAINS_REGEX: + return compareStrings( + targetCustomSignalValues, + customSignalValue, + (customSignal, targetSignal) -> compareStringRegex(customSignal, targetSignal)); + + // Numeric operations. + case NUMERIC_LESS_THAN: + return compareNumbers(targetCustomSignalValues, customSignalValue, (result) -> result < 0); + case NUMERIC_LESS_EQUAL: + return compareNumbers(targetCustomSignalValues, customSignalValue, (result) -> result <= 0); + case NUMERIC_EQUAL: + return compareNumbers(targetCustomSignalValues, customSignalValue, (result) -> result == 0); + case NUMERIC_NOT_EQUAL: + return compareNumbers(targetCustomSignalValues, customSignalValue, (result) -> result != 0); + case NUMERIC_GREATER_THAN: + return compareNumbers(targetCustomSignalValues, customSignalValue, (result) -> result > 0); + case NUMERIC_GREATER_EQUAL: + return compareNumbers(targetCustomSignalValues, customSignalValue, (result) -> result >= 0); + + // Semantic operations. + case SEMANTIC_VERSION_EQUAL: + return compareSemanticVersions( + targetCustomSignalValues, customSignalValue, (result) -> result == 0); + case SEMANTIC_VERSION_GREATER_EQUAL: + return compareSemanticVersions( + targetCustomSignalValues, customSignalValue, (result) -> result >= 0); + case SEMANTIC_VERSION_GREATER_THAN: + return compareSemanticVersions( + targetCustomSignalValues, customSignalValue, (result) -> result > 0); + case SEMANTIC_VERSION_LESS_EQUAL: + return compareSemanticVersions( + targetCustomSignalValues, customSignalValue, (result) -> result <= 0); + case SEMANTIC_VERSION_LESS_THAN: + return compareSemanticVersions( + targetCustomSignalValues, customSignalValue, (result) -> result < 0); + case SEMANTIC_VERSION_NOT_EQUAL: + return compareSemanticVersions( + targetCustomSignalValues, customSignalValue, (result) -> result != 0); + default: + return false; + } + } + + private boolean evaluatePercentCondition(PercentCondition condition, KeysAndValues context) { + if (!context.containsKey("randomizationId")) { + logger.warn("Percentage operation must not be performed without randomizationId"); + return false; + } + + PercentConditionOperator operator = condition.getPercentConditionOperator(); + + // The micro-percent interval to be used with the BETWEEN operator. + MicroPercentRange microPercentRange = condition.getMicroPercentRange(); + int microPercentUpperBound = + microPercentRange != null ? microPercentRange.getMicroPercentUpperBound() : 0; + int microPercentLowerBound = + microPercentRange != null ? microPercentRange.getMicroPercentLowerBound() : 0; + // The limit of percentiles to target in micro-percents when using the + // LESS_OR_EQUAL and GREATER_THAN operators. The value must be in the range [0 + // and 100000000]. + int microPercent = condition.getMicroPercent(); + BigInteger microPercentile = + getMicroPercentile(condition.getSeed(), context.get("randomizationId")); + switch (operator) { + case LESS_OR_EQUAL: + return microPercentile.compareTo(BigInteger.valueOf(microPercent)) <= 0; + case GREATER_THAN: + return microPercentile.compareTo(BigInteger.valueOf(microPercent)) > 0; + case BETWEEN: + return microPercentile.compareTo(BigInteger.valueOf(microPercentLowerBound)) > 0 + && microPercentile.compareTo(BigInteger.valueOf(microPercentUpperBound)) <= 0; + case UNSPECIFIED: + default: + return false; + } + } + + private BigInteger getMicroPercentile(String seed, String randomizationId) { + String seedPrefix = seed != null && !seed.isEmpty() ? seed + "." : ""; + String stringToHash = seedPrefix + randomizationId; + BigInteger hash = hashSeededRandomizationId(stringToHash); + BigInteger microPercentile = hash.mod(MICRO_PERCENT_MODULO); + + return microPercentile; + } + + private BigInteger hashSeededRandomizationId(String seededRandomizationId) { + try { + // Create a SHA-256 hash. + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hashBytes = digest.digest(seededRandomizationId.getBytes(StandardCharsets.UTF_8)); + + // Convert the hash bytes to a BigInteger + return new BigInteger(1, hashBytes); + } catch (NoSuchAlgorithmException e) { + logger.error("SHA-256 algorithm not found", e); + throw new RuntimeException("SHA-256 algorithm not found", e); + } + } + + private boolean compareStrings( + ImmutableList targetValues, + String customSignal, + BiPredicate compareFunction) { + return targetValues.stream() + .anyMatch(targetValue -> compareFunction.test(customSignal, targetValue)); + } + + private boolean compareStringRegex(String customSignal, String targetSignal) { + try { + return Pattern.compile(targetSignal).matcher(customSignal).matches(); + } catch (PatternSyntaxException e) { + return false; + } + } + + private boolean compareNumbers( + ImmutableList targetValues, String customSignal, IntPredicate compareFunction) { + if (targetValues.size() != 1) { + logger.warn( + String.format( + "Target values must contain 1 element for numeric operations. Target Value: %s", + targetValues)); + return false; + } + + try { + double customSignalDouble = Double.parseDouble(customSignal); + double targetValue = Double.parseDouble(targetValues.get(0)); + int comparisonResult = Double.compare(customSignalDouble, targetValue); + return compareFunction.test(comparisonResult); + } catch (NumberFormatException e) { + logger.warn( + "Error parsing numeric values: customSignal=%s, targetValue=%s", + customSignal, targetValues.get(0), e); + return false; + } + } + + private boolean compareSemanticVersions( + ImmutableList targetValues, String customSignal, IntPredicate compareFunction) { + if (targetValues.size() != 1) { + logger.warn(String.format("Target values must contain 1 element for semantic operation.")); + return false; + } + + String targetValueString = targetValues.get(0); + if (!validateSemanticVersion(targetValueString) || !validateSemanticVersion(customSignal)) { + return false; + } + + List targetVersion = parseSemanticVersion(targetValueString); + List customSignalVersion = parseSemanticVersion(customSignal); + + int maxLength = 5; + if (targetVersion.size() > maxLength || customSignalVersion.size() > maxLength) { + logger.warn( + "Semantic version max length(%s) exceeded. Target: %s, Custom Signal: %s", + maxLength, targetValueString, customSignal); + return false; + } + + int comparison = compareSemanticVersions(customSignalVersion, targetVersion); + return compareFunction.test(comparison); + } + + private int compareSemanticVersions(List version1, List version2) { + int maxLength = Math.max(version1.size(), version2.size()); + int version1Size = version1.size(); + int version2Size = version2.size(); + + for (int i = 0; i < maxLength; i++) { + // Default to 0 if segment is missing + int v1 = i < version1Size ? version1.get(i) : 0; + int v2 = i < version2Size ? version2.get(i) : 0; + + int comparison = Integer.compare(v1, v2); + if (comparison != 0) { + return comparison; + } + } + // Versions are equal + return 0; + } + + private List parseSemanticVersion(String versionString) { + return Arrays.stream(versionString.split("\\.")) + .map(Integer::parseInt) + .collect(Collectors.toList()); + } + + private boolean validateSemanticVersion(String version) { + return SEMVER_PATTERN.matcher(version).matches(); + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java b/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java new file mode 100644 index 000000000..a8d96efdf --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/CustomSignalCondition.java @@ -0,0 +1,140 @@ +/* + * Copyright 2025 Google LLC + * + * 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.remoteconfig; + +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.common.collect.ImmutableList; +import com.google.firebase.internal.NonNull; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.CustomSignalConditionResponse; + +import java.util.ArrayList; +import java.util.List; + +final class CustomSignalCondition { + private final String customSignalKey; + private final CustomSignalOperator customSignalOperator; + private final ImmutableList targetCustomSignalValues; + + public CustomSignalCondition( + @NonNull String customSignalKey, + @NonNull CustomSignalOperator customSignalOperator, + @NonNull List targetCustomSignalValues) { + checkArgument( + !Strings.isNullOrEmpty(customSignalKey), "Custom signal key must not be null or empty."); + checkNotNull(customSignalOperator); + checkNotNull(targetCustomSignalValues); + checkArgument( + !targetCustomSignalValues.isEmpty(), "Target custom signal values must not be empty."); + this.customSignalKey = customSignalKey.trim(); + this.customSignalOperator = customSignalOperator; + this.targetCustomSignalValues = ImmutableList.copyOf(targetCustomSignalValues); + } + + CustomSignalCondition(CustomSignalConditionResponse customSignalCondition) { + checkArgument( + !Strings.isNullOrEmpty(customSignalCondition.getKey()), + "Custom signal key must not be null or empty."); + checkArgument( + !customSignalCondition.getTargetValues().isEmpty(), + "Target custom signal values must not be empty."); + this.customSignalKey = customSignalCondition.getKey().trim(); + List targetCustomSignalValuesList = customSignalCondition.getTargetValues(); + this.targetCustomSignalValues = ImmutableList.copyOf(targetCustomSignalValuesList); + switch (customSignalCondition.getOperator()) { + case "NUMERIC_EQUAL": + this.customSignalOperator = CustomSignalOperator.NUMERIC_EQUAL; + break; + case "NUMERIC_GREATER_EQUAL": + this.customSignalOperator = CustomSignalOperator.NUMERIC_GREATER_EQUAL; + break; + case "NUMERIC_GREATER_THAN": + this.customSignalOperator = CustomSignalOperator.NUMERIC_GREATER_THAN; + break; + case "NUMERIC_LESS_EQUAL": + this.customSignalOperator = CustomSignalOperator.NUMERIC_LESS_EQUAL; + break; + case "NUMERIC_LESS_THAN": + this.customSignalOperator = CustomSignalOperator.NUMERIC_LESS_THAN; + break; + case "NUMERIC_NOT_EQUAL": + this.customSignalOperator = CustomSignalOperator.NUMERIC_NOT_EQUAL; + break; + case "SEMANTIC_VERSION_EQUAL": + this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_EQUAL; + break; + case "SEMANTIC_VERSION_GREATER_EQUAL": + this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_GREATER_EQUAL; + break; + case "SEMANTIC_VERSION_GREATER_THAN": + this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_GREATER_THAN; + break; + case "SEMANTIC_VERSION_LESS_EQUAL": + this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_LESS_EQUAL; + break; + case "SEMANTIC_VERSION_LESS_THAN": + this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_LESS_THAN; + break; + case "SEMANTIC_VERSION_NOT_EQUAL": + this.customSignalOperator = CustomSignalOperator.SEMANTIC_VERSION_NOT_EQUAL; + break; + case "STRING_CONTAINS": + this.customSignalOperator = CustomSignalOperator.STRING_CONTAINS; + break; + case "STRING_CONTAINS_REGEX": + this.customSignalOperator = CustomSignalOperator.STRING_CONTAINS_REGEX; + break; + case "STRING_DOES_NOT_CONTAIN": + this.customSignalOperator = CustomSignalOperator.STRING_DOES_NOT_CONTAIN; + break; + case "STRING_EXACTLY_MATCHES": + this.customSignalOperator = CustomSignalOperator.STRING_EXACTLY_MATCHES; + break; + default: + this.customSignalOperator = CustomSignalOperator.UNSPECIFIED; + } + checkArgument( + this.customSignalOperator != CustomSignalOperator.UNSPECIFIED, + "Custom signal operator passed is invalid"); + } + + @NonNull + String getCustomSignalKey() { + return customSignalKey; + } + + @NonNull + CustomSignalOperator getCustomSignalOperator() { + return customSignalOperator; + } + + @NonNull + List getTargetCustomSignalValues() { + return new ArrayList<>(targetCustomSignalValues); + } + + CustomSignalConditionResponse toCustomConditonResponse() { + CustomSignalConditionResponse customSignalConditionResponse = + new CustomSignalConditionResponse(); + customSignalConditionResponse.setKey(this.customSignalKey); + customSignalConditionResponse.setOperator(this.customSignalOperator.getOperator()); + customSignalConditionResponse.setTargetValues(this.targetCustomSignalValues); + return customSignalConditionResponse; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java b/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java new file mode 100644 index 000000000..0c0924d62 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/CustomSignalOperator.java @@ -0,0 +1,54 @@ +/* + * Copyright 2025 Google LLC + * + * 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.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.Strings; +import com.google.firebase.internal.NonNull; + +enum CustomSignalOperator { + NUMERIC_EQUAL("NUMERIC_EQUAL"), + NUMERIC_GREATER_EQUAL("NUMERIC_GREATER_EQUAL"), + NUMERIC_GREATER_THAN("NUMERIC_GREATER_THAN"), + NUMERIC_LESS_EQUAL("NUMERIC_LESS_EQUAL"), + NUMERIC_LESS_THAN("NUMERIC_LESS_THAN"), + NUMERIC_NOT_EQUAL("NUMERIC_NOT_EQUAL"), + SEMANTIC_VERSION_EQUAL("SEMANTIC_VERSION_EQUAL"), + SEMANTIC_VERSION_GREATER_EQUAL("SEMANTIC_VERSION_GREATER_EQUAL"), + SEMANTIC_VERSION_GREATER_THAN("SEMANTIC_VERSION_GREATER_THAN"), + SEMANTIC_VERSION_LESS_EQUAL("SEMANTIC_VERSION_LESS_EQUAL"), + SEMANTIC_VERSION_LESS_THAN("SEMANTIC_VERSION_LESS_THAN"), + SEMANTIC_VERSION_NOT_EQUAL("SEMANTIC_VERSION_NOT_EQUAL"), + STRING_CONTAINS("STRING_CONTAINS"), + STRING_CONTAINS_REGEX("STRING_CONTAINS_REGEX"), + STRING_DOES_NOT_CONTAIN("STRING_DOES_NOT_CONTAIN"), + STRING_EXACTLY_MATCHES("STRING_EXACTLY_MATCHES"), + UNSPECIFIED("CUSTOM_SIGNAL_OPERATOR_UNSPECIFIED"); + + private final String operator; + + CustomSignalOperator(@NonNull String operator) { + checkArgument(!Strings.isNullOrEmpty(operator), "Operator must not be null or empty."); + this.operator = operator; + } + + @NonNull + String getOperator() { + return operator; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java index 41a0afbe4..e3edce4e4 100644 --- a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java +++ b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java @@ -101,6 +101,73 @@ protected Template execute() throws FirebaseRemoteConfigException { }; } + /** + * Alternative to {@link #getServerTemplate} where developers can initialize with a pre-cached + * template or config. + */ + public ServerTemplateImpl.Builder serverTemplateBuilder() { + return new ServerTemplateImpl.Builder(this.remoteConfigClient); + } + + /** + * Initializes a template instance and loads the latest template data. + * + * @param defaultConfig Default parameter values to use if a getter references a parameter not + * found in the template. + * @return A {@link Template} instance with the latest template data. + */ + public ServerTemplate getServerTemplate(KeysAndValues defaultConfig) + throws FirebaseRemoteConfigException { + return getServerTemplateOp(defaultConfig).call(); + } + + /** + * Initializes a template instance without any defaults and loads the latest template data. + * + * @return A {@link Template} instance with the latest template data. + */ + public ServerTemplate getServerTemplate() throws FirebaseRemoteConfigException { + return getServerTemplate(null); + } + + /** + * Initializes a template instance and asynchronously loads the latest template data. + * + * @param defaultConfig Default parameter values to use if a getter references a parameter not + * found in the template. + * @return A {@link Template} instance with the latest template data. + */ + public ApiFuture getServerTemplateAsync(KeysAndValues defaultConfig) { + return getServerTemplateOp(defaultConfig).callAsync(app); + } + + /** + * Initializes a template instance without any defaults and asynchronously loads the latest + * template data. + * + * @return A {@link Template} instance with the latest template data. + */ + public ApiFuture getServerTemplateAsync() { + return getServerTemplateAsync(null); + } + + private CallableOperation getServerTemplateOp( + KeysAndValues defaultConfig) { + return new CallableOperation() { + @Override + protected ServerTemplate execute() throws FirebaseRemoteConfigException { + String serverTemplateData = remoteConfigClient.getServerTemplate(); + ServerTemplate template = + serverTemplateBuilder() + .defaultConfig(defaultConfig) + .cachedTemplate(serverTemplateData) + .build(); + + return template; + } + }; + } + /** * Gets the requested version of the of the Remote Config template. * @@ -413,3 +480,4 @@ public void destroy() { } } } + diff --git a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClient.java b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClient.java index 9fdb596d6..2143d07d1 100644 --- a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClient.java +++ b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClient.java @@ -40,4 +40,7 @@ Template publishTemplate(Template template, boolean validateOnly, ListVersionsResponse listVersions( ListVersionsOptions options) throws FirebaseRemoteConfigException; + + String getServerTemplate() throws FirebaseRemoteConfigException; } + diff --git a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java index 7425673fb..d84abae84 100644 --- a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java +++ b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java @@ -38,6 +38,7 @@ import com.google.firebase.internal.NonNull; import com.google.firebase.internal.SdkUtils; import com.google.firebase.remoteconfig.internal.RemoteConfigServiceErrorResponse; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse; import com.google.firebase.remoteconfig.internal.TemplateResponse; import java.io.IOException; @@ -51,6 +52,9 @@ final class FirebaseRemoteConfigClientImpl implements FirebaseRemoteConfigClient private static final String REMOTE_CONFIG_URL = "https://firebaseremoteconfig.googleapis.com/v1/projects/%s/remoteConfig"; + private static final String SERVER_REMOTE_CONFIG_URL = + "https://firebaseremoteconfig.googleapis.com/v1/projects/%s/namespaces/firebase-server/serverRemoteConfig"; + private static final Map COMMON_HEADERS = ImmutableMap.of( "X-Firebase-Client", "fire-admin-java/" + SdkUtils.getVersion(), @@ -62,6 +66,7 @@ final class FirebaseRemoteConfigClientImpl implements FirebaseRemoteConfigClient ); private final String remoteConfigUrl; + private final String serverRemoteConfigUrl; private final HttpRequestFactory requestFactory; private final JsonFactory jsonFactory; private final ErrorHandlingHttpClient httpClient; @@ -69,6 +74,7 @@ final class FirebaseRemoteConfigClientImpl implements FirebaseRemoteConfigClient private FirebaseRemoteConfigClientImpl(Builder builder) { checkArgument(!Strings.isNullOrEmpty(builder.projectId)); this.remoteConfigUrl = String.format(REMOTE_CONFIG_URL, builder.projectId); + this.serverRemoteConfigUrl = String.format(SERVER_REMOTE_CONFIG_URL, builder.projectId); this.requestFactory = checkNotNull(builder.requestFactory); this.jsonFactory = checkNotNull(builder.jsonFactory); HttpResponseInterceptor responseInterceptor = builder.responseInterceptor; @@ -82,6 +88,11 @@ String getRemoteConfigUrl() { return remoteConfigUrl; } + @VisibleForTesting + String getServerRemoteConfigUrl() { + return serverRemoteConfigUrl; + } + @VisibleForTesting HttpRequestFactory getRequestFactory() { return requestFactory; @@ -102,6 +113,18 @@ public Template getTemplate() throws FirebaseRemoteConfigException { return template.setETag(getETag(response)); } + @Override + public String getServerTemplate() throws FirebaseRemoteConfigException { + HttpRequestInfo request = + HttpRequestInfo.buildGetRequest(serverRemoteConfigUrl).addAllHeaders(COMMON_HEADERS); + IncomingHttpResponse response = httpClient.send(request); + ServerTemplateResponse templateResponse = httpClient.parse(response, + ServerTemplateResponse.class); + ServerTemplateData serverTemplateData = new ServerTemplateData(templateResponse); + serverTemplateData.setETag(getETag(response)); + return serverTemplateData.toJSON(); + } + @Override public Template getTemplateAtVersion( @NonNull String versionNumber) throws FirebaseRemoteConfigException { @@ -267,3 +290,4 @@ private RemoteConfigServiceErrorResponse safeParse(String response) { } } } + diff --git a/src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java b/src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java new file mode 100644 index 000000000..47b159bf7 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/KeysAndValues.java @@ -0,0 +1,135 @@ +/* + * Copyright 2025 Google LLC + * + * 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.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; +import com.google.firebase.internal.NonNull; + +import java.util.HashMap; +import java.util.Map; + +/** + * Represents data stored in context passed to server-side Remote Config. + */ +public class KeysAndValues { + final ImmutableMap keysAndValues; + + private KeysAndValues(@NonNull Builder builder) { + keysAndValues = ImmutableMap.builder().putAll(builder.keysAndValues).build(); + } + + /** + * Checks whether a key is present in the context. + * + * @param key The key for data stored in context. + * @return Boolean representing whether the key passed is present in context. + */ + public boolean containsKey(String key) { + return keysAndValues.containsKey(key); + } + + /** + * Gets the value of the data stored in context. + * + * @param key The key for data stored in context. + * @return Value assigned to the key in context. + */ + public String get(String key) { + return keysAndValues.get(key); + } + + /** + * Builder class for KeysAndValues using which values will be assigned to + * private variables. + */ + public static class Builder { + // Holds the converted pairs of custom keys and values. + private final Map keysAndValues = new HashMap<>(); + + /** + * Adds a context data with string value. + * + * @param key Identifies the value in context. + * @param value Value assigned to the context. + * @return Reference to class itself so that more data can be added. + */ + @NonNull + public Builder put(@NonNull String key, @NonNull String value) { + checkArgument(!Strings.isNullOrEmpty(key), "Context key must not be null or empty."); + checkArgument(!Strings.isNullOrEmpty(value), "Context key must not be null or empty."); + keysAndValues.put(key, value); + return this; + } + + /** + * Adds a context data with boolean value. + * + * @param key Identifies the value in context. + * @param value Value assigned to the context. + * @return Reference to class itself so that more data can be added. + */ + @NonNull + public Builder put(@NonNull String key, boolean value) { + checkArgument(!Strings.isNullOrEmpty(key), "Context key must not be null or empty."); + keysAndValues.put(key, Boolean.toString(value)); + return this; + } + + /** + * Adds a context data with double value. + * + * @param key Identifies the value in context. + * @param value Value assigned to the context. + * @return Reference to class itself so that more data can be added. + */ + @NonNull + public Builder put(@NonNull String key, double value) { + checkArgument(!Strings.isNullOrEmpty(key), "Context key must not be null or empty."); + keysAndValues.put(key, Double.toString(value)); + return this; + } + + /** + * Adds a context data with long value. + * + * @param key Identifies the value in context. + * @param value Value assigned to the context. + * @return Reference to class itself so that more data can be added. + */ + @NonNull + public Builder put(@NonNull String key, long value) { + checkArgument(!Strings.isNullOrEmpty(key), "Context key must not be null or empty."); + keysAndValues.put(key, Long.toString(value)); + return this; + } + + /** + * Creates an instance of KeysAndValues with the values assigned through + * builder. + * + * @return instance of KeysAndValues + */ + @NonNull + public KeysAndValues build() { + return new KeysAndValues(this); + } + } +} + diff --git a/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java b/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java new file mode 100644 index 000000000..abd5711c6 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/MicroPercentRange.java @@ -0,0 +1,46 @@ +/* +* Copyright 2025 Google LLC +* +* 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.remoteconfig; + +import com.google.firebase.internal.Nullable; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.MicroPercentRangeResponse; + +class MicroPercentRange { + private final int microPercentLowerBound; + private final int microPercentUpperBound; + + public MicroPercentRange(@Nullable Integer microPercentLowerBound, + @Nullable Integer microPercentUpperBound) { + this.microPercentLowerBound = microPercentLowerBound != null ? microPercentLowerBound : 0; + this.microPercentUpperBound = microPercentUpperBound != null ? microPercentUpperBound : 0; + } + + int getMicroPercentLowerBound() { + return microPercentLowerBound; + } + + int getMicroPercentUpperBound() { + return microPercentUpperBound; + } + + MicroPercentRangeResponse toMicroPercentRangeResponse() { + MicroPercentRangeResponse microPercentRangeResponse = new MicroPercentRangeResponse(); + microPercentRangeResponse.setMicroPercentLowerBound(this.microPercentLowerBound); + microPercentRangeResponse.setMicroPercentUpperBound(this.microPercentUpperBound); + return microPercentRangeResponse; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java b/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java new file mode 100644 index 000000000..be3f5fd3e --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/OneOfCondition.java @@ -0,0 +1,140 @@ +/* + * Copyright 2025 Google LLC + * + * 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.remoteconfig; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.VisibleForTesting; +import com.google.firebase.internal.NonNull; +import com.google.firebase.internal.Nullable; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.OneOfConditionResponse; + +class OneOfCondition { + private OrCondition orCondition; + private AndCondition andCondition; + private PercentCondition percent; + private CustomSignalCondition customSignal; + private String trueValue; + private String falseValue; + + OneOfCondition(OneOfConditionResponse oneOfconditionResponse) { + if (oneOfconditionResponse.getOrCondition() != null) { + this.orCondition = new OrCondition(oneOfconditionResponse.getOrCondition()); + } + if (oneOfconditionResponse.getAndCondition() != null) { + this.andCondition = new AndCondition(oneOfconditionResponse.getAndCondition()); + } + if (oneOfconditionResponse.getPercentCondition() != null) { + this.percent = new PercentCondition(oneOfconditionResponse.getPercentCondition()); + } + if (oneOfconditionResponse.getCustomSignalCondition() != null) { + this.customSignal = + new CustomSignalCondition(oneOfconditionResponse.getCustomSignalCondition()); + } + } + + @VisibleForTesting + OneOfCondition() { + this.orCondition = null; + this.andCondition = null; + this.percent = null; + this.trueValue = null; + this.falseValue = null; + } + + @Nullable + OrCondition getOrCondition() { + return orCondition; + } + + @Nullable + AndCondition getAndCondition() { + return andCondition; + } + + @Nullable + String isTrue() { + return trueValue; + } + + @Nullable + String isFalse() { + return falseValue; + } + + @Nullable + PercentCondition getPercent() { + return percent; + } + + @Nullable + CustomSignalCondition getCustomSignal() { + return customSignal; + } + + OneOfCondition setOrCondition(@NonNull OrCondition orCondition) { + checkNotNull(orCondition, "`Or` condition cannot be set to null."); + this.orCondition = orCondition; + return this; + } + + OneOfCondition setAndCondition(@NonNull AndCondition andCondition) { + checkNotNull(andCondition, "`And` condition cannot be set to null."); + this.andCondition = andCondition; + return this; + } + + OneOfCondition setPercent(@NonNull PercentCondition percent) { + checkNotNull(percent, "`Percent` condition cannot be set to null."); + this.percent = percent; + return this; + } + + OneOfCondition setCustomSignal(@NonNull CustomSignalCondition customSignal) { + checkNotNull(customSignal, "`Custom signal` condition cannot be set to null."); + this.customSignal = customSignal; + return this; + } + + OneOfCondition setTrue() { + this.trueValue = "true"; + return this; + } + + OneOfCondition setFalse() { + this.falseValue = "false"; + return this; + } + + OneOfConditionResponse toOneOfConditionResponse() { + OneOfConditionResponse oneOfConditionResponse = new OneOfConditionResponse(); + if (this.andCondition != null) { + oneOfConditionResponse.setAndCondition(this.andCondition.toAndConditionResponse()); + } + if (this.orCondition != null) { + oneOfConditionResponse.setOrCondition(this.orCondition.toOrConditionResponse()); + } + if (this.customSignal != null) { + oneOfConditionResponse.setCustomSignalCondition(this.customSignal.toCustomConditonResponse()); + } + if (this.percent != null) { + oneOfConditionResponse.setPercentCondition(this.percent.toPercentConditionResponse()); + } + return oneOfConditionResponse; + } +} + diff --git a/src/main/java/com/google/firebase/remoteconfig/OrCondition.java b/src/main/java/com/google/firebase/remoteconfig/OrCondition.java new file mode 100644 index 000000000..36a1c682a --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/OrCondition.java @@ -0,0 +1,58 @@ +/* + * Copyright 2025 Google LLC + * + * 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.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import com.google.firebase.internal.NonNull; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.OneOfConditionResponse; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.OrConditionResponse; +import java.util.List; +import java.util.stream.Collectors; + +final class OrCondition { + private final ImmutableList conditions; + + public OrCondition(@NonNull List conditions) { + checkNotNull(conditions, "List of conditions for OR operation must not be null."); + checkArgument(!conditions.isEmpty(), "List of conditions for OR operation must not be empty."); + this.conditions = ImmutableList.copyOf(conditions); + } + + OrCondition(OrConditionResponse orConditionResponse) { + List conditionList = orConditionResponse.getConditions(); + checkNotNull(conditionList, "List of conditions for AND operation cannot be null."); + checkArgument(!conditionList.isEmpty(), "List of conditions for AND operation cannot be empty"); + this.conditions = conditionList.stream() + .map(OneOfCondition::new) + .collect(ImmutableList.toImmutableList()); + } + + @NonNull + ImmutableList getConditions() { + return conditions; + } + + OrConditionResponse toOrConditionResponse() { + return new OrConditionResponse() + .setConditions(this.conditions.stream() + .map(OneOfCondition::toOneOfConditionResponse) + .collect(Collectors.toList())); + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java b/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java new file mode 100644 index 000000000..c5763200b --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/PercentCondition.java @@ -0,0 +1,163 @@ +/* +* Copyright 2025 Google LLC +* +* 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.remoteconfig; + +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; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.PercentConditionResponse; + +/** Represents a condition that compares the instance pseudo-random percentile to a given limit. */ +public final class PercentCondition { + private int microPercent; + private MicroPercentRange microPercentRange; + private final PercentConditionOperator percentConditionOperator; + private final String seed; + + /** + * Create a percent condition for operator BETWEEN. + * + * @param microPercent The limit of percentiles to target in micro-percents when using the + * LESS_OR_EQUAL and GREATER_THAN operators. The value must be in the range [0 and 100000000]. + * @param percentConditionOperator The choice of percent operator to determine how to compare + * targets to percent(s). + * @param seed The seed used when evaluating the hash function to map an instance to a value in + * the hash space. This is a string which can have 0 - 32 characters and can contain ASCII + * characters [-_.0-9a-zA-Z].The string is case-sensitive. + */ + PercentCondition( + @Nullable Integer microPercent, + @NonNull PercentConditionOperator percentConditionOperator, + @NonNull String seed) { + checkNotNull(percentConditionOperator, "Percentage operator must not be null."); + checkArgument(!Strings.isNullOrEmpty(seed), "Seed must not be null or empty."); + this.microPercent = microPercent != null ? microPercent : 0; + this.percentConditionOperator = percentConditionOperator; + this.seed = seed; + } + + /** + * Create a percent condition for operators GREATER_THAN and LESS_OR_EQUAL. + * + * @param microPercentRange The micro-percent interval to be used with the BETWEEN operator. + * @param percentConditionOperator The choice of percent operator to determine how to compare + * targets to percent(s). + * @param seed The seed used when evaluating the hash function to map an instance to a value in + * the hash space. This is a string which can have 0 - 32 characters and can contain ASCII + * characters [-_.0-9a-zA-Z].The string is case-sensitive. + */ + PercentCondition( + @NonNull MicroPercentRange microPercentRange, + @NonNull PercentConditionOperator percentConditionOperator, + String seed) { + checkNotNull(microPercentRange, "Percent range must not be null."); + checkNotNull(percentConditionOperator, "Percentage operator must not be null."); + this.microPercentRange = microPercentRange; + this.percentConditionOperator = percentConditionOperator; + this.seed = seed; + } + + /** + * Creates a new {@link PercentCondition} from API response. + * + * @param percentCondition the conditions obtained from server call. + */ + PercentCondition(PercentConditionResponse percentCondition) { + checkArgument( + !Strings.isNullOrEmpty(percentCondition.getSeed()), "Seed must not be empty or null"); + this.microPercent = percentCondition.getMicroPercent(); + this.seed = percentCondition.getSeed(); + switch (percentCondition.getPercentOperator()) { + case "BETWEEN": + this.percentConditionOperator = PercentConditionOperator.BETWEEN; + break; + case "GREATER_THAN": + this.percentConditionOperator = PercentConditionOperator.GREATER_THAN; + break; + case "LESS_OR_EQUAL": + this.percentConditionOperator = PercentConditionOperator.LESS_OR_EQUAL; + break; + default: + this.percentConditionOperator = PercentConditionOperator.UNSPECIFIED; + } + checkArgument( + this.percentConditionOperator != PercentConditionOperator.UNSPECIFIED, + "Percentage operator is invalid"); + if (percentCondition.getMicroPercentRange() != null) { + this.microPercentRange = + new MicroPercentRange( + percentCondition.getMicroPercentRange().getMicroPercentLowerBound(), + percentCondition.getMicroPercentRange().getMicroPercentUpperBound()); + } + } + + /** + * Gets the limit of percentiles to target in micro-percents when using the LESS_OR_EQUAL and + * GREATER_THAN operators. The value must be in the range [0 and 100000000]. + * + * @return micro percent. + */ + @Nullable + public int getMicroPercent() { + return microPercent; + } + + /** + * Gets micro-percent interval to be used with the BETWEEN operator. + * + * @return micro percent range. + */ + @Nullable + public MicroPercentRange getMicroPercentRange() { + return microPercentRange; + } + + /** + * Gets choice of percent operator to determine how to compare targets to percent(s). + * + * @return operator. + */ + @NonNull + public PercentConditionOperator getPercentConditionOperator() { + return percentConditionOperator; + } + + /** + * The seed used when evaluating the hash function to map an instance to a value in the hash + * space. This is a string which can have 0 - 32 characters and can contain ASCII characters + * [-_.0-9a-zA-Z].The string is case-sensitive. + * + * @return seed. + */ + @NonNull + public String getSeed() { + return seed; + } + + PercentConditionResponse toPercentConditionResponse() { + PercentConditionResponse percentConditionResponse = new PercentConditionResponse(); + percentConditionResponse.setMicroPercent(this.microPercent); + percentConditionResponse.setMicroPercentRange( + this.microPercentRange.toMicroPercentRangeResponse()); + percentConditionResponse.setPercentOperator(this.percentConditionOperator.getOperator()); + percentConditionResponse.setSeed(this.seed); + return percentConditionResponse; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java b/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java new file mode 100644 index 000000000..478f13e4e --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/PercentConditionOperator.java @@ -0,0 +1,55 @@ +/* +* Copyright 2025 Google LLC +* +* 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.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.Strings; +import com.google.firebase.internal.NonNull; + +/** +* Defines supported operators for percent conditions. +*/ +public enum PercentConditionOperator { + BETWEEN("BETWEEN"), + GREATER_THAN("GREATER_THAN"), + LESS_OR_EQUAL("LESS_OR_EQUAL"), + UNSPECIFIED("PERCENT_OPERATOR_UNSPECIFIED"); + + private final String operator; + + /** + * Creates percent condition operator. + * + * @param operator The choice of percent operator to determine how to compare targets to + * percent(s). + */ + PercentConditionOperator(@NonNull String operator) { + checkArgument(!Strings.isNullOrEmpty(operator), "Operator must not be null or empty."); + this.operator = operator; + } + + /** + * Gets percent condition operator. + * + * @return operator. + */ + @NonNull + public String getOperator() { + return operator; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerCondition.java b/src/main/java/com/google/firebase/remoteconfig/ServerCondition.java new file mode 100644 index 000000000..f16aeffc8 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ServerCondition.java @@ -0,0 +1,90 @@ +/* + * Copyright 2025 Google LLC + * + * 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.remoteconfig; + +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.remoteconfig.internal.ServerTemplateResponse.ServerConditionResponse; + +import java.util.Objects; + +final class ServerCondition { + + private String name; + private OneOfCondition serverCondition; + + ServerCondition(@NonNull String name, @NonNull OneOfCondition condition) { + checkArgument(!Strings.isNullOrEmpty(name), "condition name must not be null or empty"); + this.name = name; + this.serverCondition = condition; + } + + ServerCondition(@NonNull ServerConditionResponse serverConditionResponse) { + checkNotNull(serverConditionResponse); + this.name = serverConditionResponse.getName(); + this.serverCondition = new OneOfCondition(serverConditionResponse.getServerCondition()); + } + + @NonNull + String getName() { + return name; + } + + @NonNull + OneOfCondition getCondition() { + return serverCondition; + } + + ServerCondition setName(@NonNull String name) { + checkArgument(!Strings.isNullOrEmpty(name), "condition name must not be null or empty"); + this.name = name; + return this; + } + + ServerCondition setServerCondition(@NonNull OneOfCondition condition) { + checkNotNull(condition, "condition must not be null or empty"); + this.serverCondition = condition; + return this; + } + + ServerConditionResponse toServerConditionResponse() { + return new ServerConditionResponse().setName(this.name) + .setServerCondition(this.serverCondition.toOneOfConditionResponse()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ServerCondition condition = (ServerCondition) o; + return Objects.equals(name, condition.name) + && Objects.equals(serverCondition, condition.serverCondition); + } + + @Override + public int hashCode() { + return Objects.hash(name, serverCondition); + } +} + diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java b/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java new file mode 100644 index 000000000..8540578b0 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ServerConfig.java @@ -0,0 +1,102 @@ +/* + * Copyright 2025 Google LLC + * + * 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.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.Strings; +import com.google.firebase.internal.NonNull; + +import java.util.Map; + +/** + * Represents the configuration produced by evaluating a server template. + */ +public final class ServerConfig { + private final Map configValues; + + ServerConfig(Map configValues) { + this.configValues = configValues; + } + + /** + * Gets the value for the given key as a string. Convenience method for calling + * serverConfig.getValue(key).asString(). + * + * @param key The name of the parameter. + * @return config value for the given key as string. + */ + @NonNull + public String getString(@NonNull String key) { + return this.getValue(key).asString(); + } + + /** + * Gets the value for the given key as a boolean.Convenience method for calling + * serverConfig.getValue(key).asBoolean(). + * + * @param key The name of the parameter. + * @return config value for the given key as boolean. + */ + @NonNull + public boolean getBoolean(@NonNull String key) { + return this.getValue(key).asBoolean(); + } + + /** + * Gets the value for the given key as long.Convenience method for calling + * serverConfig.getValue(key).asLong(). + * + * @param key The name of the parameter. + * @return config value for the given key as long. + */ + @NonNull + public long getLong(@NonNull String key) { + return this.getValue(key).asLong(); + } + + /** + * Gets the value for the given key as double.Convenience method for calling + * serverConfig.getValue(key).asDouble(). + * + * @param key The name of the parameter. + * @return config value for the given key as double. + */ + @NonNull + public double getDouble(@NonNull String key) { + return this.getValue(key).asDouble(); + } + + /** + * Gets the {@link ValueSource} for the given key. + * + * @param key The name of the parameter. + * @return config value source for the given key. + */ + @NonNull + public ValueSource getValueSource(@NonNull String key) { + return this.getValue(key).getSource(); + } + + private Value getValue(String key) { + checkArgument(!Strings.isNullOrEmpty(key), "Server config key cannot be null or empty."); + if (configValues.containsKey(key)) { + return configValues.get(key); + } + return new Value(ValueSource.STATIC); + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java new file mode 100644 index 000000000..bd1a59940 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplate.java @@ -0,0 +1,45 @@ +/* + * Copyright 2025 Google LLC + * + * 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.remoteconfig; + +import com.google.api.core.ApiFuture; + +public interface ServerTemplate { + public interface Builder { + + Builder defaultConfig(KeysAndValues config); + + Builder cachedTemplate(String templateJson); + + ServerTemplate build(); + } + /** + * Process the template data with a condition evaluator + * based on the provided context. + */ + ServerConfig evaluate(KeysAndValues context) throws FirebaseRemoteConfigException; + /** + * Process the template data without context. + */ + ServerConfig evaluate() throws FirebaseRemoteConfigException; + /** + * Fetches and caches the current active version of the project. + */ + ApiFuture load() throws FirebaseRemoteConfigException; + + String toJson(); +} diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java new file mode 100644 index 000000000..59d51b51a --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateData.java @@ -0,0 +1,215 @@ +/* + * Copyright 2025 Google LLC + * + * 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.remoteconfig; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.api.client.json.JsonFactory; +import com.google.common.base.Strings; +import com.google.firebase.ErrorCode; +import com.google.firebase.internal.ApiClientUtils; +import com.google.firebase.internal.NonNull; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse; +import com.google.firebase.remoteconfig.internal.TemplateResponse; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +final class ServerTemplateData { + + private String etag; + private Map parameters; + private List serverConditions; + private Map parameterGroups; + private Version version; + + + ServerTemplateData(String etag) { + this.parameters = new HashMap<>(); + this.serverConditions = new ArrayList<>(); + this.parameterGroups = new HashMap<>(); + this.etag = etag; + } + + ServerTemplateData() { + this((String) null); + } + + ServerTemplateData(@NonNull ServerTemplateResponse serverTemplateResponse) { + checkNotNull(serverTemplateResponse); + this.parameters = new HashMap<>(); + this.serverConditions = new ArrayList<>(); + this.parameterGroups = new HashMap<>(); + if (serverTemplateResponse.getParameters() != null) { + for (Map.Entry entry : + serverTemplateResponse.getParameters().entrySet()) { + this.parameters.put(entry.getKey(), new Parameter(entry.getValue())); + } + } + if (serverTemplateResponse.getServerConditions() != null) { + for (ServerTemplateResponse.ServerConditionResponse conditionResponse : + serverTemplateResponse.getServerConditions()) { + this.serverConditions.add(new ServerCondition(conditionResponse)); + } + } + if (serverTemplateResponse.getParameterGroups() != null) { + for (Map.Entry entry : + serverTemplateResponse.getParameterGroups().entrySet()) { + this.parameterGroups.put(entry.getKey(), new ParameterGroup(entry.getValue())); + } + } + if (serverTemplateResponse.getVersion() != null) { + this.version = new Version(serverTemplateResponse.getVersion()); + } + this.etag = serverTemplateResponse.getEtag(); + } + + + static ServerTemplateData fromJSON(@NonNull String json) + throws FirebaseRemoteConfigException { + checkArgument(!Strings.isNullOrEmpty(json), "JSON String must not be null or empty."); + // using the default json factory as no rpc calls are made here + JsonFactory jsonFactory = ApiClientUtils.getDefaultJsonFactory(); + try { + ServerTemplateResponse serverTemplateResponse = + jsonFactory.createJsonParser(json).parseAndClose(ServerTemplateResponse.class); + return new ServerTemplateData(serverTemplateResponse); + } catch (IOException e) { + throw new FirebaseRemoteConfigException( + ErrorCode.INVALID_ARGUMENT, "Unable to parse JSON string."); + } + } + + + String getETag() { + return this.etag; + } + + + @NonNull + public Map getParameters() { + return this.parameters; + } + + @NonNull + List getServerConditions() { + return serverConditions; + } + + @NonNull + Map getParameterGroups() { + return parameterGroups; + } + + Version getVersion() { + return version; + } + + ServerTemplateData setParameters(@NonNull Map parameters) { + checkNotNull(parameters, "parameters must not be null."); + this.parameters = parameters; + return this; + } + + + ServerTemplateData setServerConditions(@NonNull List conditions) { + checkNotNull(conditions, "conditions must not be null."); + this.serverConditions = conditions; + return this; + } + + ServerTemplateData setParameterGroups( + @NonNull Map parameterGroups) { + checkNotNull(parameterGroups, "parameter groups must not be null."); + this.parameterGroups = parameterGroups; + return this; + } + + ServerTemplateData setVersion(Version version) { + this.version = version; + return this; + } + + String toJSON() { + JsonFactory jsonFactory = ApiClientUtils.getDefaultJsonFactory(); + try { + return jsonFactory.toString(this.toServerTemplateResponse(true)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + ServerTemplateData setETag(String etag) { + this.etag = etag; + return this; + } + + ServerTemplateResponse toServerTemplateResponse(boolean includeAll) { + Map parameterResponses = new HashMap<>(); + for (Map.Entry entry : this.parameters.entrySet()) { + parameterResponses.put(entry.getKey(), entry.getValue().toParameterResponse()); + } + List serverConditionResponses = + new ArrayList<>(); + for (ServerCondition condition : this.serverConditions) { + serverConditionResponses.add(condition.toServerConditionResponse()); + } + Map parameterGroupResponse = new HashMap<>(); + for (Map.Entry entry : this.parameterGroups.entrySet()) { + parameterGroupResponse.put(entry.getKey(), entry.getValue().toParameterGroupResponse()); + } + TemplateResponse.VersionResponse versionResponse = + (this.version == null) ? null : this.version.toVersionResponse(includeAll); + ServerTemplateResponse serverTemplateResponse = + new ServerTemplateResponse() + .setParameters(parameterResponses) + .setServerConditions(serverConditionResponses) + .setParameterGroups(parameterGroupResponse) + .setVersion(versionResponse); + if (includeAll) { + return serverTemplateResponse.setEtag(this.etag); + } + return serverTemplateResponse; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ServerTemplateData template = (ServerTemplateData) o; + return Objects.equals(etag, template.etag) + && Objects.equals(parameters, template.parameters) + && Objects.equals(serverConditions, template.serverConditions) + && Objects.equals(parameterGroups, template.parameterGroups) + && Objects.equals(version, template.version); + } + + @Override + public int hashCode() { + return Objects.hash(etag, parameters, serverConditions, parameterGroups, version); + } +} + diff --git a/src/main/java/com/google/firebase/remoteconfig/ServerTemplateImpl.java b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateImpl.java new file mode 100644 index 000000000..742c19803 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ServerTemplateImpl.java @@ -0,0 +1,195 @@ +/* + * Copyright 2025 Google LLC + * + * 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.remoteconfig; + +import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutures; +import com.google.common.collect.ImmutableMap; +import com.google.firebase.ErrorCode; +import com.google.firebase.internal.Nullable; +import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterValueResponse; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class ServerTemplateImpl implements ServerTemplate { + + private final KeysAndValues defaultConfig; + private FirebaseRemoteConfigClient client; + private AtomicReference cache; + private final AtomicReference cachedTemplate; + private static final Logger logger = LoggerFactory.getLogger(ServerTemplate.class); + + public static class Builder implements ServerTemplate.Builder { + private KeysAndValues defaultConfig; + private String cachedTemplate; + private FirebaseRemoteConfigClient client; + + Builder(FirebaseRemoteConfigClient remoteConfigClient) { + this.client = remoteConfigClient; + } + + @Override + public Builder defaultConfig(KeysAndValues config) { + this.defaultConfig = config; + return this; + } + + @Override + public Builder cachedTemplate(String templateJson) { + this.cachedTemplate = templateJson; + return this; + } + + @Override + public ServerTemplate build() { + return new ServerTemplateImpl(this); + } + } + + private ServerTemplateImpl(Builder builder) { + this.defaultConfig = builder.defaultConfig; + this.cachedTemplate = new AtomicReference<>(builder.cachedTemplate); + this.client = builder.client; + this.cache = new AtomicReference<>(null); + + String initialTemplate = this.cachedTemplate.get(); + try { + this.cache.set(ServerTemplateData.fromJSON(initialTemplate)); + } catch (FirebaseRemoteConfigException e) { + e.printStackTrace(); + } + } + + @Override + public ServerConfig evaluate(@Nullable KeysAndValues context) + throws FirebaseRemoteConfigException { + ServerTemplateData cachedData = this.cache.get(); + if (cachedData == null) { + throw new FirebaseRemoteConfigException(ErrorCode.FAILED_PRECONDITION, + "No Remote Config Server template in cache. Call load() before calling evaluate()."); + } + + Map configValues = new HashMap<>(); + ImmutableMap defaultConfigValues = defaultConfig.keysAndValues; + // Initializes configValue objects with default values. + for (String configName : defaultConfigValues.keySet()) { + configValues.put(configName, new Value(ValueSource.DEFAULT, + defaultConfigValues.get(configName))); + } + + ConditionEvaluator conditionEvaluator = new ConditionEvaluator(); + ImmutableMap evaluatedCondition = ImmutableMap.copyOf( + conditionEvaluator.evaluateConditions(cachedData.getServerConditions(), context)); + ImmutableMap parameters = ImmutableMap.copyOf(cachedData.getParameters()); + mergeDerivedConfigValues(evaluatedCondition, parameters, configValues); + + return new ServerConfig(configValues); + } + + @Override + public ServerConfig evaluate() throws FirebaseRemoteConfigException { + return evaluate(null); + } + + @Override + public ApiFuture load() throws FirebaseRemoteConfigException { + String serverTemplate = client.getServerTemplate(); + this.cachedTemplate.set(serverTemplate); + this.cache.set(ServerTemplateData.fromJSON(serverTemplate)); + return ApiFutures.immediateFuture(null); + } + + // Add getters or other methods as needed + public KeysAndValues getDefaultConfig() { + return defaultConfig; + } + + public String getCachedTemplate() { + return cachedTemplate.get(); + } + + @Override + public String toJson() { + ServerTemplateData currentCache = this.cache.get(); + if (currentCache == null) { + return "{}"; + } + return currentCache.toJSON(); + } + + private void mergeDerivedConfigValues(ImmutableMap evaluatedCondition, + ImmutableMap parameters, Map configValues) { + for (String parameterName : parameters.keySet()) { + Parameter parameter = parameters.get(parameterName); + if (parameter == null) { + logger.warn(String.format("Parameter value is not assigned for %s", parameterName)); + continue; + } + + ImmutableMap conditionalValues = ImmutableMap.copyOf( + parameter.getConditionalValues()); + ParameterValue derivedValue = null; + + // Iterates in order over condition list. If there is a value associated + // with a condition, this checks if the condition is true. + for (String conditionName : evaluatedCondition.keySet()) { + boolean conditionEvaluation = evaluatedCondition.get(conditionName); + if (conditionalValues.containsKey(conditionName) && conditionEvaluation) { + derivedValue = conditionalValues.get(conditionName); + break; + } + } + + if (derivedValue != null && derivedValue.toParameterValueResponse().isUseInAppDefault()) { + logger.warn( + String.format("Derived value found for %s but parameter is set to use in app default.", + parameterName)); + continue; + } + + if (derivedValue != null) { + String parameterValue = derivedValue.toParameterValueResponse().getValue(); + Value value = new Value(ValueSource.REMOTE, parameterValue); + configValues.put(parameterName, value); + continue; + } + + ParameterValue defaultValue = parameter.getDefaultValue(); + if (defaultValue == null) { + logger.warn(String.format("Default parameter value for %s is not set.", + parameterName)); + continue; + } + + ParameterValueResponse defaultValueResponse = defaultValue.toParameterValueResponse(); + if (defaultValueResponse != null && defaultValueResponse.isUseInAppDefault()) { + logger.info(String.format("Default value for %s is set to use in app default.", + parameterName)); + continue; + } + + String parameterDefaultValue = defaultValue.toParameterValueResponse().getValue(); + Value value = new Value(ValueSource.REMOTE, parameterDefaultValue); + configValues.put(parameterName, value); + } + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/Value.java b/src/main/java/com/google/firebase/remoteconfig/Value.java new file mode 100644 index 000000000..7935e8491 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/Value.java @@ -0,0 +1,135 @@ +/* + * Copyright 2025 Google LLC + * + * 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.remoteconfig; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import com.google.firebase.internal.NonNull; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Wraps a parameter value with metadata and type-safe getters. Type-safe + * getters insulate application logic from remote changes to parameter names and + * types. + */ +class Value { + private static final Logger logger = LoggerFactory.getLogger(Value.class); + private static final boolean DEFAULT_VALUE_FOR_BOOLEAN = false; + private static final String DEFAULT_VALUE_FOR_STRING = ""; + private static final long DEFAULT_VALUE_FOR_LONG = 0; + private static final double DEFAULT_VALUE_FOR_DOUBLE = 0; + private static final ImmutableList BOOLEAN_TRUTHY_VALUES = ImmutableList.of("1", "true", + "t", "yes", "y", "on"); + + private final ValueSource source; + private final String value; + + /** + * Creates a new {@link Value} object. + * + * @param source Indicates the source of a value. + * @param value Indicates a parameter value. + */ + Value(@NonNull ValueSource source, String value) { + checkNotNull(source, "Value source cannot be null."); + this.source = source; + this.value = value; + } + + /** + * Creates a new {@link Value} object with default value. + * + * @param source Indicates the source of a value. + */ + Value(@NonNull ValueSource source) { + this(source, DEFAULT_VALUE_FOR_STRING); + } + + /** + * Gets the value as a string. + * + * @return value as string + */ + @NonNull + String asString() { + return this.value; + } + + /** + * Gets the value as a boolean.The following values (case + * insensitive) are interpreted as true: "1", "true", "t", "yes", "y", "on". + * Other values are interpreted as false. + * + * @return value as boolean + */ + @NonNull + boolean asBoolean() { + if (source == ValueSource.STATIC) { + return DEFAULT_VALUE_FOR_BOOLEAN; + } + return BOOLEAN_TRUTHY_VALUES.contains(value.toLowerCase()); + } + + /** + * Gets the value as long. Comparable to calling Number(value) || 0. + * + * @return value as long + */ + @NonNull + long asLong() { + if (source == ValueSource.STATIC) { + return DEFAULT_VALUE_FOR_LONG; + } + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + logger.warn("Unable to convert {} to long type.", value); + return DEFAULT_VALUE_FOR_LONG; + } + } + + /** + * Gets the value as double. Comparable to calling Number(value) || 0. + * + * @return value as double + */ + @NonNull + double asDouble() { + if (source == ValueSource.STATIC) { + return DEFAULT_VALUE_FOR_DOUBLE; + } + try { + return Double.parseDouble(this.value); + } catch (NumberFormatException e) { + logger.warn("Unable to convert {} to double type.", value); + return DEFAULT_VALUE_FOR_DOUBLE; + } + } + + /** + * Gets the {@link ValueSource} for the given key. + * + * @return source. + */ + @NonNull + ValueSource getSource() { + return source; + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/ValueSource.java b/src/main/java/com/google/firebase/remoteconfig/ValueSource.java new file mode 100644 index 000000000..c870e8514 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/ValueSource.java @@ -0,0 +1,31 @@ + +/* + * Copyright 2025 Google LLC + * + * 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.remoteconfig; + +/** + * Indicates the source of a value. + * "static" indicates the value was defined by a static constant. + * "default" indicates the value was defined by default config. + * "remote" indicates the value was defined by config produced by evaluating a template. + */ +public enum ValueSource { + STATIC, + REMOTE, + DEFAULT +} + diff --git a/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java b/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java new file mode 100644 index 000000000..db3785afe --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/internal/ServerTemplateResponse.java @@ -0,0 +1,322 @@ +/* + * Copyright 2025 Google LLC + * + * 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.remoteconfig.internal; + +import com.google.api.client.util.Key; +import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterGroupResponse; +import com.google.firebase.remoteconfig.internal.TemplateResponse.ParameterResponse; +import com.google.firebase.remoteconfig.internal.TemplateResponse.VersionResponse; + +import java.util.List; +import java.util.Map; + +/** + * The Data Transfer Object for parsing Remote Config template responses from + * the Remote Config + * service. + */ +public final class ServerTemplateResponse { + @Key("parameters") + private Map parameters; + + @Key("conditions") + private List serverConditions; + + @Key("parameterGroups") + private Map parameterGroups; + + @Key("version") + private VersionResponse version; + + // For local JSON serialization and deserialization purposes only. + // ETag in response type is never set by the HTTP response. + @Key("etag") + private String etag; + + public Map getParameters() { + return parameters; + } + + public List getServerConditions() { + return serverConditions; + } + + public Map getParameterGroups() { + return parameterGroups; + } + + public VersionResponse getVersion() { + return version; + } + + public String getEtag() { + return etag; + } + + public ServerTemplateResponse setParameters(Map parameters) { + this.parameters = parameters; + return this; + } + + public ServerTemplateResponse setServerConditions( + List serverConditions) { + this.serverConditions = serverConditions; + return this; + } + + public ServerTemplateResponse setParameterGroups( + Map parameterGroups) { + this.parameterGroups = parameterGroups; + return this; + } + + public ServerTemplateResponse setVersion(VersionResponse version) { + this.version = version; + return this; + } + + public ServerTemplateResponse setEtag(String etag) { + this.etag = etag; + return this; + } + + /** + * The Data Transfer Object for parsing Remote Config condition responses from + * the Remote Config + * service. + */ + public static final class ServerConditionResponse { + + @Key("name") + private String name; + + @Key("condition") + private OneOfConditionResponse condition; + + public String getName() { + return name; + } + + public OneOfConditionResponse getServerCondition() { + return condition; + } + + public ServerConditionResponse setName(String name) { + this.name = name; + return this; + } + + public ServerConditionResponse setServerCondition(OneOfConditionResponse condition) { + this.condition = condition; + return this; + } + } + + public static final class OneOfConditionResponse { + @Key("orCondition") + private OrConditionResponse orCondition; + + @Key("andCondition") + private AndConditionResponse andCondition; + + @Key("customSignal") + private CustomSignalConditionResponse customSignalCondition; + + @Key("percent") + private PercentConditionResponse percentCondition; + + public OrConditionResponse getOrCondition() { + return orCondition; + } + + public AndConditionResponse getAndCondition() { + return andCondition; + } + + public PercentConditionResponse getPercentCondition() { + return percentCondition; + } + + public CustomSignalConditionResponse getCustomSignalCondition() { + return customSignalCondition; + } + + public OneOfConditionResponse setOrCondition(OrConditionResponse orCondition) { + this.orCondition = orCondition; + return this; + } + + public OneOfConditionResponse setAndCondition(AndConditionResponse andCondition) { + this.andCondition = andCondition; + return this; + } + + public OneOfConditionResponse setCustomSignalCondition( + CustomSignalConditionResponse customSignalCondition) { + this.customSignalCondition = customSignalCondition; + return this; + } + + public OneOfConditionResponse setPercentCondition(PercentConditionResponse percentCondition) { + this.percentCondition = percentCondition; + return this; + } + } + + public static final class OrConditionResponse { + @Key("conditions") + private List conditions; + + public List getConditions() { + return conditions; + } + + public OrConditionResponse setConditions(List conditions) { + this.conditions = conditions; + return this; + } + } + + public static final class AndConditionResponse { + @Key("conditions") + private List conditions; + + public List getConditions() { + return conditions; + } + + public AndConditionResponse setConditions(List conditions) { + this.conditions = conditions; + return this; + } + } + + public static final class CustomSignalConditionResponse { + @Key("customSignalOperator") + private String operator; + + @Key("customSignalKey") + private String key; + + @Key("targetCustomSignalValues") + private List targetValues; + + public String getOperator() { + return operator; + } + + public String getKey() { + return key; + } + + public List getTargetValues() { + return targetValues; + } + + public CustomSignalConditionResponse setOperator(String operator) { + this.operator = operator; + return this; + } + + public CustomSignalConditionResponse setKey(String key) { + this.key = key; + return this; + } + + public CustomSignalConditionResponse setTargetValues(List targetValues) { + this.targetValues = targetValues; + return this; + } + } + + public static final class PercentConditionResponse { + @Key("microPercent") + private int microPercent; + + @Key("microPercentRange") + private MicroPercentRangeResponse microPercentRange; + + @Key("percentOperator") + private String percentOperator; + + @Key("seed") + private String seed; + + public int getMicroPercent() { + return microPercent; + } + + public MicroPercentRangeResponse getMicroPercentRange() { + return microPercentRange; + } + + public String getPercentOperator() { + return percentOperator; + } + + public String getSeed() { + return seed; + } + + public PercentConditionResponse setMicroPercent(int microPercent) { + this.microPercent = microPercent; + return this; + } + + public PercentConditionResponse setMicroPercentRange( + MicroPercentRangeResponse microPercentRange) { + this.microPercentRange = microPercentRange; + return this; + } + + public PercentConditionResponse setPercentOperator(String percentOperator) { + this.percentOperator = percentOperator; + return this; + } + + public PercentConditionResponse setSeed(String seed) { + this.seed = seed; + return this; + } + } + + public static final class MicroPercentRangeResponse { + @Key("microPercentLowerBound") + private int microPercentLowerBound; + + @Key("microPercentUpperBound") + private int microPercentUpperBound; + + public int getMicroPercentLowerBound() { + return microPercentLowerBound; + } + + public int getMicroPercentUpperBound() { + return microPercentUpperBound; + } + + public MicroPercentRangeResponse setMicroPercentLowerBound(int microPercentLowerBound) { + this.microPercentLowerBound = microPercentLowerBound; + return this; + } + + public MicroPercentRangeResponse setMicroPercentUpperBound(int microPercentUpperBound) { + this.microPercentUpperBound = microPercentUpperBound; + return this; + } + } +} diff --git a/src/test/java/com/google/firebase/remoteconfig/ConditionEvaluatorTest.java b/src/test/java/com/google/firebase/remoteconfig/ConditionEvaluatorTest.java new file mode 100644 index 000000000..0cd6e4528 --- /dev/null +++ b/src/test/java/com/google/firebase/remoteconfig/ConditionEvaluatorTest.java @@ -0,0 +1,832 @@ +/* + * Copyright 2025 Google LLC + * + * 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.remoteconfig; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.ImmutableList; + +import java.util.Arrays; +import java.util.Map; +import java.util.UUID; + +import org.junit.Test; + +public class ConditionEvaluatorTest { + + private final ConditionEvaluator conditionEvaluator = new ConditionEvaluator(); + + @Test + public void testEvaluateConditionsEmptyOrConditionThrowsException() { + IllegalArgumentException error = assertThrows(IllegalArgumentException.class, + () -> createOneOfOrCondition(null)); + assertEquals("List of conditions for OR operation must not be empty.", error.getMessage()); + } + + @Test + public void testEvaluateConditionsEmptyOrAndConditionThrowsException() { + IllegalArgumentException error = assertThrows(IllegalArgumentException.class, + () -> createOneOfAndCondition(null)); + assertEquals("List of conditions for AND operation must not be empty.", error.getMessage()); + } + + @Test + public void testEvaluateConditionsOrAndTrueToTrue() { + OneOfCondition oneOfConditionTrue = createOneOfTrueCondition(); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionTrue); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues context = new KeysAndValues.Builder().build(); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + context); + + assertTrue(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsOrAndFalseToFalse() { + OneOfCondition oneOfConditionFalse = createOneOfFalseCondition(); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionFalse); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues context = new KeysAndValues.Builder().build(); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + context); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsNonOrTopConditionToTrue() { + OneOfCondition oneOfConditionTrue = createOneOfTrueCondition(); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionTrue); + KeysAndValues context = new KeysAndValues.Builder().build(); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + context); + + assertTrue(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsPercentConditionWithInvalidOperatorToFalse() { + OneOfCondition oneOfConditionPercent = createPercentCondition(0, + PercentConditionOperator.UNSPECIFIED, "seed"); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionPercent); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "abc"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsPercentConditionLessOrEqualMaxToTrue() { + OneOfCondition oneOfConditionPercent = createPercentCondition(10_000_0000, + PercentConditionOperator.LESS_OR_EQUAL, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsPercentConditionLessOrEqualMinToFalse() { + OneOfCondition oneOfConditionPercent = createPercentCondition(0, + PercentConditionOperator.LESS_OR_EQUAL, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsPercentConditionUndefinedMicroPercentToFalse() { + OneOfCondition oneOfConditionPercent = createPercentCondition(null, + PercentConditionOperator.LESS_OR_EQUAL, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsUseZeroForUndefinedPercentRange() { + OneOfCondition oneOfConditionPercent = createBetweenPercentCondition(null, null, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsUseZeroForUndefinedUpperBound() { + OneOfCondition oneOfConditionPercent = createBetweenPercentCondition(0, null, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsUseZeroForUndefinedLowerBound() { + OneOfCondition oneOfConditionPercent = createBetweenPercentCondition(null, 10_000_0000, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("is_enabled")); + } + + @Test + public void testEvaluatedConditionsGreaterThanMinToTrue() { + OneOfCondition oneOfConditionPercent = createPercentCondition(0, + PercentConditionOperator.GREATER_THAN, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("is_enabled")); + } + + @Test + public void testEvaluatedConditionsGreaterThanMaxToFalse() { + OneOfCondition oneOfConditionPercent = createPercentCondition(10_000_0000, + PercentConditionOperator.GREATER_THAN, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluatedConditionsBetweenMinAndMaxToTrue() { + OneOfCondition oneOfConditionPercent = createBetweenPercentCondition(0, 10_000_0000, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("is_enabled")); + } + + @Test + public void testEvaluatedConditionsBetweenEqualBoundsToFalse() { + OneOfCondition oneOfConditionPercent = createBetweenPercentCondition(5_000_000, + 5_000_000, "seed"); + OneOfCondition oneOfConditionAnd = createOneOfAndCondition(oneOfConditionPercent); + OneOfCondition oneOfConditionOr = createOneOfOrCondition(oneOfConditionAnd); + ServerCondition condition = new ServerCondition("is_enabled", oneOfConditionOr); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", "123"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("is_enabled")); + } + + @Test + public void testEvaluateConditionsLessOrEqualToApprox() { + OneOfCondition oneOfConditionPerCondition = createPercentCondition(10_000_000, + PercentConditionOperator.LESS_OR_EQUAL, "seed"); + // 284 is 3 standard deviations for 100k trials with 10% probability. + int tolerance = 284; + + int truthyAssignments = evaluateRandomAssignments(oneOfConditionPerCondition, 100000); + + // Evaluate less than or equal 10% to approx 10% + assertTrue(truthyAssignments >= 10_000 - tolerance); + assertTrue(truthyAssignments <= 10_000 + tolerance); + } + + @Test + public void testEvaluateConditionsBetweenApproximateToTrue() { + // Micropercent range is 40% to 60%. + OneOfCondition oneOfConditionPerCondition = createBetweenPercentCondition(40_000_000, + 60_000_000, "seed"); + // 379 is 3 standard deviations for 100k trials with 20% probability. + int tolerance = 379; + + int truthyAssignments = evaluateRandomAssignments(oneOfConditionPerCondition, 100000); + + // Evaluate between 40% to 60% to approx 20% + assertTrue(truthyAssignments >= 20_000 - tolerance); + assertTrue(truthyAssignments <= 20_000 + tolerance); + } + + @Test + public void testEvaluateConditionsInterquartileToFiftyPercent() { + // Micropercent range is 25% to 75%. + OneOfCondition oneOfConditionPerCondition = createBetweenPercentCondition(25_000_000, + 75_000_000, "seed"); + // 474 is 3 standard deviations for 100k trials with 50% probability. + int tolerance = 474; + + int truthyAssignments = evaluateRandomAssignments(oneOfConditionPerCondition, 100000); + + // Evaluate between 25% to 75 to approx 50% + assertTrue(truthyAssignments >= 50_000 - tolerance); + assertTrue(truthyAssignments <= 50_000 + tolerance); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericLessThanToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_LESS_THAN, ImmutableList.of("-50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-50.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericLessThanToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_LESS_THAN, ImmutableList.of("-50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-50.01"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalInvalidValueNumericOperationToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_LESS_THAN, ImmutableList.of("non-numeric")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-50.01"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericLessEqualToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_LESS_EQUAL, ImmutableList.of("-50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-50.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericLessEqualToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_LESS_EQUAL, ImmutableList.of("-50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-49.9"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericEqualsToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_EQUAL, ImmutableList.of("50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericEqualsToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_EQUAL, ImmutableList.of("50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.000001"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericNotEqualsToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_NOT_EQUAL, ImmutableList.of("50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericNotEqualsToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_NOT_EQUAL, ImmutableList.of("50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.000001"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericGreaterEqualToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_GREATER_EQUAL, ImmutableList.of("-50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-50.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericGreaterEqualToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_GREATER_EQUAL, ImmutableList.of("-50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-50.01"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericgreaterThanToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_GREATER_THAN, ImmutableList.of("-50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-50.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalNumericGreaterThanToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.NUMERIC_GREATER_THAN, ImmutableList.of("-50.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "-49.09"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringContainsToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.STRING_CONTAINS, ImmutableList.of("One", "hundred")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "Two hundred"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringContainsToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.STRING_CONTAINS, ImmutableList.of("One", "hundred")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "Two hudred"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringDoesNotContainToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.STRING_DOES_NOT_CONTAIN, ImmutableList.of("One", "hundred")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "Two hudred"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringDoesNotContainToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.STRING_DOES_NOT_CONTAIN, ImmutableList.of("One", "hundred")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "Two hundred"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringExactlyMatchesToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.STRING_EXACTLY_MATCHES, ImmutableList.of("hundred")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "hundred"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringExactlyMatchesToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.STRING_EXACTLY_MATCHES, ImmutableList.of("hundred")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "Two hundred"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringContainsRegexToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.STRING_CONTAINS_REGEX, ImmutableList.of(".*hund.*")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "hundred"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringContainsRegexToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.STRING_CONTAINS_REGEX, ImmutableList.of("$hund.*")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "Two ahundred"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionsCustomSignalStringContainsInvalidRegexToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.STRING_CONTAINS_REGEX, ImmutableList.of("abc)")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "Two ahundred"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticLessThanToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_LESS_THAN, ImmutableList.of("50.0.20")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.2.0.1"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticLessThanToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_LESS_THAN, ImmutableList.of("50.0.20")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticLessThanInvalidVersionToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_LESS_THAN, ImmutableList.of("50.0.-20")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.2.0.1"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticLessEqualToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_LESS_EQUAL, ImmutableList.of("50.0.20.0.0")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticLessEqualToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_LESS_EQUAL, ImmutableList.of("50.0.2")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.2.1.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticGreaterThanToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_GREATER_THAN, ImmutableList.of("50.0.2")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.1"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticGreaterThanToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_GREATER_THAN, ImmutableList.of("50.0.20")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticGreaterEqualToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_GREATER_EQUAL, ImmutableList.of("50.0.20")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticGreaterEqualToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_GREATER_EQUAL, ImmutableList.of("50.0.20.1")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticEqualToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_EQUAL, ImmutableList.of("50.0.20")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticEqualToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_EQUAL, ImmutableList.of("50.0.20.1")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticNotEqualToTrue() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_NOT_EQUAL, ImmutableList.of("50.0.20.1")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertTrue(result.get("signal_key")); + } + + @Test + public void testEvaluateConditionCustomSignalSemanticNotEqualToFalse() { + ServerCondition condition = createCustomSignalServerCondition( + CustomSignalOperator.SEMANTIC_VERSION_NOT_EQUAL, ImmutableList.of("50.0.20")); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("signal_key", "50.0.20.0.0"); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + assertFalse(result.get("signal_key")); + } + + private ServerCondition createCustomSignalServerCondition( + CustomSignalOperator operator, + ImmutableList targetCustomSignalValues) { + CustomSignalCondition condition = new CustomSignalCondition("signal_key", operator, + targetCustomSignalValues); + OneOfCondition oneOfConditionCustomSignal = new OneOfCondition(); + oneOfConditionCustomSignal.setCustomSignal(condition); + return new ServerCondition("signal_key", oneOfConditionCustomSignal); + } + + private int evaluateRandomAssignments(OneOfCondition percentCondition, int numOfAssignments) { + int evalTrueCount = 0; + ServerCondition condition = new ServerCondition("is_enabled", percentCondition); + for (int i = 0; i < numOfAssignments; i++) { + UUID randomizationId = UUID.randomUUID(); + KeysAndValues.Builder contextBuilder = new KeysAndValues.Builder(); + contextBuilder.put("randomizationId", randomizationId.toString()); + + Map result = conditionEvaluator.evaluateConditions(Arrays.asList(condition), + contextBuilder.build()); + + if (result.get("is_enabled")) { + evalTrueCount++; + } + } + return evalTrueCount; + } + + private OneOfCondition createPercentCondition(Integer microPercent, + PercentConditionOperator operator, String seed) { + PercentCondition percentCondition = new PercentCondition(microPercent, operator, seed); + OneOfCondition oneOfCondition = new OneOfCondition(); + oneOfCondition.setPercent(percentCondition); + return oneOfCondition; + } + + private OneOfCondition createBetweenPercentCondition(Integer lowerBound, Integer upperBound, + String seed) { + MicroPercentRange microPercentRange = new MicroPercentRange(lowerBound, upperBound); + PercentCondition percentCondition = new PercentCondition(microPercentRange, + PercentConditionOperator.BETWEEN, seed); + OneOfCondition oneOfCondition = new OneOfCondition(); + oneOfCondition.setPercent(percentCondition); + return oneOfCondition; + } + + private OneOfCondition createOneOfOrCondition(OneOfCondition condition) { + OrCondition orCondition = condition != null ? new OrCondition(ImmutableList.of(condition)) + : new OrCondition(ImmutableList.of()); + OneOfCondition oneOfCondition = new OneOfCondition(); + oneOfCondition.setOrCondition(orCondition); + return oneOfCondition; + } + + private OneOfCondition createOneOfAndCondition(OneOfCondition condition) { + AndCondition andCondition = condition != null ? new AndCondition(ImmutableList.of(condition)) + : new AndCondition(ImmutableList.of()); + OneOfCondition oneOfCondition = new OneOfCondition(); + oneOfCondition.setAndCondition(andCondition); + return oneOfCondition; + } + + private OneOfCondition createOneOfTrueCondition() { + OneOfCondition oneOfCondition = new OneOfCondition(); + oneOfCondition.setTrue(); + return oneOfCondition; + } + + private OneOfCondition createOneOfFalseCondition() { + OneOfCondition oneOfCondition = new OneOfCondition(); + oneOfCondition.setFalse(); + return oneOfCondition; + } +} \ No newline at end of file diff --git a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java index edc52a19d..78a5c276a 100644 --- a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java @@ -25,7 +25,6 @@ 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.GenericUrl; import com.google.api.client.http.HttpHeaders; import com.google.api.client.http.HttpMethods; @@ -48,82 +47,165 @@ import com.google.firebase.remoteconfig.internal.TemplateResponse; import com.google.firebase.testing.TestResponseInterceptor; import com.google.firebase.testing.TestUtils; - +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URLDecoder; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; - import org.junit.Before; import org.junit.Test; public class FirebaseRemoteConfigClientImplTest { private static final String TEST_REMOTE_CONFIG_URL = - "https://firebaseremoteconfig.googleapis.com/v1/projects/test-project/remoteConfig"; + "https://firebaseremoteconfig.googleapis.com/v1/projects/test-project/remoteConfig"; + private static final String TEST_SERVER_REMOTE_CONFIG_URL = + "https://firebaseremoteconfig.googleapis.com/v1/projects/test-project/namespaces/firebase-server/serverRemoteConfig"; private static final List HTTP_STATUS_CODES = ImmutableList.of(401, 404, 500); - private static final Map HTTP_STATUS_TO_ERROR_CODE = ImmutableMap.of( + private static final Map HTTP_STATUS_TO_ERROR_CODE = + ImmutableMap.of( 401, ErrorCode.UNAUTHENTICATED, 404, ErrorCode.NOT_FOUND, 500, ErrorCode.INTERNAL); - private static final String MOCK_TEMPLATE_RESPONSE = TestUtils - .loadResource("getRemoteConfig.json"); + private static final String MOCK_TEMPLATE_RESPONSE = + TestUtils.loadResource("getRemoteConfig.json"); + + private static final String MOCK_SERVER_TEMPLATE_RESPONSE = + TestUtils.loadResource("getServerRemoteConfig.json"); - private static final String MOCK_LIST_VERSIONS_RESPONSE = TestUtils - .loadResource("listRemoteConfigVersions.json"); + private static final String MOCK_LIST_VERSIONS_RESPONSE = + TestUtils.loadResource("listRemoteConfigVersions.json"); private static final String TEST_ETAG = "etag-123456789012-1"; - private static final Map EXPECTED_PARAMETERS = ImmutableMap.of( - "welcome_message_text", new Parameter() - .setDefaultValue(ParameterValue.of("welcome to app")) - .setConditionalValues(ImmutableMap.of( - "ios_en", ParameterValue.of("welcome to app en") - )) - .setDescription("text for welcome message!") - .setValueType(ParameterValueType.STRING), - "header_text", new Parameter() - .setDefaultValue(ParameterValue.inAppDefault()) - .setValueType(ParameterValueType.STRING) - ); - - private static final Map EXPECTED_PARAMETER_GROUPS = ImmutableMap.of( - "new menu", new ParameterGroup() - .setDescription("New Menu") - .setParameters(ImmutableMap.of( - "pumpkin_spice_season", new Parameter() - .setDefaultValue(ParameterValue.of("true")) - .setDescription("Whether it's currently pumpkin spice season.") - .setValueType(ParameterValueType.BOOLEAN) - ) - ) - ); - - private static final List EXPECTED_CONDITIONS = ImmutableList.of( + private static final Map EXPECTED_PARAMETERS = + ImmutableMap.of( + "welcome_message_text", + new Parameter() + .setDefaultValue(ParameterValue.of("welcome to app")) + .setConditionalValues( + ImmutableMap.of( + "ios_en", ParameterValue.of("welcome to app en"))) + .setDescription("text for welcome message!") + .setValueType(ParameterValueType.STRING), + "header_text", + new Parameter() + .setDefaultValue(ParameterValue.inAppDefault()) + .setValueType(ParameterValueType.STRING)); + + private static final Map EXPECTED_PARAMETER_GROUPS = + ImmutableMap.of( + "new menu", + new ParameterGroup() + .setDescription("New Menu") + .setParameters( + ImmutableMap.of( + "pumpkin_spice_season", + new Parameter() + .setDefaultValue(ParameterValue.of("true")) + .setDescription("Whether it's currently pumpkin spice season.") + .setValueType(ParameterValueType.BOOLEAN)))); + + private static final List EXPECTED_CONDITIONS = + ImmutableList.of( new Condition("ios_en", "device.os == 'ios' && device.country in ['us', 'uk']") - .setTagColor(TagColor.INDIGO), - new Condition("android_en", - "device.os == 'android' && device.country in ['us', 'uk']") - ); - - private static final Version EXPECTED_VERSION = new Version(new TemplateResponse.VersionResponse() - .setVersionNumber("17") - .setUpdateOrigin("ADMIN_SDK_NODE") - .setUpdateType("INCREMENTAL_UPDATE") - .setUpdateUser(new TemplateResponse.UserResponse() - .setEmail("firebase-user@account.com") - .setName("dev-admin") - .setImageUrl("http://image.jpg")) - .setUpdateTime("2020-11-15T06:57:26.342763941Z") - .setDescription("promo config") - ); - - private static final Template EXPECTED_TEMPLATE = new Template() + .setTagColor(TagColor.INDIGO), + new Condition("android_en", "device.os == 'android' && device.country in ['us', 'uk']")); + + private static final List EXPECTED_SERVER_CONDITIONS = + ImmutableList.of( + new ServerCondition("custom_signal", null) + .setServerCondition( + new OneOfCondition() + .setOrCondition( + new OrCondition( + ImmutableList.of( + new OneOfCondition() + .setAndCondition( + new AndCondition( + ImmutableList.of( + new OneOfCondition() + .setCustomSignal( + new CustomSignalCondition( + "users", + CustomSignalOperator + .NUMERIC_LESS_THAN, + new ArrayList<>( + ImmutableList.of("100"))))))))))), + new ServerCondition("percent", null) + .setServerCondition( + new OneOfCondition() + .setOrCondition( + new OrCondition( + ImmutableList.of( + new OneOfCondition() + .setAndCondition( + new AndCondition( + ImmutableList.of( + new OneOfCondition() + .setPercent( + new PercentCondition( + new MicroPercentRange( + 12000000, 100000000), + PercentConditionOperator.BETWEEN, + "3maarirs9xzs"))))))))), + new ServerCondition("chained_conditions", null) + .setServerCondition( + new OneOfCondition() + .setOrCondition( + new OrCondition( + ImmutableList.of( + new OneOfCondition() + .setAndCondition( + new AndCondition( + ImmutableList.of( + new OneOfCondition() + .setCustomSignal( + new CustomSignalCondition( + "users", + CustomSignalOperator + .NUMERIC_LESS_THAN, + new ArrayList<>( + ImmutableList.of("100")))), + new OneOfCondition() + .setCustomSignal( + new CustomSignalCondition( + "premium users", + CustomSignalOperator + .NUMERIC_GREATER_THAN, + new ArrayList<>( + ImmutableList.of("20")))), + new OneOfCondition() + .setPercent( + new PercentCondition( + new MicroPercentRange( + 25000000, 100000000), + PercentConditionOperator.BETWEEN, + "cla24qoibb61")))))))))); + + private static final Version EXPECTED_VERSION = + new Version( + new TemplateResponse.VersionResponse() + .setVersionNumber("17") + .setUpdateOrigin("ADMIN_SDK_NODE") + .setUpdateType("INCREMENTAL_UPDATE") + .setUpdateUser( + new TemplateResponse.UserResponse() + .setEmail("firebase-user@account.com") + .setName("dev-admin") + .setImageUrl("http://image.jpg")) + .setUpdateTime("2020-11-15T06:57:26.342763941Z") + .setDescription("promo config")); + + private static final Template EXPECTED_TEMPLATE = + new Template() .setETag(TEST_ETAG) .setParameters(EXPECTED_PARAMETERS) .setConditions(EXPECTED_CONDITIONS) @@ -158,16 +240,19 @@ public void testGetTemplate() throws Exception { @Test public void testGetTemplateWithTimestampUpToNanosecondPrecision() throws Exception { - List timestamps = ImmutableList.of( + List timestamps = + ImmutableList.of( "2020-11-15T06:57:26.342Z", "2020-11-15T06:57:26.342763Z", - "2020-11-15T06:57:26.342763941Z" - ); + "2020-11-15T06:57:26.342763941Z"); for (String timestamp : timestamps) { response.addHeader("etag", TEST_ETAG); - String templateResponse = "{\"version\": {" + String templateResponse = + "{\"version\": {" + " \"versionNumber\": \"17\"," - + " \"updateTime\": \"" + timestamp + "\"" + + " \"updateTime\": \"" + + timestamp + + "\"" + " }}"; response.setContent(templateResponse); @@ -221,8 +306,12 @@ public void testGetTemplateHttpError() { client.getTemplate(); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\n{}", HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\n{}", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest()); } @@ -237,8 +326,8 @@ public void testGetTemplateTransportError() { fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); - assertEquals("Unknown error while making a remote service call: transport error", - error.getMessage()); + assertEquals( + "Unknown error while making a remote service call: transport error", error.getMessage()); assertTrue(error.getCause() instanceof IOException); assertNull(error.getHttpResponse()); assertNull(error.getRemoteConfigErrorCode()); @@ -271,8 +360,12 @@ public void testGetTemplateErrorWithZeroContentResponse() { client.getTemplate(); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnull", HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnull", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest()); } @@ -287,8 +380,12 @@ public void testGetTemplateErrorWithMalformedResponse() { client.getTemplate(); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnot json", HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnot json", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest()); } @@ -297,15 +394,17 @@ public void testGetTemplateErrorWithMalformedResponse() { @Test public void testGetTemplateErrorWithDetails() { for (int code : HTTP_STATUS_CODES) { - response.setStatusCode(code).setContent( + response + .setStatusCode(code) + .setContent( "{\"error\": {\"status\": \"INVALID_ARGUMENT\", \"message\": \"test error\"}}"); try { client.getTemplate(); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, null, "test error", - HttpMethods.GET); + checkExceptionFromHttpResponse( + error, ErrorCode.INVALID_ARGUMENT, null, "test error", HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest()); } @@ -314,17 +413,22 @@ public void testGetTemplateErrorWithDetails() { @Test public void testGetTemplateErrorWithRcError() { for (int code : HTTP_STATUS_CODES) { - response.setStatusCode(code).setContent( + response + .setStatusCode(code) + .setContent( "{\"error\": {\"status\": \"INVALID_ARGUMENT\", " - + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); + + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); try { client.getTemplate(); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, - RemoteConfigErrorCode.INVALID_ARGUMENT, "[INVALID_ARGUMENT]: test error", - HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + ErrorCode.INVALID_ARGUMENT, + RemoteConfigErrorCode.INVALID_ARGUMENT, + "[INVALID_ARGUMENT]: test error", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest()); } @@ -339,8 +443,9 @@ public void testGetTemplateAtVersionWithNullString() throws Exception { @Test public void testGetTemplateAtVersionWithInvalidString() throws Exception { - List invalidVersionStrings = ImmutableList - .of("", " ", "abc", "t123", "123t", "t123t", "12t3", "#$*&^", "-123", "+123", "123.4"); + List invalidVersionStrings = + ImmutableList.of( + "", " ", "abc", "t123", "123t", "t123t", "12t3", "#$*&^", "-123", "+123", "123.4"); for (String version : invalidVersionStrings) { try { @@ -407,8 +512,12 @@ public void testGetTemplateAtVersionHttpError() { client.getTemplateAtVersion("24"); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\n{}", HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\n{}", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest(), "?versionNumber=24"); } @@ -423,8 +532,8 @@ public void testGetTemplateAtVersionTransportError() { fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); - assertEquals("Unknown error while making a remote service call: transport error", - error.getMessage()); + assertEquals( + "Unknown error while making a remote service call: transport error", error.getMessage()); assertTrue(error.getCause() instanceof IOException); assertNull(error.getHttpResponse()); assertNull(error.getRemoteConfigErrorCode()); @@ -457,8 +566,12 @@ public void testGetTemplateAtVersionErrorWithZeroContentResponse() { client.getTemplateAtVersion("24"); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnull", HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnull", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest(), "?versionNumber=24"); } @@ -473,8 +586,12 @@ public void testGetTemplateAtVersionErrorWithMalformedResponse() { client.getTemplateAtVersion("24"); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnot json", HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnot json", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest(), "?versionNumber=24"); } @@ -483,15 +600,17 @@ public void testGetTemplateAtVersionErrorWithMalformedResponse() { @Test public void testGetTemplateAtVersionErrorWithDetails() { for (int code : HTTP_STATUS_CODES) { - response.setStatusCode(code).setContent( + response + .setStatusCode(code) + .setContent( "{\"error\": {\"status\": \"INVALID_ARGUMENT\", \"message\": \"test error\"}}"); try { client.getTemplateAtVersion("24"); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, null, "test error", - HttpMethods.GET); + checkExceptionFromHttpResponse( + error, ErrorCode.INVALID_ARGUMENT, null, "test error", HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest(), "?versionNumber=24"); } @@ -500,17 +619,22 @@ public void testGetTemplateAtVersionErrorWithDetails() { @Test public void testGetTemplateAtVersionErrorWithRcError() { for (int code : HTTP_STATUS_CODES) { - response.setStatusCode(code).setContent( + response + .setStatusCode(code) + .setContent( "{\"error\": {\"status\": \"INVALID_ARGUMENT\", " - + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); + + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); try { client.getTemplateAtVersion("24"); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, - RemoteConfigErrorCode.INVALID_ARGUMENT, "[INVALID_ARGUMENT]: test error", - HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + ErrorCode.INVALID_ARGUMENT, + RemoteConfigErrorCode.INVALID_ARGUMENT, + "[INVALID_ARGUMENT]: test error", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest(), "?versionNumber=24"); } @@ -553,7 +677,8 @@ public void testPublishTemplateWithValidTemplateAndForceTrue() throws Exception public void testPublishTemplateWithValidTemplateAndValidateOnlyTrue() throws Exception { response.addHeader("etag", TEST_ETAG); response.setContent(MOCK_TEMPLATE_RESPONSE); - Template expectedTemplate = new Template() + Template expectedTemplate = + new Template() .setETag("etag-123456789012-45") .setParameters(EXPECTED_PARAMETERS) .setConditions(EXPECTED_CONDITIONS) @@ -562,12 +687,13 @@ public void testPublishTemplateWithValidTemplateAndValidateOnlyTrue() throws Exc Template validatedTemplate = client.publishTemplate(expectedTemplate, true, false); - // check if the etag matches the input template's etag and not the etag from the server response + // check if the etag matches the input template's etag and not the etag from the + // server response assertNotEquals(TEST_ETAG, validatedTemplate.getETag()); assertEquals("etag-123456789012-45", validatedTemplate.getETag()); assertEquals(expectedTemplate, validatedTemplate); - checkPutRequestHeader(interceptor.getLastRequest(), "?validateOnly=true", - "etag-123456789012-45"); + checkPutRequestHeader( + interceptor.getLastRequest(), "?validateOnly=true", "etag-123456789012-45"); } @Test @@ -611,8 +737,12 @@ public void testPublishTemplateHttpError() { client.publishTemplate(new Template().setETag(TEST_ETAG), false, false); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\n{}", HttpMethods.PUT); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\n{}", + HttpMethods.PUT); } checkPutRequestHeader(interceptor.getLastRequest()); } @@ -627,8 +757,8 @@ public void testPublishTemplateTransportError() { fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); - assertEquals("Unknown error while making a remote service call: transport error", - error.getMessage()); + assertEquals( + "Unknown error while making a remote service call: transport error", error.getMessage()); assertTrue(error.getCause() instanceof IOException); assertNull(error.getHttpResponse()); assertNull(error.getRemoteConfigErrorCode()); @@ -661,8 +791,12 @@ public void testPublishTemplateErrorWithZeroContentResponse() { client.publishTemplate(new Template().setETag(TEST_ETAG), false, false); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnull", HttpMethods.PUT); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnull", + HttpMethods.PUT); } checkPutRequestHeader(interceptor.getLastRequest()); } @@ -677,8 +811,12 @@ public void testPublishTemplateErrorWithMalformedResponse() { client.publishTemplate(new Template().setETag(TEST_ETAG), false, false); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnot json", HttpMethods.PUT); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnot json", + HttpMethods.PUT); } checkPutRequestHeader(interceptor.getLastRequest()); } @@ -687,15 +825,17 @@ public void testPublishTemplateErrorWithMalformedResponse() { @Test public void testPublishTemplateErrorWithDetails() { for (int code : HTTP_STATUS_CODES) { - response.setStatusCode(code).setContent( + response + .setStatusCode(code) + .setContent( "{\"error\": {\"status\": \"INVALID_ARGUMENT\", \"message\": \"test error\"}}"); try { client.publishTemplate(new Template().setETag(TEST_ETAG), false, false); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, null, "test error", - HttpMethods.PUT); + checkExceptionFromHttpResponse( + error, ErrorCode.INVALID_ARGUMENT, null, "test error", HttpMethods.PUT); } checkPutRequestHeader(interceptor.getLastRequest()); } @@ -704,17 +844,22 @@ public void testPublishTemplateErrorWithDetails() { @Test public void testPublishTemplateErrorWithRcError() { for (int code : HTTP_STATUS_CODES) { - response.setStatusCode(code).setContent( + response + .setStatusCode(code) + .setContent( "{\"error\": {\"status\": \"INVALID_ARGUMENT\", " - + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); + + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); try { client.publishTemplate(new Template().setETag(TEST_ETAG), false, false); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, - RemoteConfigErrorCode.INVALID_ARGUMENT, "[INVALID_ARGUMENT]: test error", - HttpMethods.PUT); + checkExceptionFromHttpResponse( + error, + ErrorCode.INVALID_ARGUMENT, + RemoteConfigErrorCode.INVALID_ARGUMENT, + "[INVALID_ARGUMENT]: test error", + HttpMethods.PUT); } checkPutRequestHeader(interceptor.getLastRequest()); } @@ -729,8 +874,9 @@ public void testRollbackWithNullString() throws Exception { @Test public void testRollbackWithInvalidString() throws Exception { - List invalidVersionStrings = ImmutableList - .of("", " ", "abc", "t123", "123t", "t123t", "12t3", "#$*&^", "-123", "+123", "123.4"); + List invalidVersionStrings = + ImmutableList.of( + "", " ", "abc", "t123", "123t", "t123t", "12t3", "#$*&^", "-123", "+123", "123.4"); for (String version : invalidVersionStrings) { try { @@ -754,8 +900,8 @@ public void testRollbackWithValidString() throws Exception { assertEquals(EXPECTED_TEMPLATE, rolledBackTemplate); assertEquals(1605423446000L, rolledBackTemplate.getVersion().getUpdateTime()); checkPostRequestHeader(interceptor.getLastRequest(), ":rollback"); - checkRequestContent(interceptor.getLastRequest(), - ImmutableMap.of("versionNumber", "24")); + checkRequestContent( + interceptor.getLastRequest(), ImmutableMap.of("versionNumber", "24")); } @Test @@ -771,8 +917,8 @@ public void testRollbackWithEmptyTemplateResponse() throws Exception { assertEquals(0, template.getParameterGroups().size()); assertNull(template.getVersion()); checkPostRequestHeader(interceptor.getLastRequest(), ":rollback"); - checkRequestContent(interceptor.getLastRequest(), - ImmutableMap.of("versionNumber", "24")); + checkRequestContent( + interceptor.getLastRequest(), ImmutableMap.of("versionNumber", "24")); } @Test(expected = IllegalStateException.class) @@ -801,12 +947,16 @@ public void testRollbackHttpError() throws IOException { client.rollback("24"); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\n{}", HttpMethods.POST); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\n{}", + HttpMethods.POST); } checkPostRequestHeader(interceptor.getLastRequest(), ":rollback"); - checkRequestContent(interceptor.getLastRequest(), - ImmutableMap.of("versionNumber", "24")); + checkRequestContent( + interceptor.getLastRequest(), ImmutableMap.of("versionNumber", "24")); } } @@ -819,8 +969,8 @@ public void testRollbackTransportError() { fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); - assertEquals("Unknown error while making a remote service call: transport error", - error.getMessage()); + assertEquals( + "Unknown error while making a remote service call: transport error", error.getMessage()); assertTrue(error.getCause() instanceof IOException); assertNull(error.getHttpResponse()); assertNull(error.getRemoteConfigErrorCode()); @@ -842,8 +992,8 @@ public void testRollbackSuccessResponseWithUnexpectedPayload() throws IOExceptio assertNull(error.getRemoteConfigErrorCode()); } checkPostRequestHeader(interceptor.getLastRequest(), ":rollback"); - checkRequestContent(interceptor.getLastRequest(), - ImmutableMap.of("versionNumber", "24")); + checkRequestContent( + interceptor.getLastRequest(), ImmutableMap.of("versionNumber", "24")); } @Test @@ -855,12 +1005,16 @@ public void testRollbackErrorWithZeroContentResponse() throws IOException { client.rollback("24"); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnull", HttpMethods.POST); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnull", + HttpMethods.POST); } checkPostRequestHeader(interceptor.getLastRequest(), ":rollback"); - checkRequestContent(interceptor.getLastRequest(), - ImmutableMap.of("versionNumber", "24")); + checkRequestContent( + interceptor.getLastRequest(), ImmutableMap.of("versionNumber", "24")); } } @@ -873,52 +1027,63 @@ public void testRollbackErrorWithMalformedResponse() throws IOException { client.rollback("24"); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnot json", HttpMethods.POST); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnot json", + HttpMethods.POST); } checkPostRequestHeader(interceptor.getLastRequest(), ":rollback"); - checkRequestContent(interceptor.getLastRequest(), - ImmutableMap.of("versionNumber", "24")); + checkRequestContent( + interceptor.getLastRequest(), ImmutableMap.of("versionNumber", "24")); } } @Test public void testRollbackErrorWithDetails() throws IOException { for (int code : HTTP_STATUS_CODES) { - response.setStatusCode(code).setContent( + response + .setStatusCode(code) + .setContent( "{\"error\": {\"status\": \"INVALID_ARGUMENT\", \"message\": \"test error\"}}"); try { client.rollback("24"); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, null, "test error", - HttpMethods.POST); + checkExceptionFromHttpResponse( + error, ErrorCode.INVALID_ARGUMENT, null, "test error", HttpMethods.POST); } checkPostRequestHeader(interceptor.getLastRequest(), ":rollback"); - checkRequestContent(interceptor.getLastRequest(), - ImmutableMap.of("versionNumber", "24")); + checkRequestContent( + interceptor.getLastRequest(), ImmutableMap.of("versionNumber", "24")); } } @Test public void testRollbackErrorWithRcError() throws IOException { for (int code : HTTP_STATUS_CODES) { - response.setStatusCode(code).setContent( + response + .setStatusCode(code) + .setContent( "{\"error\": {\"status\": \"INVALID_ARGUMENT\", " - + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); + + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); try { client.rollback("24"); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, - RemoteConfigErrorCode.INVALID_ARGUMENT, "[INVALID_ARGUMENT]: test error", - HttpMethods.POST); + checkExceptionFromHttpResponse( + error, + ErrorCode.INVALID_ARGUMENT, + RemoteConfigErrorCode.INVALID_ARGUMENT, + "[INVALID_ARGUMENT]: test error", + HttpMethods.POST); } checkPostRequestHeader(interceptor.getLastRequest(), ":rollback"); - checkRequestContent(interceptor.getLastRequest(), - ImmutableMap.of("versionNumber", "24")); + checkRequestContent( + interceptor.getLastRequest(), ImmutableMap.of("versionNumber", "24")); } } @@ -940,33 +1105,36 @@ public void testListVersionsWithNullOptions() throws Exception { public void testListVersionsWithOptions() throws Exception { response.setContent(MOCK_LIST_VERSIONS_RESPONSE); - TemplateResponse.ListVersionsResponse versionsList = client.listVersions( + TemplateResponse.ListVersionsResponse versionsList = + client.listVersions( ListVersionsOptions.builder() - .setPageSize(10) - .setPageToken("token") - .setStartTimeMillis(1605219122000L) - .setEndTimeMillis(1606245035000L) - .setEndVersionNumber("29").build()); + .setPageSize(10) + .setPageToken("token") + .setStartTimeMillis(1605219122000L) + .setEndTimeMillis(1606245035000L) + .setEndVersionNumber("29") + .build()); assertTrue(versionsList.hasVersions()); HttpRequest request = interceptor.getLastRequest(); - String urlWithoutParameters = request.getUrl().toString() - .substring(0, request.getUrl().toString().lastIndexOf('?')); - final Map expectedQuery = ImmutableMap.of( + String urlWithoutParameters = + request.getUrl().toString().substring(0, request.getUrl().toString().lastIndexOf('?')); + final Map expectedQuery = + ImmutableMap.of( "endVersionNumber", "29", "pageSize", "10", "pageToken", "token", "startTime", "2020-11-12T22:12:02.000000000Z", - "endTime", "2020-11-24T19:10:35.000000000Z" - ); + "endTime", "2020-11-24T19:10:35.000000000Z"); Map actualQuery = new HashMap<>(); String query = request.getUrl().toURI().getQuery(); String[] pairs = query.split("&"); for (String pair : pairs) { int idx = pair.indexOf("="); - actualQuery.put(URLDecoder.decode(pair.substring(0, idx), "UTF-8"), - URLDecoder.decode(pair.substring(idx + 1), "UTF-8")); + actualQuery.put( + URLDecoder.decode(pair.substring(0, idx), "UTF-8"), + URLDecoder.decode(pair.substring(idx + 1), "UTF-8")); } assertEquals("GET", request.getRequestMethod()); @@ -998,8 +1166,12 @@ public void testListVersionsHttpError() { client.listVersions(null); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\n{}", HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\n{}", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest(), ":listVersions"); } @@ -1014,8 +1186,8 @@ public void testListVersionsTransportError() { fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); - assertEquals("Unknown error while making a remote service call: transport error", - error.getMessage()); + assertEquals( + "Unknown error while making a remote service call: transport error", error.getMessage()); assertTrue(error.getCause() instanceof IOException); assertNull(error.getHttpResponse()); assertNull(error.getRemoteConfigErrorCode()); @@ -1048,8 +1220,12 @@ public void testListVersionsErrorWithZeroContentResponse() { client.listVersions(null); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnull", HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnull", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest(), ":listVersions"); } @@ -1064,8 +1240,12 @@ public void testListVersionsErrorWithMalformedResponse() { client.listVersions(null); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, HTTP_STATUS_TO_ERROR_CODE.get(code), null, - "Unexpected HTTP response with status: " + code + "\nnot json", HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnot json", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest(), ":listVersions"); } @@ -1074,15 +1254,17 @@ public void testListVersionsErrorWithMalformedResponse() { @Test public void testListVersionsErrorWithDetails() { for (int code : HTTP_STATUS_CODES) { - response.setStatusCode(code).setContent( + response + .setStatusCode(code) + .setContent( "{\"error\": {\"status\": \"INVALID_ARGUMENT\", \"message\": \"test error\"}}"); try { client.listVersions(null); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, null, "test error", - HttpMethods.GET); + checkExceptionFromHttpResponse( + error, ErrorCode.INVALID_ARGUMENT, null, "test error", HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest(), ":listVersions"); } @@ -1091,17 +1273,22 @@ public void testListVersionsErrorWithDetails() { @Test public void testListVersionsErrorWithRcError() { for (int code : HTTP_STATUS_CODES) { - response.setStatusCode(code).setContent( + response + .setStatusCode(code) + .setContent( "{\"error\": {\"status\": \"INVALID_ARGUMENT\", " - + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); + + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); try { client.listVersions(null); fail("No error thrown for HTTP error"); } catch (FirebaseRemoteConfigException error) { - checkExceptionFromHttpResponse(error, ErrorCode.INVALID_ARGUMENT, - RemoteConfigErrorCode.INVALID_ARGUMENT, "[INVALID_ARGUMENT]: test error", - HttpMethods.GET); + checkExceptionFromHttpResponse( + error, + ErrorCode.INVALID_ARGUMENT, + RemoteConfigErrorCode.INVALID_ARGUMENT, + "[INVALID_ARGUMENT]: test error", + HttpMethods.GET); } checkGetRequestHeader(interceptor.getLastRequest(), ":listVersions"); } @@ -1126,7 +1313,8 @@ public void testBuilderNullRequestFactory() { @Test public void testFromApp() throws IOException { - FirebaseOptions options = FirebaseOptions.builder() + FirebaseOptions options = + FirebaseOptions.builder() .setCredentials(new MockGoogleCredentials("test-token")) .setProjectId("test-project") .build(); @@ -1138,8 +1326,8 @@ public void testFromApp() throws IOException { assertEquals(TEST_REMOTE_CONFIG_URL, client.getRemoteConfigUrl()); assertSame(options.getJsonFactory(), client.getJsonFactory()); - HttpRequest request = client.getRequestFactory().buildGetRequest( - new GenericUrl("https://example.com")); + HttpRequest request = + client.getRequestFactory().buildGetRequest(new GenericUrl("https://example.com")); assertEquals("Bearer test-token", request.getHeaders().getAuthorization()); } finally { app.delete(); @@ -1147,33 +1335,32 @@ public void testFromApp() throws IOException { } private FirebaseRemoteConfigClientImpl initRemoteConfigClient( - MockLowLevelHttpResponse mockResponse, HttpResponseInterceptor interceptor) { - MockHttpTransport transport = new MockHttpTransport.Builder() - .setLowLevelHttpResponse(mockResponse) - .build(); + MockLowLevelHttpResponse mockResponse, HttpResponseInterceptor interceptor) { + MockHttpTransport transport = + new MockHttpTransport.Builder().setLowLevelHttpResponse(mockResponse).build(); return FirebaseRemoteConfigClientImpl.builder() - .setProjectId("test-project") - .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) - .setRequestFactory(transport.createRequestFactory()) - .setResponseInterceptor(interceptor) - .build(); + .setProjectId("test-project") + .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) + .setRequestFactory(transport.createRequestFactory()) + .setResponseInterceptor(interceptor) + .build(); } private FirebaseRemoteConfigClientImpl initClientWithFaultyTransport() { HttpTransport transport = TestUtils.createFaultyHttpTransport(); return FirebaseRemoteConfigClientImpl.builder() - .setProjectId("test-project") - .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) - .setRequestFactory(transport.createRequestFactory()) - .build(); + .setProjectId("test-project") + .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) + .setRequestFactory(transport.createRequestFactory()) + .build(); } private FirebaseRemoteConfigClientImpl.Builder fullyPopulatedBuilder() { return FirebaseRemoteConfigClientImpl.builder() - .setProjectId("test-project") - .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) - .setRequestFactory(ApiClientUtils.getDefaultTransport().createRequestFactory()); + .setProjectId("test-project") + .setJsonFactory(ApiClientUtils.getDefaultJsonFactory()) + .setRequestFactory(ApiClientUtils.getDefaultTransport().createRequestFactory()); } private void checkGetRequestHeader(HttpRequest request) { @@ -1189,6 +1376,19 @@ private void checkGetRequestHeader(HttpRequest request, String urlSuffix) { assertEquals("gzip", headers.getAcceptEncoding()); } + private void checkGetRequestHeaderForServer(HttpRequest request) { + checkGetRequestHeaderForServer(request, ""); + } + + private void checkGetRequestHeaderForServer(HttpRequest request, String urlSuffix) { + assertEquals("GET", request.getRequestMethod()); + assertEquals(TEST_SERVER_REMOTE_CONFIG_URL + urlSuffix, request.getUrl().toString()); + HttpHeaders headers = request.getHeaders(); + assertEquals("fire-admin-java/" + SdkUtils.getVersion(), headers.get("X-Firebase-Client")); + assertEquals(SdkUtils.getMetricsHeader(), request.getHeaders().get("X-Goog-Api-Client")); + assertEquals("gzip", headers.getAcceptEncoding()); + } + private void checkPutRequestHeader(HttpRequest request) { checkPutRequestHeader(request, "", TEST_ETAG); } @@ -1212,8 +1412,8 @@ private void checkPostRequestHeader(HttpRequest request, String urlSuffix) { assertEquals("gzip", headers.getAcceptEncoding()); } - private void checkRequestContent( - HttpRequest request, Map expected) throws IOException { + private void checkRequestContent(HttpRequest request, Map expected) + throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); request.getContent().writeTo(out); JsonParser parser = ApiClientUtils.getDefaultJsonFactory().createJsonParser(out.toString()); @@ -1223,11 +1423,11 @@ private void checkRequestContent( } private void checkExceptionFromHttpResponse( - FirebaseRemoteConfigException error, - ErrorCode expectedCode, - RemoteConfigErrorCode expectedRemoteConfigCode, - String expectedMessage, - String httpMethod) { + FirebaseRemoteConfigException error, + ErrorCode expectedCode, + RemoteConfigErrorCode expectedRemoteConfigCode, + String expectedMessage, + String httpMethod) { assertEquals(expectedCode, error.getErrorCode()); assertEquals(expectedMessage, error.getMessage()); assertTrue(error.getCause() instanceof HttpResponseException); @@ -1238,4 +1438,225 @@ private void checkExceptionFromHttpResponse( assertEquals(httpMethod, request.getMethod()); assertTrue(request.getUrl().startsWith("https://firebaseremoteconfig.googleapis.com")); } + + // Get server template tests + + @Test + public void testGetServerTemplate() throws Exception { + response.addHeader("etag", TEST_ETAG); + response.setContent(MOCK_SERVER_TEMPLATE_RESPONSE); + + String receivedTemplate = client.getServerTemplate(); + ServerTemplateData serverTemplateData = ServerTemplateData.fromJSON(receivedTemplate); + + assertEquals(EXPECTED_PARAMETERS, serverTemplateData.getParameters()); + assertEquals(TEST_ETAG, serverTemplateData.getETag()); + assertEquals( + convertObjectToString(EXPECTED_SERVER_CONDITIONS), + convertObjectToString(serverTemplateData.getServerConditions())); + assertEquals(1605423446000L, serverTemplateData.getVersion().getUpdateTime()); + checkGetRequestHeaderForServer(interceptor.getLastRequest()); + } + + @Test + public void testGetServerTemplateWithTimestampUpToNanosecondPrecision() throws Exception { + List timestamps = + ImmutableList.of( + "2020-11-15T06:57:26.342Z", + "2020-11-15T06:57:26.342763Z", + "2020-11-15T06:57:26.342763941Z"); + for (String timestamp : timestamps) { + response.addHeader("etag", TEST_ETAG); + String templateResponse = + "{\"version\": {" + + " \"versionNumber\": \"17\"," + + " \"updateTime\": \"" + + timestamp + + "\"" + + " }}"; + response.setContent(templateResponse); + + String receivedTemplate = client.getServerTemplate(); + ServerTemplateData serverTemplateData = ServerTemplateData.fromJSON(receivedTemplate); + assertEquals(TEST_ETAG, serverTemplateData.getETag()); + assertEquals("17", serverTemplateData.getVersion().getVersionNumber()); + assertEquals(1605423446000L, serverTemplateData.getVersion().getUpdateTime()); + checkGetRequestHeaderForServer(interceptor.getLastRequest()); + } + } + + @Test + public void testGetServerTemplateWithEmptyTemplateResponse() throws Exception { + response.addHeader("etag", TEST_ETAG); + response.setContent("{}"); + + String receivedTemplate = client.getServerTemplate(); + ServerTemplateData serverTemplateData = ServerTemplateData.fromJSON(receivedTemplate); + + assertEquals(TEST_ETAG, serverTemplateData.getETag()); + assertEquals(0, serverTemplateData.getParameters().size()); + assertEquals(0, serverTemplateData.getServerConditions().size()); + assertEquals(0, serverTemplateData.getParameterGroups().size()); + assertNull(serverTemplateData.getVersion()); + checkGetRequestHeaderForServer(interceptor.getLastRequest()); + } + + @Test(expected = IllegalStateException.class) + public void testGetServerTemplateWithNoEtag() throws FirebaseRemoteConfigException { + // ETag does not exist + response.setContent(MOCK_SERVER_TEMPLATE_RESPONSE); + + client.getServerTemplate(); + } + + @Test(expected = IllegalStateException.class) + public void testGetServerTemplateWithEmptyEtag() throws FirebaseRemoteConfigException { + // Empty ETag + response.addHeader("etag", ""); + response.setContent(MOCK_SERVER_TEMPLATE_RESPONSE); + + client.getServerTemplate(); + } + + @Test + public void testGetServerTemplateHttpError() { + for (int code : HTTP_STATUS_CODES) { + response.setStatusCode(code).setContent("{}"); + + try { + client.getServerTemplate(); + fail("No error thrown for HTTP error"); + } catch (FirebaseRemoteConfigException error) { + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\n{}", + HttpMethods.GET); + } + checkGetRequestHeaderForServer(interceptor.getLastRequest()); + } + } + + @Test + public void testGetServerTemplateTransportError() { + client = initClientWithFaultyTransport(); + + try { + client.getServerTemplate(); + fail("No error thrown for HTTP error"); + } catch (FirebaseRemoteConfigException error) { + assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); + assertEquals( + "Unknown error while making a remote service call: transport error", error.getMessage()); + assertTrue(error.getCause() instanceof IOException); + assertNull(error.getHttpResponse()); + assertNull(error.getRemoteConfigErrorCode()); + } + } + + @Test + public void testGetServerTemplateSuccessResponseWithUnexpectedPayload() { + response.setContent("not valid json"); + + try { + client.getServerTemplate(); + fail("No error thrown for malformed response"); + } catch (FirebaseRemoteConfigException error) { + assertEquals(ErrorCode.UNKNOWN, error.getErrorCode()); + assertTrue(error.getMessage().startsWith("Error while parsing HTTP response: ")); + assertNotNull(error.getCause()); + assertNotNull(error.getHttpResponse()); + assertNull(error.getRemoteConfigErrorCode()); + } + checkGetRequestHeaderForServer(interceptor.getLastRequest()); + } + + @Test + public void testGetServerTemplateErrorWithZeroContentResponse() { + for (int code : HTTP_STATUS_CODES) { + response.setStatusCode(code).setZeroContent(); + + try { + client.getServerTemplate(); + fail("No error thrown for HTTP error"); + } catch (FirebaseRemoteConfigException error) { + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnull", + HttpMethods.GET); + } + checkGetRequestHeaderForServer(interceptor.getLastRequest()); + } + } + + @Test + public void testGetServerTemplateErrorWithMalformedResponse() { + for (int code : HTTP_STATUS_CODES) { + response.setStatusCode(code).setContent("not json"); + + try { + client.getServerTemplate(); + fail("No error thrown for HTTP error"); + } catch (FirebaseRemoteConfigException error) { + checkExceptionFromHttpResponse( + error, + HTTP_STATUS_TO_ERROR_CODE.get(code), + null, + "Unexpected HTTP response with status: " + code + "\nnot json", + HttpMethods.GET); + } + checkGetRequestHeaderForServer(interceptor.getLastRequest()); + } + } + + @Test + public void testGetServerTemplateErrorWithDetails() { + for (int code : HTTP_STATUS_CODES) { + response + .setStatusCode(code) + .setContent( + "{\"error\": {\"status\": \"INVALID_ARGUMENT\", \"message\": \"test error\"}}"); + + try { + client.getServerTemplate(); + fail("No error thrown for HTTP error"); + } catch (FirebaseRemoteConfigException error) { + checkExceptionFromHttpResponse( + error, ErrorCode.INVALID_ARGUMENT, null, "test error", HttpMethods.GET); + } + checkGetRequestHeaderForServer(interceptor.getLastRequest()); + } + } + + @Test + public void testGetServerTemplateErrorWithRcError() { + for (int code : HTTP_STATUS_CODES) { + response + .setStatusCode(code) + .setContent( + "{\"error\": {\"status\": \"INVALID_ARGUMENT\", " + + "\"message\": \"[INVALID_ARGUMENT]: test error\"}}"); + + try { + client.getServerTemplate(); + fail("No error thrown for HTTP error"); + } catch (FirebaseRemoteConfigException error) { + checkExceptionFromHttpResponse( + error, + ErrorCode.INVALID_ARGUMENT, + RemoteConfigErrorCode.INVALID_ARGUMENT, + "[INVALID_ARGUMENT]: test error", + HttpMethods.GET); + } + checkGetRequestHeaderForServer(interceptor.getLastRequest()); + } + } + + public static String convertObjectToString(Object object) { + Gson gson = new GsonBuilder().setPrettyPrinting().create(); // Optional: pretty printing + return gson.toJson(object); + } } diff --git a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java index d3e7fbff2..8b416b67b 100644 --- a/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java @@ -27,22 +27,26 @@ import com.google.firebase.FirebaseOptions; import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.auth.MockGoogleCredentials; - import com.google.firebase.remoteconfig.internal.TemplateResponse; - +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; import java.util.concurrent.ExecutionException; - import org.junit.After; import org.junit.Test; + +/** Tests +* for {@link FirebaseRemoteConfig}. +* */ public class FirebaseRemoteConfigTest { - private static final FirebaseOptions TEST_OPTIONS = FirebaseOptions.builder() + private static final FirebaseOptions TEST_OPTIONS = + FirebaseOptions.builder() .setCredentials(new MockGoogleCredentials("test-token")) .setProjectId("test-project") .build(); private static final FirebaseRemoteConfigException TEST_EXCEPTION = - new FirebaseRemoteConfigException(ErrorCode.INTERNAL, "Test error message"); + new FirebaseRemoteConfigException(ErrorCode.INTERNAL, "Test error message"); @After public void tearDown() { @@ -76,10 +80,25 @@ public void testDefaultRemoteConfigClient() { assertTrue(client instanceof FirebaseRemoteConfigClientImpl); assertSame(client, remoteConfig.getRemoteConfigClient()); - String expectedUrl = "https://firebaseremoteconfig.googleapis.com/v1/projects/test-project/remoteConfig"; + String expectedUrl = + "https://firebaseremoteconfig.googleapis.com/v1/projects/test-project/remoteConfig"; assertEquals(expectedUrl, ((FirebaseRemoteConfigClientImpl) client).getRemoteConfigUrl()); } + @Test + public void testDefaultServerRemoteConfigClient() { + FirebaseApp app = FirebaseApp.initializeApp(TEST_OPTIONS, "custom-app"); + FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.getInstance(app); + + FirebaseRemoteConfigClient client = remoteConfig.getRemoteConfigClient(); + + assertTrue(client instanceof FirebaseRemoteConfigClientImpl); + assertSame(client, remoteConfig.getRemoteConfigClient()); + String expectedUrl = + "https://firebaseremoteconfig.googleapis.com/v1/projects/test-project/namespaces/firebase-server/serverRemoteConfig"; + assertEquals(expectedUrl, ((FirebaseRemoteConfigClientImpl) client).getServerRemoteConfigUrl()); + } + @Test public void testAppDelete() { FirebaseApp app = FirebaseApp.initializeApp(TEST_OPTIONS, "custom-app"); @@ -98,16 +117,16 @@ public void testAppDelete() { @Test public void testRemoteConfigClientWithoutProjectId() { - FirebaseOptions options = FirebaseOptions.builder() - .setCredentials(new MockGoogleCredentials("test-token")) - .build(); + FirebaseOptions options = + FirebaseOptions.builder().setCredentials(new MockGoogleCredentials("test-token")).build(); FirebaseApp.initializeApp(options); try { FirebaseRemoteConfig.getInstance(); fail("No error thrown for missing project ID"); } catch (IllegalArgumentException expected) { - String message = "Project ID is required to access Remote Config service. Use a service " + String message = + "Project ID is required to access Remote Config 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."; @@ -121,8 +140,8 @@ public void testRemoteConfigClientWithoutProjectId() { @Test public void testGetTemplate() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplate(); @@ -144,8 +163,8 @@ public void testGetTemplateFailure() { @Test public void testGetTemplateAsync() throws Exception { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplateAsync().get(); @@ -169,8 +188,8 @@ public void testGetTemplateAsyncFailure() throws InterruptedException { @Test public void testGetTemplateAtVersionWithStringValue() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplateAtVersion("64"); @@ -192,8 +211,8 @@ public void testGetTemplateAtVersionWithStringValueFailure() { @Test public void testGetTemplateAtVersionAsyncWithStringValue() throws Exception { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplateAtVersionAsync("55").get(); @@ -215,8 +234,8 @@ public void testGetTemplateAtVersionAsyncWithStringValueFailure() throws Interru @Test public void testGetTemplateAtVersionWithLongValue() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplateAtVersion(64L); @@ -238,8 +257,8 @@ public void testGetTemplateAtVersionWithLongValueFailure() { @Test public void testGetTemplateAtVersionAsyncWithLongValue() throws Exception { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.getTemplateAtVersionAsync(55L).get(); @@ -407,8 +426,8 @@ public void testForcePublishTemplateAsyncFailure() throws InterruptedException { @Test public void testRollbackWithStringValue() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.rollback("64"); @@ -430,8 +449,8 @@ public void testRollbackWithStringValueFailure() { @Test public void testRollbackAsyncWithStringValue() throws Exception { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.rollbackAsync("55").get(); @@ -453,8 +472,8 @@ public void testRollbackAsyncWithStringValueFailure() throws InterruptedExceptio @Test public void testRollbackWithLongValue() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.rollback(64L); @@ -476,8 +495,8 @@ public void testRollbackWithLongValueFailure() { @Test public void testRollbackAsyncWithLongValue() throws Exception { - MockRemoteConfigClient client = MockRemoteConfigClient.fromTemplate( - new Template().setETag(TEST_ETAG)); + MockRemoteConfigClient client = + MockRemoteConfigClient.fromTemplate(new Template().setETag(TEST_ETAG)); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); Template template = remoteConfig.rollbackAsync(55L).get(); @@ -501,7 +520,8 @@ public void testRollbackAsyncWithLongValueFailure() throws InterruptedException @Test public void testListVersionsWithNoOptions() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = MockRemoteConfigClient.fromListVersionsResponse( + MockRemoteConfigClient client = + MockRemoteConfigClient.fromListVersionsResponse( new TemplateResponse.ListVersionsResponse().setNextPageToken("token")); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); @@ -524,7 +544,8 @@ public void testListVersionsWithNoOptionsFailure() { @Test public void testListVersionsAsyncWithNoOptions() throws Exception { - MockRemoteConfigClient client = MockRemoteConfigClient.fromListVersionsResponse( + MockRemoteConfigClient client = + MockRemoteConfigClient.fromListVersionsResponse( new TemplateResponse.ListVersionsResponse().setNextPageToken("token")); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); @@ -547,12 +568,13 @@ public void testListVersionsAsyncWithNoOptionsFailure() throws InterruptedExcept @Test public void testListVersionsWithOptions() throws FirebaseRemoteConfigException { - MockRemoteConfigClient client = MockRemoteConfigClient.fromListVersionsResponse( + MockRemoteConfigClient client = + MockRemoteConfigClient.fromListVersionsResponse( new TemplateResponse.ListVersionsResponse().setNextPageToken("token")); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); - ListVersionsPage listVersionsPage = remoteConfig.listVersions( - ListVersionsOptions.builder().build()); + ListVersionsPage listVersionsPage = + remoteConfig.listVersions(ListVersionsOptions.builder().build()); assertEquals("token", listVersionsPage.getNextPageToken()); } @@ -571,12 +593,13 @@ public void testListVersionsWithOptionsFailure() { @Test public void testListVersionsAsyncWithOptions() throws Exception { - MockRemoteConfigClient client = MockRemoteConfigClient.fromListVersionsResponse( + MockRemoteConfigClient client = + MockRemoteConfigClient.fromListVersionsResponse( new TemplateResponse.ListVersionsResponse().setNextPageToken("token")); FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); - ListVersionsPage listVersionsPage = remoteConfig.listVersionsAsync( - ListVersionsOptions.builder().build()).get(); + ListVersionsPage listVersionsPage = + remoteConfig.listVersionsAsync(ListVersionsOptions.builder().build()).get(); assertEquals("token", listVersionsPage.getNextPageToken()); } @@ -597,4 +620,62 @@ private FirebaseRemoteConfig getRemoteConfig(FirebaseRemoteConfigClient client) FirebaseApp app = FirebaseApp.initializeApp(TEST_OPTIONS); return new FirebaseRemoteConfig(app, client); } + + // Get Server template tests + + @Test + public void testGetServerTemplate() throws FirebaseRemoteConfigException { + MockRemoteConfigClient client = + MockRemoteConfigClient.fromServerTemplate( + new ServerTemplateData().setETag(TEST_ETAG).toJSON()); + FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); + + ServerTemplate template = remoteConfig.getServerTemplate(); + String templateData = template.toJson(); + JsonElement expectedJson = + JsonParser.parseString(new ServerTemplateData().setETag(TEST_ETAG).toJSON()); + JsonElement actualJson = JsonParser.parseString(templateData); + + assertEquals(expectedJson, actualJson); + } + + @Test + public void testGetServerTemplateFailure() { + MockRemoteConfigClient client = MockRemoteConfigClient.fromException(TEST_EXCEPTION); + FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); + + try { + remoteConfig.getServerTemplate(); + } catch (FirebaseRemoteConfigException e) { + assertSame(TEST_EXCEPTION, e); + } + } + + @Test + public void testGetServerTemplateAsync() throws Exception { + MockRemoteConfigClient client = + MockRemoteConfigClient.fromServerTemplate( + new ServerTemplateData().setETag(TEST_ETAG).toJSON()); + FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); + + ServerTemplate template = remoteConfig.getServerTemplateAsync().get(); + String templateData = template.toJson(); + JsonElement expectedJson = + JsonParser.parseString(new ServerTemplateData().setETag(TEST_ETAG).toJSON()); + JsonElement actualJson = JsonParser.parseString(templateData); + + assertEquals(expectedJson, actualJson); + } + + @Test + public void testGetServerTemplateAsyncFailure() throws InterruptedException { + MockRemoteConfigClient client = MockRemoteConfigClient.fromException(TEST_EXCEPTION); + FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); + + try { + remoteConfig.getServerTemplateAsync().get(); + } catch (ExecutionException e) { + assertSame(TEST_EXCEPTION, e.getCause()); + } + } } diff --git a/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java b/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java index 9ca58508d..3ac7f6b1c 100644 --- a/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java +++ b/src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java @@ -21,28 +21,35 @@ public class MockRemoteConfigClient implements FirebaseRemoteConfigClient{ private final Template resultTemplate; + private final String resultServerTemplate; private final FirebaseRemoteConfigException exception; private final ListVersionsResponse listVersionsResponse; private MockRemoteConfigClient(Template resultTemplate, - ListVersionsResponse listVersionsResponse, - FirebaseRemoteConfigException exception) { + String resultServerTemplate, + ListVersionsResponse listVersionsResponse, + FirebaseRemoteConfigException exception) { this.resultTemplate = resultTemplate; + this.resultServerTemplate = resultServerTemplate; this.listVersionsResponse = listVersionsResponse; this.exception = exception; } static MockRemoteConfigClient fromTemplate(Template resultTemplate) { - return new MockRemoteConfigClient(resultTemplate, null, null); + return new MockRemoteConfigClient(resultTemplate,null, null, null); + } + + static MockRemoteConfigClient fromServerTemplate(String resultServerTemplate) { + return new MockRemoteConfigClient(null, resultServerTemplate,null, null); } static MockRemoteConfigClient fromListVersionsResponse( ListVersionsResponse listVersionsResponse) { - return new MockRemoteConfigClient(null, listVersionsResponse, null); + return new MockRemoteConfigClient(null,null, listVersionsResponse, null); } static MockRemoteConfigClient fromException(FirebaseRemoteConfigException exception) { - return new MockRemoteConfigClient(null, null, exception); + return new MockRemoteConfigClient(null,null, null, exception); } @Override @@ -53,6 +60,14 @@ public Template getTemplate() throws FirebaseRemoteConfigException { return resultTemplate; } + @Override + public String getServerTemplate() throws FirebaseRemoteConfigException { + if (exception != null) { + throw exception; + } + return resultServerTemplate; + } + @Override public Template getTemplateAtVersion(String versionNumber) throws FirebaseRemoteConfigException { if (exception != null) { diff --git a/src/test/java/com/google/firebase/remoteconfig/ServerConditionTest.java b/src/test/java/com/google/firebase/remoteconfig/ServerConditionTest.java new file mode 100644 index 000000000..6cbece1c0 --- /dev/null +++ b/src/test/java/com/google/firebase/remoteconfig/ServerConditionTest.java @@ -0,0 +1,218 @@ +/* + * Copyright 2025 Google LLC + * + * 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.remoteconfig; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import com.google.common.collect.ImmutableList; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.CustomSignalConditionResponse; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.OneOfConditionResponse; +import com.google.firebase.remoteconfig.internal.ServerTemplateResponse.ServerConditionResponse; +import java.util.ArrayList; +import org.junit.Test; + +/** Tests +* for {@link ServerCondition}. +* */ +public class ServerConditionTest { + + @Test + public void testConstructor() { + OneOfCondition conditions = + new OneOfCondition() + .setOrCondition( + new OrCondition( + ImmutableList.of( + new OneOfCondition() + .setAndCondition( + new AndCondition( + ImmutableList.of( + new OneOfCondition() + .setCustomSignal( + new CustomSignalCondition( + "users", + CustomSignalOperator.NUMERIC_LESS_THAN, + new ArrayList<>( + ImmutableList.of("100")))))))))); + ServerCondition serverCondition = new ServerCondition("ios_en_1", conditions); + + assertEquals("ios_en_1", serverCondition.getName()); + assertEquals(conditions, serverCondition.getCondition()); + } + + @Test + public void testConstructorWithResponse() { + CustomSignalConditionResponse customResponse = + new CustomSignalConditionResponse() + .setKey("test_key") + .setOperator("NUMERIC_EQUAL") + .setTargetValues(ImmutableList.of("1")); + OneOfConditionResponse conditionResponse = + new OneOfConditionResponse().setCustomSignalCondition(customResponse); + ServerConditionResponse response = + new ServerConditionResponse().setName("ios_en_2").setServerCondition(conditionResponse); + ServerCondition serverCondition = new ServerCondition(response); + + assertEquals("ios_en_2", serverCondition.getName()); + assertEquals("test_key", serverCondition.getCondition().getCustomSignal().getCustomSignalKey()); + } + + @Test + public void testIllegalConstructor() { + IllegalArgumentException error = + assertThrows(IllegalArgumentException.class, () -> new ServerCondition(null, null)); + + assertEquals("condition name must not be null or empty", error.getMessage()); + } + + @Test + public void testConstructorWithNullServerConditionResponse() { + assertThrows(NullPointerException.class, () -> new ServerCondition(null)); + } + + @Test + public void testSetNullName() { + OneOfCondition conditions = + new OneOfCondition() + .setOrCondition( + new OrCondition( + ImmutableList.of( + new OneOfCondition() + .setAndCondition( + new AndCondition( + ImmutableList.of( + new OneOfCondition() + .setCustomSignal( + new CustomSignalCondition( + "users", + CustomSignalOperator.NUMERIC_LESS_THAN, + new ArrayList<>( + ImmutableList.of("100")))))))))); + ServerCondition condition = new ServerCondition("ios", conditions); + + IllegalArgumentException error = assertThrows(IllegalArgumentException.class, + () -> condition.setName(null)); + + assertEquals("condition name must not be null or empty", error.getMessage()); + } + + @Test + public void testSetEmptyName() { + OneOfCondition conditions = + new OneOfCondition() + .setOrCondition( + new OrCondition( + ImmutableList.of( + new OneOfCondition() + .setAndCondition( + new AndCondition( + ImmutableList.of( + new OneOfCondition() + .setCustomSignal( + new CustomSignalCondition( + "users", + CustomSignalOperator.NUMERIC_LESS_THAN, + new ArrayList<>( + ImmutableList.of("100")))))))))); + ServerCondition condition = new ServerCondition("ios", conditions); + + IllegalArgumentException error = assertThrows(IllegalArgumentException.class, + () -> condition.setName("")); + + assertEquals("condition name must not be null or empty", error.getMessage()); + } + + @Test + public void testSetNullServerCondition() { + OneOfCondition conditions = + new OneOfCondition() + .setOrCondition( + new OrCondition( + ImmutableList.of( + new OneOfCondition() + .setAndCondition( + new AndCondition( + ImmutableList.of( + new OneOfCondition() + .setCustomSignal( + new CustomSignalCondition( + "users", + CustomSignalOperator.NUMERIC_LESS_THAN, + new ArrayList<>( + ImmutableList.of("100")))))))))); + ServerCondition condition = new ServerCondition("ios", conditions); + + assertThrows(NullPointerException.class, () -> condition.setServerCondition(null)); + } + + @Test + public void testEquality() { + OneOfCondition conditionOne = + new OneOfCondition() + .setOrCondition( + new OrCondition( + ImmutableList.of( + new OneOfCondition() + .setAndCondition( + new AndCondition( + ImmutableList.of( + new OneOfCondition() + .setCustomSignal( + new CustomSignalCondition( + "users", + CustomSignalOperator.NUMERIC_LESS_THAN, + new ArrayList<>( + ImmutableList.of("100")))))))))); + OneOfCondition conditionTwo = + new OneOfCondition() + .setOrCondition( + new OrCondition( + ImmutableList.of( + new OneOfCondition() + .setAndCondition( + new AndCondition( + ImmutableList.of( + new OneOfCondition() + .setCustomSignal( + new CustomSignalCondition( + "users", + CustomSignalOperator.NUMERIC_LESS_THAN, + new ArrayList<>(ImmutableList.of("100")))), + new OneOfCondition() + .setCustomSignal( + new CustomSignalCondition( + "users", + CustomSignalOperator + .NUMERIC_GREATER_THAN, + new ArrayList<>(ImmutableList.of("20")))), + new OneOfCondition() + .setPercent( + new PercentCondition( + new MicroPercentRange(25000000, 100000000), + PercentConditionOperator.BETWEEN, + "cla24qoibb61")))))))); + + final ServerCondition serverConditionOne = new ServerCondition("ios", conditionOne); + final ServerCondition serverConditionTwo = new ServerCondition("ios", conditionOne); + final ServerCondition serverConditionThree = new ServerCondition("android", conditionTwo); + final ServerCondition serverConditionFour = new ServerCondition("android", conditionTwo); + + assertEquals(serverConditionOne, serverConditionTwo); + assertEquals(serverConditionThree, serverConditionFour); + } +} diff --git a/src/test/java/com/google/firebase/remoteconfig/ServerTemplateImplTest.java b/src/test/java/com/google/firebase/remoteconfig/ServerTemplateImplTest.java new file mode 100644 index 000000000..bafe84331 --- /dev/null +++ b/src/test/java/com/google/firebase/remoteconfig/ServerTemplateImplTest.java @@ -0,0 +1,421 @@ +/* + * Copyright 2025 Google LLC + * + * 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.remoteconfig; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import com.google.api.core.ApiFuture; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import com.google.firebase.auth.MockGoogleCredentials; +import com.google.firebase.testing.TestUtils; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import org.junit.BeforeClass; +import org.junit.Test; + +/** Tests +* for {@link ServerTemplateImpl}. +* */ +public class ServerTemplateImplTest { + + private static final FirebaseOptions TEST_OPTIONS = + FirebaseOptions.builder() + .setCredentials(new MockGoogleCredentials("test-token")) + .setProjectId("test-project") + .build(); + + private static String cacheTemplate; + + @BeforeClass + public static void setUpClass() { + cacheTemplate = TestUtils.loadResource("getServerTemplateData.json"); + } + + @Test + public void testServerTemplateWithoutCacheValueThrowsException() + throws FirebaseRemoteConfigException { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + + IllegalArgumentException error = + assertThrows( + IllegalArgumentException.class, + () -> new ServerTemplateImpl.Builder(null).defaultConfig(defaultConfig).build()); + + assertEquals("JSON String must not be null or empty.", error.getMessage()); + } + + @Test + public void testEvaluateWithoutContextReturnsDefaultValue() throws FirebaseRemoteConfigException { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(); + + assertEquals("Default value", evaluatedConfig.getString("Custom")); + } + + @Test + public void testEvaluateCustomSignalReturnsDefaultValue() throws FirebaseRemoteConfigException { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().put("users", "100").build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("Default value", evaluatedConfig.getString("Custom")); + } + + @Test + public void testEvaluateCustomSignalReturnsConditionalValue() + throws FirebaseRemoteConfigException { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().put("users", "99").build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("Conditional value", evaluatedConfig.getString("Custom")); + } + + @Test + public void testEvaluateCustomSignalWithoutContextReturnsDefaultValue() + throws FirebaseRemoteConfigException { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("Default value", evaluatedConfig.getString("Custom")); + } + + @Test + public void testEvaluateCustomSignalWithInvalidContextReturnsDefaultValue() + throws FirebaseRemoteConfigException { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().put("users", "abc").build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("Default value", evaluatedConfig.getString("Custom")); + } + + @Test + public void testEvaluatePercentWithoutRandomizationIdReturnsDefaultValue() + throws FirebaseRemoteConfigException { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("Default value", evaluatedConfig.getString("Percent")); + } + + @Test + public void testEvaluatePercentReturnsConditionalValue() throws FirebaseRemoteConfigException { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().put("randomizationId", "user").build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("Conditional value", evaluatedConfig.getString("Percent")); + } + + @Test + public void testEvaluateWithoutDefaultValueReturnsEmptyString() + throws FirebaseRemoteConfigException { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("", evaluatedConfig.getString("Unset default value")); + } + + @Test + public void testEvaluateWithInvalidCacheValueThrowsException() + throws FirebaseRemoteConfigException { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().build(); + String invalidJsonString = "abc"; + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(invalidJsonString) + .build(); + + FirebaseRemoteConfigException error = + assertThrows(FirebaseRemoteConfigException.class, () -> template.evaluate(context)); + + assertEquals( + "No Remote Config Server template in cache. Call load() before " + "calling evaluate().", + error.getMessage()); + } + + @Test + public void testEvaluateWithInAppDefaultReturnsEmptyString() throws Exception { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("", evaluatedConfig.getString("In-app default")); + } + + @Test + public void testEvaluateWithDerivedInAppDefaultReturnsDefaultValue() throws Exception { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("Default value", evaluatedConfig.getString("Derived in-app default")); + } + + @Test + public void testEvaluateWithMultipleConditionReturnsConditionalValue() throws Exception { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().put("users", "99").build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("Conditional value 1", evaluatedConfig.getString("Multiple conditions")); + } + + @Test + public void testEvaluateWithChainedAndConditionReturnsDefaultValue() throws Exception { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = + new KeysAndValues.Builder() + .put("users", "100") + .put("premium users", 20) + .put("randomizationId", "user") + .build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("Default value", evaluatedConfig.getString("Chained conditions")); + } + + @Test + public void testEvaluateWithChainedAndConditionReturnsConditionalValue() throws Exception { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = + new KeysAndValues.Builder().put("users", "99").put("premium users", "30").build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals("Conditional value", evaluatedConfig.getString("Chained conditions")); + } + + @Test + public void testGetEvaluateConfigOnInvalidTypeReturnsDefaultValue() throws Exception { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().put("randomizationId", "user").build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals(0L, evaluatedConfig.getLong("Percent")); + } + + @Test + public void testGetEvaluateConfigInvalidKeyReturnsStaticValueSource() throws Exception { + KeysAndValues defaultConfig = new KeysAndValues.Builder().build(); + KeysAndValues context = new KeysAndValues.Builder().build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals(ValueSource.STATIC, evaluatedConfig.getValueSource("invalid")); + } + + @Test + public void testGetEvaluateConfigInAppDefaultConfigReturnsDefaultValueSource() throws Exception { + KeysAndValues defaultConfig = new KeysAndValues.Builder().put("In-app default", "abc").build(); + KeysAndValues context = new KeysAndValues.Builder().build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals(ValueSource.DEFAULT, evaluatedConfig.getValueSource("In-app default")); + } + + @Test + public void testGetEvaluateConfigUnsetDefaultConfigReturnsDefaultValueSource() throws Exception { + KeysAndValues defaultConfig = + new KeysAndValues.Builder().put("Unset default config", "abc").build(); + KeysAndValues context = new KeysAndValues.Builder().build(); + ServerTemplate template = + new ServerTemplateImpl.Builder(null) + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) + .build(); + + ServerConfig evaluatedConfig = template.evaluate(context); + + assertEquals(ValueSource.DEFAULT, evaluatedConfig.getValueSource("Unset default config")); + } + + private static final String TEST_ETAG = "etag-123456789012-1"; + + private FirebaseRemoteConfig getRemoteConfig(FirebaseRemoteConfigClient client) { + FirebaseApp app = FirebaseApp.initializeApp(TEST_OPTIONS, "test-app"); + return new FirebaseRemoteConfig(app, client); + } + + @Test + public void testLoad() throws Exception { + // 1. Define the template data that the mock client will return. + // This is the EXPECTED state after `load()` is called. + final String expectedTemplateJsonAfterLoad = + new ServerTemplateData().setETag(TEST_ETAG).toJSON(); + + // 2. Mock the HTTP client to return the predefined response. + MockRemoteConfigClient client = + MockRemoteConfigClient.fromServerTemplate(expectedTemplateJsonAfterLoad); + FirebaseRemoteConfig remoteConfig = getRemoteConfig(client); + + // 3. Build the template instance. + // It's initialized with a complex `cacheTemplate` to ensure `load()` properly + // overwrites it. + KeysAndValues defaultConfig = + new KeysAndValues.Builder().put("Unset default config", "abc").build(); + ServerTemplate template = + remoteConfig + .serverTemplateBuilder() + .defaultConfig(defaultConfig) + .cachedTemplate(cacheTemplate) // This is the initial state before `load()` + .build(); + + // 4. Call the load method, which fetches the new template from the mock client. + ApiFuture loadFuture = template.load(); + loadFuture.get(); // Wait for the async operation to complete. + + // 5. Get the ACTUAL state of the template after `load()` has executed. + String actualJsonAfterLoad = template.toJson(); + + // 6. Assert that the template's state has been updated to match what the mock + // client returned. + // Parsing to JsonElement performs a deep, order-insensitive comparison. + JsonElement expectedJson = JsonParser.parseString(expectedTemplateJsonAfterLoad); + JsonElement actualJson = JsonParser.parseString(actualJsonAfterLoad); + + assertEquals(expectedJson, actualJson); + } + + @Test + public void testBuilderParsesCachedTemplateCorrectly() throws FirebaseRemoteConfigException { + // Arrange: + // 1. Create a canonical JSON string by parsing the input file and then + // re-serializing it. This gives us the precise expected output format, + // accounting for any formatting or default value differences. + ServerTemplateData canonicalData = ServerTemplateData.fromJSON(cacheTemplate); + String expectedJsonString = canonicalData.toJSON(); + + // Act: + // 2. Build a ServerTemplate instance from the original cached JSON string, + // which triggers the parsing logic we want to test. + ServerTemplate template = + new ServerTemplateImpl.Builder(null).cachedTemplate(cacheTemplate).build(); + + // Assert: + // 3. Compare the JSON from the newly built template against the canonical + // version. + // This verifies that the internal state was parsed and stored correctly. + // Using JsonElement ensures the comparison is not affected by key order. + JsonElement expectedJsonTree = JsonParser.parseString(expectedJsonString); + JsonElement actualJsonTree = JsonParser.parseString(template.toJson()); + + assertEquals(expectedJsonTree, actualJsonTree); + } +} diff --git a/src/test/java/com/google/firebase/remoteconfig/ValueTest.java b/src/test/java/com/google/firebase/remoteconfig/ValueTest.java new file mode 100644 index 000000000..c1822b063 --- /dev/null +++ b/src/test/java/com/google/firebase/remoteconfig/ValueTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2025 Google LLC + * + * 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.remoteconfig; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class ValueTest { + @Test + public void testGetSourceReturnsValueSource() { + Value value = new Value(ValueSource.STATIC); + assertEquals(value.getSource(), ValueSource.STATIC); + } + + @Test + public void testAsStringReturnsValueAsString() { + Value value = new Value(ValueSource.STATIC, "sample-string"); + assertEquals(value.asString(), "sample-string"); + } + + @Test + public void testAsStringReturnsDefaultEmptyString() { + Value value = new Value(ValueSource.STATIC); + assertEquals(value.asString(), ""); + } + + @Test + public void testAsLongReturnsDefaultValueForStaticSource() { + Value value = new Value(ValueSource.STATIC); + assertEquals(value.asLong(), 0L); + } + + @Test + public void testAsLongReturnsDefaultValueForInvalidSourceValue() { + Value value = new Value(ValueSource.REMOTE, "sample-string"); + assertEquals(value.asLong(), 0L); + } + + @Test + public void testAsLongReturnsSourceValueAsLong() { + Value value = new Value(ValueSource.REMOTE, "123"); + assertEquals(value.asLong(), 123L); + } + + @Test + public void testAsDoubleReturnsDefaultValueForStaticSource() { + Value value = new Value(ValueSource.STATIC); + assertEquals(value.asDouble(), 0, 0); + } + + @Test + public void testAsDoubleReturnsDefaultValueForInvalidSourceValue() { + Value value = new Value(ValueSource.REMOTE, "sample-string"); + assertEquals(value.asDouble(), 0, 0); + } + + @Test + public void testAsDoubleReturnsSourceValueAsDouble() { + Value value = new Value(ValueSource.REMOTE, "123.34"); + assertEquals(value.asDouble(), 123.34, 0); + } + + @Test + public void testAsBooleanReturnsDefaultValueForStaticSource() { + Value value = new Value(ValueSource.STATIC); + assertFalse(value.asBoolean()); + } + + @Test + public void testAsBooleanReturnsDefaultValueForInvalidSourceValue() { + Value value = new Value(ValueSource.REMOTE, "sample-string"); + assertFalse(value.asBoolean()); + } + + @Test + public void testAsBooleanReturnsSourceValueAsBoolean() { + Value value = new Value(ValueSource.REMOTE, "1"); + assertTrue(value.asBoolean()); + } + + @Test + public void testAsBooleanReturnsSourceValueYesAsBoolean() { + Value value = new Value(ValueSource.REMOTE, "YeS"); + assertTrue(value.asBoolean()); + } +} + \ No newline at end of file diff --git a/src/test/java/com/google/firebase/remoteconfig/VersionTest.java b/src/test/java/com/google/firebase/remoteconfig/VersionTest.java index 515629660..1e524195f 100644 --- a/src/test/java/com/google/firebase/remoteconfig/VersionTest.java +++ b/src/test/java/com/google/firebase/remoteconfig/VersionTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import com.google.firebase.remoteconfig.internal.TemplateResponse; import com.google.firebase.remoteconfig.internal.TemplateResponse.VersionResponse; @@ -27,15 +28,16 @@ public class VersionTest { - @Test(expected = NullPointerException.class) + @Test public void testConstructorWithNullVersionResponse() { - new Version(null); + assertThrows(NullPointerException.class, () -> new Version(null)); } - @Test(expected = IllegalStateException.class) + @Test public void testConstructorWithInvalidUpdateTime() { - new Version(new VersionResponse() - .setUpdateTime("sunday,26th")); + assertThrows(IllegalStateException.class, () -> + new Version(new VersionResponse().setUpdateTime("sunday,26th"))); + } @Test diff --git a/src/test/resources/getServerRemoteConfig.json b/src/test/resources/getServerRemoteConfig.json new file mode 100644 index 000000000..e5cb1a9eb --- /dev/null +++ b/src/test/resources/getServerRemoteConfig.json @@ -0,0 +1,142 @@ +{ + "conditions": [ + { + "name": "custom_signal", + "condition": { + "orCondition": { + "conditions": [ + { + "andCondition": { + "conditions": [ + { + "customSignal": { + "customSignalOperator": "NUMERIC_LESS_THAN", + "customSignalKey": "users", + "targetCustomSignalValues": [ + "100" + ] + } + } + ] + } + } + ] + } + } + }, + { + "name": "percent", + "condition": { + "orCondition": { + "conditions": [ + { + "andCondition": { + "conditions": [ + { + "percent": { + "percentOperator": "BETWEEN", + "seed": "3maarirs9xzs", + "microPercentRange": { + "microPercentLowerBound": 12000000, + "microPercentUpperBound": 100000000 + } + } + } + ] + } + } + ] + } + } + }, + { + "name": "chained_conditions", + "condition": { + "orCondition": { + "conditions": [ + { + "andCondition": { + "conditions": [ + { + "customSignal": { + "customSignalOperator": "NUMERIC_LESS_THAN", + "customSignalKey": "users", + "targetCustomSignalValues": [ + "100" + ] + } + }, + { + "customSignal": { + "customSignalOperator": "NUMERIC_GREATER_THAN", + "customSignalKey": "premium users", + "targetCustomSignalValues": [ + "20" + ] + } + }, + { + "percent": { + "percentOperator": "BETWEEN", + "seed": "cla24qoibb61", + "microPercentRange": { + "microPercentLowerBound": 25000000, + "microPercentUpperBound": 100000000 + } + } + } + ] + } + } + ] + } + } + } + ], + "parameters": { + "welcome_message_text": { + "defaultValue": { + "value": "welcome to app" + }, + "conditionalValues": { + "ios_en": { + "value": "welcome to app en" + } + }, + "description": "text for welcome message!", + "valueType": "STRING" + }, + "header_text": { + "defaultValue": { + "useInAppDefault": true + }, + "valueType": "STRING" + } + }, + "parameterGroups": { + "new menu": { + "description": "New Menu", + "parameters": { + "pumpkin_spice_season": { + "defaultValue": { + "value": "true" + }, + "description": "Whether it's currently pumpkin spice season.", + "valueType": "BOOLEAN" + } + } + } + }, + "version": { + "versionNumber": "17", + "updateOrigin": "ADMIN_SDK_NODE", + "updateType": "INCREMENTAL_UPDATE", + "updateUser": { + "email": "firebase-user@account.com", + "name": "dev-admin", + "imageUrl": "http://image.jpg" + }, + "updateTime": "2020-11-15T06:57:26.342763941Z", + "description": "promo config" + } +} \ No newline at end of file diff --git a/src/test/resources/getServerTemplateData.json b/src/test/resources/getServerTemplateData.json new file mode 100644 index 000000000..c26f74185 --- /dev/null +++ b/src/test/resources/getServerTemplateData.json @@ -0,0 +1,181 @@ +{ + "conditions": [ + { + "name": "custom_signal", + "condition": { + "orCondition": { + "conditions": [ + { + "andCondition": { + "conditions": [ + { + "customSignal": { + "customSignalOperator": "NUMERIC_LESS_THAN", + "customSignalKey": "users", + "targetCustomSignalValues": [ + "100" + ] + } + } + ] + } + } + ] + } + } + }, + { + "name": "percent", + "condition": { + "orCondition": { + "conditions": [ + { + "andCondition": { + "conditions": [ + { + "percent": { + "percentOperator": "BETWEEN", + "seed": "3maarirs9xzs", + "microPercentRange": { + "microPercentLowerBound": 12000000, + "microPercentUpperBound": 100000000 + } + } + } + ] + } + } + ] + } + } + }, + { + "name": "chained_conditions", + "condition": { + "orCondition": { + "conditions": [ + { + "andCondition": { + "conditions": [ + { + "customSignal": { + "customSignalOperator": "NUMERIC_LESS_THAN", + "customSignalKey": "users", + "targetCustomSignalValues": [ + "100" + ] + } + }, + { + "customSignal": { + "customSignalOperator": "NUMERIC_GREATER_THAN", + "customSignalKey": "premium users", + "targetCustomSignalValues": [ + "20" + ] + }, + "percent": { + "percentOperator": "BETWEEN", + "seed": "cla24qoibb61", + "microPercentRange": { + "microPercentLowerBound": 25000000, + "microPercentUpperBound": 100000000 + } + } + } + ] + } + } + ] + } + } + } + ], + "parameters": { + "Percent": { + "defaultValue": { + "value": "Default value" + }, + "conditionalValues": { + "percent": { + "value": "Conditional value" + } + } + }, + "Custom": { + "defaultValue": { + "value": "Default value" + }, + "conditionalValues": { + "custom_signal": { + "value": "Conditional value" + } + } + }, + "Welcome Message": { + "defaultValue": { + "value": "Welcome" + } + }, + "Unset default value": { + "defaultValue": { + "value": "" + } + }, + "In-app default": { + "defaultValue": { + "useInAppDefault": true + }, + "conditionalValues": { + "percent": { + "value": "Conditional value" + } + } + }, + "Derived in-app default": { + "defaultValue": { + "value": "Default value" + }, + "conditionalValues": { + "percent": { + "useInAppDefault": true + } + } + }, + "Multiple conditions": { + "defaultValue": { + "value": "Default value" + }, + "conditionalValues": { + "custom_signal": { + "value": "Conditional value 1" + }, + "percent": { + "value": "Conditional value 2" + } + } + }, + "Chained conditions": { + "defaultValue": { + "value": "Default value" + }, + "conditionalValues": { + "chained_conditions": { + "value": "Conditional value" + } + } + } + }, + "version": { + "versionNumber": "17", + "updateOrigin": "ADMIN_SDK_NODE", + "updateType": "INCREMENTAL_UPDATE", + "updateUser": { + "email": "firebase-user@account.com", + "name": "dev-admin", + "imageUrl": "http://image.jpg" + }, + "updateTime": "2020-11-15T06:57:26.342763941Z", + "description": "promo config" + } +} \ No newline at end of file From c141def8c6803021e50ec473934d5aa34f187c36 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 15:18:41 -0400 Subject: [PATCH 262/269] chore(deps): Bump org.apache.maven.plugins:maven-surefire-plugin (#1133) Bumps [org.apache.maven.plugins:maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.5.3 to 3.5.4. - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.5.3...surefire-3.5.4) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-surefire-plugin dependency-version: 3.5.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Lahiru Maramba --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 91c203218..30a898622 100644 --- a/pom.xml +++ b/pom.xml @@ -273,7 +273,7 @@ maven-surefire-plugin - 3.5.3 + 3.5.4 ${skipUTs} From 3737fe2fe9cee4190d97bcf5f628787dfd1308c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 16:54:15 -0400 Subject: [PATCH 263/269] chore(deps): Bump netty.version from 4.2.5.Final to 4.2.6.Final (#1131) Bumps `netty.version` from 4.2.5.Final to 4.2.6.Final. Updates `io.netty:netty-codec-http` from 4.2.5.Final to 4.2.6.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.5.Final...netty-4.2.6.Final) Updates `io.netty:netty-handler` from 4.2.5.Final to 4.2.6.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.5.Final...netty-4.2.6.Final) Updates `io.netty:netty-transport` from 4.2.5.Final to 4.2.6.Final - [Commits](https://github.com/netty/netty/compare/netty-4.2.5.Final...netty-4.2.6.Final) --- updated-dependencies: - dependency-name: io.netty:netty-codec-http dependency-version: 4.2.6.Final dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-handler dependency-version: 4.2.6.Final dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.netty:netty-transport dependency-version: 4.2.6.Final dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Lahiru Maramba --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 30a898622..d4a8e3db8 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.2.5.Final + 4.2.6.Final From 800298c46df2d77eaa089e0cfcfcf48c1b3b19fa Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Wed, 24 Sep 2025 11:40:55 -0400 Subject: [PATCH 264/269] [chore] Release 9.7.0 (#1136) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d4a8e3db8..e6fdd2baa 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 9.6.0 + 9.7.0 jar firebase-admin From 27af418713393604fd82d8022246bf3a3a0944b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 20:30:38 +0000 Subject: [PATCH 265/269] chore(deps): Bump com.google.cloud:libraries-bom from 26.67.0 to 26.71.0 (#1146) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e6fdd2baa..c4998f31b 100644 --- a/pom.xml +++ b/pom.xml @@ -378,7 +378,7 @@ com.google.cloud libraries-bom - 26.67.0 + 26.71.0 pom import From 1e3bddc2fedcab9cf9b02918b88200097c99648a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 20:35:14 +0000 Subject: [PATCH 266/269] chore(deps): Bump org.apache.maven.plugins:maven-javadoc-plugin (#1141) --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index c4998f31b..1c0d9a40a 100644 --- a/pom.xml +++ b/pom.xml @@ -82,7 +82,7 @@ maven-javadoc-plugin - 3.11.3 + 3.12.0 site @@ -294,7 +294,7 @@ maven-javadoc-plugin - 3.11.3 + 3.12.0 attach-javadocs From 88381d8517e7a847b723a3f401cbe01017891abe Mon Sep 17 00:00:00 2001 From: Jos Date: Wed, 19 Nov 2025 16:53:05 +0100 Subject: [PATCH 267/269] fix(fcm): Preserve unmapped `TopicManagementResponse` error reasons (#1073) * RESOURCE_EXHAUSTED is a possible error code as well, given https://developers.google.com/instance-id/reference/server * If the reason is not in the predefined ERROR_CODES it would be more helpful to use the reason String that came in as an parameter than UNKNOWN_ERROR. UNKNOWN_ERROR could be used for null or empty Strings. * For consistency with the current format of the error reasons, logic is added to map them from UPPER_SNAKE_CASE to lower-kebab-case. Entries from the ERROR_CODES that can be computed this way are removed from the map. Extra tests are added for those 3 cases. --- .../messaging/TopicManagementResponse.java | 9 ++-- .../messaging/InstanceIdClientImplTest.java | 50 ++++++++++++++++++- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/google/firebase/messaging/TopicManagementResponse.java b/src/main/java/com/google/firebase/messaging/TopicManagementResponse.java index bbf4c944a..9f75f5871 100644 --- a/src/main/java/com/google/firebase/messaging/TopicManagementResponse.java +++ b/src/main/java/com/google/firebase/messaging/TopicManagementResponse.java @@ -37,10 +37,8 @@ public class TopicManagementResponse { // Server error codes as defined in https://developers.google.com/instance-id/reference/server // TODO: Should we handle other error codes here (e.g. PERMISSION_DENIED)? private static final Map ERROR_CODES = ImmutableMap.builder() - .put("INVALID_ARGUMENT", "invalid-argument") .put("NOT_FOUND", "registration-token-not-registered") .put("INTERNAL", "internal-error") - .put("TOO_MANY_TOPICS", "too-many-topics") .build(); private final int successCount; @@ -101,8 +99,11 @@ public static class Error { private Error(int index, String reason) { this.index = index; - this.reason = ERROR_CODES.containsKey(reason) - ? ERROR_CODES.get(reason) : UNKNOWN_ERROR; + if (reason == null || reason.trim().isEmpty()) { + this.reason = UNKNOWN_ERROR; + } else { + this.reason = ERROR_CODES.getOrDefault(reason, reason.toLowerCase().replace('_', '-')); + } } /** diff --git a/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java b/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java index d0151bb94..a6f1c7543 100644 --- a/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java +++ b/src/test/java/com/google/firebase/messaging/InstanceIdClientImplTest.java @@ -404,7 +404,30 @@ public void testTopicManagementResponseWithEmptyList() { @Test public void testTopicManagementResponseErrorToString() { - GenericJson json = new GenericJson().set("error", "test error"); + GenericJson json = new GenericJson().set("error", "INVALID_ARGUMENT"); + ImmutableList jsonList = ImmutableList.of(json); + + TopicManagementResponse topicManagementResponse = new TopicManagementResponse(jsonList); + + String expected = "[Error{index=0, reason=invalid-argument}]"; + assertEquals(expected, topicManagementResponse.getErrors().toString()); + } + + @Test + public void testTopicManagementResponseErrorNotInErrorCodes() { + String myError = "MY_ERROR"; + GenericJson json = new GenericJson().set("error", myError); + ImmutableList jsonList = ImmutableList.of(json); + + TopicManagementResponse topicManagementResponse = new TopicManagementResponse(jsonList); + + String expected = "[Error{index=0, reason=my-error}]"; + assertEquals(expected, topicManagementResponse.getErrors().toString()); + } + + @Test + public void testTopicManagementResponseErrorUnknown() { + GenericJson json = new GenericJson().set("error", ""); ImmutableList jsonList = ImmutableList.of(json); TopicManagementResponse topicManagementResponse = new TopicManagementResponse(jsonList); @@ -413,6 +436,29 @@ public void testTopicManagementResponseErrorToString() { assertEquals(expected, topicManagementResponse.getErrors().toString()); } + @Test + public void testTopicManagementResponseErrorResourceExhausted() { + GenericJson json = new GenericJson().set("error", "RESOURCE_EXHAUSTED"); + ImmutableList jsonList = ImmutableList.of(json); + + TopicManagementResponse topicManagementResponse = new TopicManagementResponse(jsonList); + + String expected = "[Error{index=0, reason=resource-exhausted}]"; + assertEquals(expected, topicManagementResponse.getErrors().toString()); + } + + @Test + public void testTopicManagementResponseErrorTooManyTopics() { + GenericJson json = new GenericJson().set("error", "TOO_MANY_TOPICS"); + ImmutableList jsonList = ImmutableList.of(json); + + TopicManagementResponse topicManagementResponse = new TopicManagementResponse(jsonList); + + String expected = "[Error{index=0, reason=too-many-topics}]"; + assertEquals(expected, topicManagementResponse.getErrors().toString()); + } + + private static InstanceIdClientImpl initInstanceIdClient( final MockLowLevelHttpResponse mockResponse, final HttpResponseInterceptor interceptor) { @@ -432,7 +478,7 @@ private void checkTopicManagementRequest( assertEquals(1, result.getFailureCount()); assertEquals(1, result.getErrors().size()); assertEquals(1, result.getErrors().get(0).getIndex()); - assertEquals("unknown-error", result.getErrors().get(0).getReason()); + assertEquals("error-reason", result.getErrors().get(0).getReason()); ByteArrayOutputStream out = new ByteArrayOutputStream(); request.getContent().writeTo(out); From 029185574a0028cc4080df607d3f4c2254875979 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:42:37 +0000 Subject: [PATCH 268/269] chore(deps): Bump netty.version from 4.2.6.Final to 4.2.7.Final (#1147) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1c0d9a40a..324dbd935 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.2.6.Final + 4.2.7.Final From eee14b47ffb7bba7aeb4dafed85f77677f98e937 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:51:37 +0000 Subject: [PATCH 269/269] chore(deps): Bump org.codehaus.mojo:exec-maven-plugin (#1148) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 324dbd935..275a7830d 100644 --- a/pom.xml +++ b/pom.xml @@ -208,7 +208,7 @@ org.codehaus.mojo exec-maven-plugin - 3.5.1 + 3.6.2 test