From 062223d527c9f30aaca033a3b1dfe174f97f07b2 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Mon, 31 Aug 2020 10:51:26 -0700 Subject: [PATCH 001/390] fix(auth): Support verifying tenant ID tokens in FirebaseAuth (#475) * fix(auth): Support verifying tenant ID tokens in FirebaseAuth * fix: Fixing the error message for null tenant ID --- .../auth/FirebaseTokenVerifierImpl.java | 8 ++-- .../com/google/firebase/FirebaseAppTest.java | 26 ++++++------- .../auth/FirebaseTokenVerifierImplTest.java | 38 +++++++++++++++++-- .../TenantAwareFirebaseAuthIT.java | 5 +++ 4 files changed, 56 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java b/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java index 273ec1532..bbe741f5b 100644 --- a/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java +++ b/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java @@ -71,7 +71,7 @@ private FirebaseTokenVerifierImpl(Builder builder) { this.docUrl = builder.docUrl; this.invalidTokenErrorCode = checkNotNull(builder.invalidTokenErrorCode); this.expiredTokenErrorCode = checkNotNull(builder.expiredTokenErrorCode); - this.tenantId = Strings.nullToEmpty(builder.tenantId); + this.tenantId = builder.tenantId; } /** @@ -323,11 +323,11 @@ private boolean containsLegacyUidField(IdToken.Payload payload) { } private void checkTenantId(final FirebaseToken firebaseToken) throws FirebaseAuthException { - String tokenTenantId = Strings.nullToEmpty(firebaseToken.getTenantId()); - if (!this.tenantId.equals(tokenTenantId)) { + String tokenTenantId = firebaseToken.getTenantId(); + if (this.tenantId != null && !this.tenantId.equals(tokenTenantId)) { String message = String.format( "The tenant ID ('%s') of the token did not match the expected value ('%s')", - tokenTenantId, + Strings.nullToEmpty(tokenTenantId), tenantId); throw newException(message, AuthErrorCode.TENANT_ID_MISMATCH); } diff --git a/src/test/java/com/google/firebase/FirebaseAppTest.java b/src/test/java/com/google/firebase/FirebaseAppTest.java index d481e3c0e..26ca93432 100644 --- a/src/test/java/com/google/firebase/FirebaseAppTest.java +++ b/src/test/java/com/google/firebase/FirebaseAppTest.java @@ -66,7 +66,7 @@ import org.junit.Test; import org.mockito.Mockito; -/** +/** * Unit tests for {@link com.google.firebase.FirebaseApp}. */ public class FirebaseAppTest { @@ -472,8 +472,8 @@ public void testAppWithAuthVariableOverrides() { public void testEmptyFirebaseConfigFile() { setFirebaseConfigEnvironmentVariable("firebase_config_empty.json"); FirebaseApp.initializeApp(); - } - + } + @Test public void testEmptyFirebaseConfigString() { setFirebaseConfigEnvironmentVariable(""); @@ -481,7 +481,7 @@ public void testEmptyFirebaseConfigString() { assertNull(firebaseApp.getOptions().getProjectId()); assertNull(firebaseApp.getOptions().getStorageBucket()); assertNull(firebaseApp.getOptions().getDatabaseUrl()); - assertTrue(firebaseApp.getOptions().getDatabaseAuthVariableOverride().isEmpty()); + assertTrue(firebaseApp.getOptions().getDatabaseAuthVariableOverride().isEmpty()); } @Test @@ -542,20 +542,20 @@ public void testEnvironmentVariableIgnored() { @Test public void testValidFirebaseConfigString() { - setFirebaseConfigEnvironmentVariable("{" - + "\"databaseAuthVariableOverride\": {" - + "\"uid\":" - + "\"testuser\"" - + "}," - + "\"databaseUrl\": \"https://hipster-chat.firebaseio.mock\"," - + "\"projectId\": \"hipster-chat-mock\"," - + "\"storageBucket\": \"hipster-chat.appspot.mock\"" + setFirebaseConfigEnvironmentVariable("{" + + "\"databaseAuthVariableOverride\": {" + + "\"uid\":" + + "\"testuser\"" + + "}," + + "\"databaseUrl\": \"https://hipster-chat.firebaseio.mock\"," + + "\"projectId\": \"hipster-chat-mock\"," + + "\"storageBucket\": \"hipster-chat.appspot.mock\"" + "}"); FirebaseApp firebaseApp = FirebaseApp.initializeApp(); assertEquals("hipster-chat-mock", firebaseApp.getOptions().getProjectId()); assertEquals("hipster-chat.appspot.mock", firebaseApp.getOptions().getStorageBucket()); assertEquals("https://hipster-chat.firebaseio.mock", firebaseApp.getOptions().getDatabaseUrl()); - assertEquals("testuser", + assertEquals("testuser", firebaseApp.getOptions().getDatabaseAuthVariableOverride().get("uid")); } diff --git a/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java b/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java index 833e2ed95..189cb223e 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseTokenVerifierImplTest.java @@ -328,6 +328,17 @@ public void testMalformedToken() { @Test public void testVerifyTokenWithTenantId() throws FirebaseAuthException { + FirebaseTokenVerifierImpl verifier = fullyPopulatedBuilder().build(); + + FirebaseToken firebaseToken = verifier.verifyToken(createTokenWithTenantId("TENANT_1")); + + assertEquals(TEST_TOKEN_ISSUER, firebaseToken.getIssuer()); + assertEquals(TestTokenFactory.UID, firebaseToken.getUid()); + assertEquals("TENANT_1", firebaseToken.getTenantId()); + } + + @Test + public void testVerifyTokenWithMatchingTenantId() throws FirebaseAuthException { FirebaseTokenVerifierImpl verifier = fullyPopulatedBuilder() .setTenantId("TENANT_1") .build(); @@ -341,11 +352,13 @@ public void testVerifyTokenWithTenantId() throws FirebaseAuthException { @Test public void testVerifyTokenDifferentTenantIds() { - try { - fullyPopulatedBuilder() + FirebaseTokenVerifierImpl verifier = fullyPopulatedBuilder() .setTenantId("TENANT_1") - .build() - .verifyToken(createTokenWithTenantId("TENANT_2")); + .build(); + String token = createTokenWithTenantId("TENANT_2"); + + try { + verifier.verifyToken(token); } catch (FirebaseAuthException e) { assertEquals(AuthErrorCode.TENANT_ID_MISMATCH, e.getAuthErrorCode()); assertEquals( @@ -354,6 +367,23 @@ public void testVerifyTokenDifferentTenantIds() { } } + @Test + public void testVerifyTokenNoTenantId() { + FirebaseTokenVerifierImpl verifier = fullyPopulatedBuilder() + .setTenantId("TENANT_1") + .build(); + String token = tokenFactory.createToken(); + + try { + verifier.verifyToken(token); + } catch (FirebaseAuthException e) { + assertEquals(AuthErrorCode.TENANT_ID_MISMATCH, e.getAuthErrorCode()); + assertEquals( + "The tenant ID ('') of the token did not match the expected value ('TENANT_1')", + e.getMessage()); + } + } + @Test public void testVerifyTokenMissingTenantId() { try { diff --git a/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthIT.java b/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthIT.java index fd8e4af83..1ebac213f 100644 --- a/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthIT.java +++ b/src/test/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuthIT.java @@ -261,6 +261,11 @@ public void testVerifyTokenWithWrongTenantAwareClient() throws Exception { assertEquals(AuthErrorCode.TENANT_ID_MISMATCH, ((FirebaseAuthException) e.getCause()).getAuthErrorCode()); } + + // Verifies with FirebaseAuth + FirebaseToken decoded = FirebaseAuth.getInstance().verifyIdToken(idToken); + assertEquals("user", decoded.getUid()); + assertEquals(tenantId, decoded.getTenantId()); } @Test From 0b356e1fdf6da40f01315f54dad0322a0d732803 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Thu, 10 Sep 2020 14:27:15 -0700 Subject: [PATCH 002/390] fix: Cleaning up FirebaseApp state management (#476) --- .../java/com/google/firebase/FirebaseApp.java | 8 ++- .../firebase/ImplFirebaseTrampolines.java | 1 - .../firebase/auth/AbstractFirebaseAuth.java | 48 ---------------- .../google/firebase/auth/FirebaseAuth.java | 8 --- .../multitenancy/TenantAwareFirebaseAuth.java | 5 -- .../google/firebase/cloud/StorageClient.java | 8 --- .../firebase/database/FirebaseDatabase.java | 6 +- .../firebase/iid/FirebaseInstanceId.java | 7 --- .../firebase/internal/FirebaseService.java | 6 +- .../firebase/messaging/FirebaseMessaging.java | 7 --- .../FirebaseProjectManagement.java | 5 -- .../FirebaseProjectManagementServiceImpl.java | 8 --- .../com/google/firebase/FirebaseAppTest.java | 34 +++++++++++ .../firebase/auth/FirebaseAuthTest.java | 57 +++++++++---------- .../firebase/iid/FirebaseInstanceIdTest.java | 35 ++++++++---- 15 files changed, 100 insertions(+), 143 deletions(-) diff --git a/src/main/java/com/google/firebase/FirebaseApp.java b/src/main/java/com/google/firebase/FirebaseApp.java index 38482541c..06188cd55 100644 --- a/src/main/java/com/google/firebase/FirebaseApp.java +++ b/src/main/java/com/google/firebase/FirebaseApp.java @@ -277,6 +277,8 @@ public FirebaseOptions getOptions() { */ @Nullable String getProjectId() { + checkNotDeleted(); + // Try to get project ID from user-specified options. String projectId = options.getProjectId(); @@ -314,8 +316,10 @@ public String toString() { } /** - * Deletes the {@link FirebaseApp} and all its data. All calls to this {@link FirebaseApp} - * instance will throw once it has been called. + * Deletes this {@link FirebaseApp} object, and releases any local state and managed resources + * associated with it. All calls to this {@link FirebaseApp} instance will throw once this method + * has been called. This also releases any managed resources allocated by other services + * attached to this object instance (e.g. {@code FirebaseAuth}). * *

A no-op if delete was called before. */ diff --git a/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java b/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java index 7a50e0b07..3a7f6499a 100644 --- a/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java +++ b/src/main/java/com/google/firebase/ImplFirebaseTrampolines.java @@ -26,7 +26,6 @@ import com.google.firebase.internal.NonNull; import java.util.concurrent.Callable; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadFactory; diff --git a/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java b/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java index 8004548a5..fc9c8df0b 100644 --- a/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java +++ b/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java @@ -18,7 +18,6 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; import com.google.api.client.json.JsonFactory; import com.google.api.client.util.Clock; @@ -164,7 +163,6 @@ public ApiFuture createCustomTokenAsync( private CallableOperation createCustomTokenOp( final String uid, final Map developerClaims) { - checkNotDestroyed(); checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty"); final FirebaseTokenFactory tokenFactory = this.tokenFactory.get(); return new CallableOperation() { @@ -208,7 +206,6 @@ public ApiFuture createSessionCookieAsync( private CallableOperation createSessionCookieOp( final String idToken, final SessionCookieOptions options) { - checkNotDestroyed(); checkArgument(!Strings.isNullOrEmpty(idToken), "idToken must not be null or empty"); checkNotNull(options, "options must not be null"); final FirebaseUserManager userManager = getUserManager(); @@ -299,7 +296,6 @@ public ApiFuture verifyIdTokenAsync(@NonNull String idToken) { private CallableOperation verifyIdTokenOp( final String idToken, final boolean checkRevoked) { - checkNotDestroyed(); checkArgument(!Strings.isNullOrEmpty(idToken), "ID token must not be null or empty"); final FirebaseTokenVerifier verifier = getIdTokenVerifier(checkRevoked); return new CallableOperation() { @@ -380,7 +376,6 @@ public ApiFuture verifySessionCookieAsync(String cookie, boolean private CallableOperation verifySessionCookieOp( final String cookie, final boolean checkRevoked) { - checkNotDestroyed(); checkArgument(!Strings.isNullOrEmpty(cookie), "Session cookie must not be null or empty"); final FirebaseTokenVerifier sessionCookieVerifier = getSessionCookieVerifier(checkRevoked); return new CallableOperation() { @@ -434,7 +429,6 @@ public ApiFuture revokeRefreshTokensAsync(@NonNull String uid) { } private CallableOperation revokeRefreshTokensOp(final String uid) { - checkNotDestroyed(); checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty"); final FirebaseUserManager userManager = getUserManager(); return new CallableOperation() { @@ -475,7 +469,6 @@ public ApiFuture getUserAsync(@NonNull String uid) { } private CallableOperation getUserOp(final String uid) { - checkNotDestroyed(); checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty"); final FirebaseUserManager userManager = getUserManager(); return new CallableOperation() { @@ -513,7 +506,6 @@ public ApiFuture getUserByEmailAsync(@NonNull String email) { private CallableOperation getUserByEmailOp( final String email) { - checkNotDestroyed(); checkArgument(!Strings.isNullOrEmpty(email), "email must not be null or empty"); final FirebaseUserManager userManager = getUserManager(); return new CallableOperation() { @@ -551,7 +543,6 @@ public ApiFuture getUserByPhoneNumberAsync(@NonNull String phoneNumb private CallableOperation getUserByPhoneNumberOp( final String phoneNumber) { - checkNotDestroyed(); checkArgument(!Strings.isNullOrEmpty(phoneNumber), "phone number must not be null or empty"); final FirebaseUserManager userManager = getUserManager(); return new CallableOperation() { @@ -620,7 +611,6 @@ public ApiFuture listUsersAsync(@Nullable String pageToken, int m private CallableOperation listUsersOp( @Nullable final String pageToken, final int maxResults) { - checkNotDestroyed(); final FirebaseUserManager userManager = getUserManager(); final DefaultUserSource source = new DefaultUserSource(userManager, jsonFactory); final ListUsersPage.Factory factory = new ListUsersPage.Factory(source, maxResults, pageToken); @@ -661,7 +651,6 @@ public ApiFuture createUserAsync(@NonNull UserRecord.CreateRequest r private CallableOperation createUserOp( final UserRecord.CreateRequest request) { - checkNotDestroyed(); checkNotNull(request, "create request must not be null"); final FirebaseUserManager userManager = getUserManager(); return new CallableOperation() { @@ -701,7 +690,6 @@ public ApiFuture updateUserAsync(@NonNull UserRecord.UpdateRequest r private CallableOperation updateUserOp( final UserRecord.UpdateRequest request) { - checkNotDestroyed(); checkNotNull(request, "update request must not be null"); final FirebaseUserManager userManager = getUserManager(); return new CallableOperation() { @@ -754,7 +742,6 @@ public ApiFuture setCustomUserClaimsAsync( private CallableOperation setCustomUserClaimsOp( final String uid, final Map claims) { - checkNotDestroyed(); checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty"); final FirebaseUserManager userManager = getUserManager(); return new CallableOperation() { @@ -793,7 +780,6 @@ public ApiFuture deleteUserAsync(String uid) { } private CallableOperation deleteUserOp(final String uid) { - checkNotDestroyed(); checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty"); final FirebaseUserManager userManager = getUserManager(); return new CallableOperation() { @@ -876,7 +862,6 @@ public ApiFuture importUsersAsync( private CallableOperation importUsersOp( final List users, final UserImportOptions options) { - checkNotDestroyed(); final UserImportRequest request = new UserImportRequest(users, options, jsonFactory); final FirebaseUserManager userManager = getUserManager(); return new CallableOperation() { @@ -931,7 +916,6 @@ public ApiFuture getUsersAsync(@NonNull Collection getUsersOp( @NonNull final Collection identifiers) { - checkNotDestroyed(); checkNotNull(identifiers, "identifiers must not be null"); checkArgument(identifiers.size() <= FirebaseUserManager.MAX_GET_ACCOUNTS_BATCH_SIZE, "identifiers parameter must have <= " + FirebaseUserManager.MAX_GET_ACCOUNTS_BATCH_SIZE @@ -1004,7 +988,6 @@ public ApiFuture deleteUsersAsync(List uids) { private CallableOperation deleteUsersOp( final List uids) { - checkNotDestroyed(); checkNotNull(uids, "uids must not be null"); for (String uid : uids) { UserRecord.checkUid(uid); @@ -1178,7 +1161,6 @@ public ApiFuture generateSignInWithEmailLinkAsync( private CallableOperation generateEmailActionLinkOp( final EmailLinkType type, final String email, final ActionCodeSettings settings) { - checkNotDestroyed(); checkArgument(!Strings.isNullOrEmpty(email), "email must not be null or empty"); if (type == EmailLinkType.EMAIL_SIGNIN) { checkNotNull(settings, "ActionCodeSettings must not be null when generating sign-in links"); @@ -1227,7 +1209,6 @@ public ApiFuture createOidcProviderConfigAsync( private CallableOperation createOidcProviderConfigOp(final OidcProviderConfig.CreateRequest request) { - checkNotDestroyed(); checkNotNull(request, "Create request must not be null."); OidcProviderConfig.checkOidcProviderId(request.getProviderId()); final FirebaseUserManager userManager = getUserManager(); @@ -1271,7 +1252,6 @@ public ApiFuture updateOidcProviderConfigAsync( private CallableOperation updateOidcProviderConfigOp( final OidcProviderConfig.UpdateRequest request) { - checkNotDestroyed(); checkNotNull(request, "Update request must not be null."); checkArgument(!request.getProperties().isEmpty(), "Update request must have at least one property set."); @@ -1316,7 +1296,6 @@ public ApiFuture getOidcProviderConfigAsync(@NonNull String private CallableOperation getOidcProviderConfigOp(final String providerId) { - checkNotDestroyed(); OidcProviderConfig.checkOidcProviderId(providerId); final FirebaseUserManager userManager = getUserManager(); return new CallableOperation() { @@ -1400,7 +1379,6 @@ public ApiFuture> listOidcProviderCo private CallableOperation, FirebaseAuthException> listOidcProviderConfigsOp(@Nullable final String pageToken, final int maxResults) { - checkNotDestroyed(); final FirebaseUserManager userManager = getUserManager(); final DefaultOidcProviderConfigSource source = new DefaultOidcProviderConfigSource(userManager); final ListProviderConfigsPage.Factory factory = @@ -1443,7 +1421,6 @@ public ApiFuture deleteOidcProviderConfigAsync(String providerId) { private CallableOperation deleteOidcProviderConfigOp( final String providerId) { - checkNotDestroyed(); OidcProviderConfig.checkOidcProviderId(providerId); final FirebaseUserManager userManager = getUserManager(); return new CallableOperation() { @@ -1490,7 +1467,6 @@ public ApiFuture createSamlProviderConfigAsync( private CallableOperation createSamlProviderConfigOp(final SamlProviderConfig.CreateRequest request) { - checkNotDestroyed(); checkNotNull(request, "Create request must not be null."); SamlProviderConfig.checkSamlProviderId(request.getProviderId()); final FirebaseUserManager userManager = getUserManager(); @@ -1534,7 +1510,6 @@ public ApiFuture updateSamlProviderConfigAsync( private CallableOperation updateSamlProviderConfigOp( final SamlProviderConfig.UpdateRequest request) { - checkNotDestroyed(); checkNotNull(request, "Update request must not be null."); checkArgument(!request.getProperties().isEmpty(), "Update request must have at least one property set."); @@ -1579,7 +1554,6 @@ public ApiFuture getSamlProviderConfigAsync(@NonNull String private CallableOperation getSamlProviderConfigOp(final String providerId) { - checkNotDestroyed(); SamlProviderConfig.checkSamlProviderId(providerId); final FirebaseUserManager userManager = getUserManager(); return new CallableOperation() { @@ -1663,7 +1637,6 @@ public ApiFuture> listSamlProviderCo private CallableOperation, FirebaseAuthException> listSamlProviderConfigsOp(@Nullable final String pageToken, final int maxResults) { - checkNotDestroyed(); final FirebaseUserManager userManager = getUserManager(); final DefaultSamlProviderConfigSource source = new DefaultSamlProviderConfigSource(userManager); final ListProviderConfigsPage.Factory factory = @@ -1706,7 +1679,6 @@ public ApiFuture deleteSamlProviderConfigAsync(String providerId) { private CallableOperation deleteSamlProviderConfigOp( final String providerId) { - checkNotDestroyed(); SamlProviderConfig.checkSamlProviderId(providerId); final FirebaseUserManager userManager = getUserManager(); return new CallableOperation() { @@ -1729,32 +1701,12 @@ Supplier threadSafeMemoize(final Supplier supplier) { public T get() { checkNotNull(supplier); synchronized (lock) { - checkNotDestroyed(); return supplier.get(); } } }); } - private void checkNotDestroyed() { - synchronized (lock) { - checkState( - !destroyed.get(), - "FirebaseAuth instance is no longer alive. This happens when " - + "the parent FirebaseApp instance has been deleted."); - } - } - - final void destroy() { - synchronized (lock) { - doDestroy(); - destroyed.set(true); - } - } - - /** Performs any additional required clean up. */ - protected abstract void doDestroy(); - protected abstract static class Builder> { private FirebaseApp firebaseApp; diff --git a/src/main/java/com/google/firebase/auth/FirebaseAuth.java b/src/main/java/com/google/firebase/auth/FirebaseAuth.java index 417ea6436..27e79960d 100644 --- a/src/main/java/com/google/firebase/auth/FirebaseAuth.java +++ b/src/main/java/com/google/firebase/auth/FirebaseAuth.java @@ -69,9 +69,6 @@ public static synchronized FirebaseAuth getInstance(FirebaseApp app) { return service.getInstance(); } - @Override - protected void doDestroy() { } - private static FirebaseAuth fromApp(final FirebaseApp app) { return populateBuilderFromApp(builder(), app, null) .setTenantManager(new Supplier() { @@ -88,11 +85,6 @@ private static class FirebaseAuthService extends FirebaseService { FirebaseAuthService(FirebaseApp app) { super(SERVICE_ID, FirebaseAuth.fromApp(app)); } - - @Override - public void destroy() { - instance.destroy(); - } } static Builder builder() { diff --git a/src/main/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuth.java b/src/main/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuth.java index 95ef169da..bc374c036 100644 --- a/src/main/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuth.java +++ b/src/main/java/com/google/firebase/auth/multitenancy/TenantAwareFirebaseAuth.java @@ -69,11 +69,6 @@ public ApiFuture apply(FirebaseToken input) { }, MoreExecutors.directExecutor()); } - @Override - protected void doDestroy() { - // Nothing extra needs to be destroyed. - } - static TenantAwareFirebaseAuth fromApp(FirebaseApp app, String tenantId) { return populateBuilderFromApp(builder(), app, tenantId) .setTenantId(tenantId) diff --git a/src/main/java/com/google/firebase/cloud/StorageClient.java b/src/main/java/com/google/firebase/cloud/StorageClient.java index d6a1567f4..955abd7e2 100644 --- a/src/main/java/com/google/firebase/cloud/StorageClient.java +++ b/src/main/java/com/google/firebase/cloud/StorageClient.java @@ -106,13 +106,5 @@ private static class StorageClientService extends FirebaseService StorageClientService(StorageClient client) { super(SERVICE_ID, client); } - - @Override - public void destroy() { - // NOTE: We don't explicitly tear down anything here, but public methods of StorageClient - // will now fail because calls to getOptions() and getToken() will hit FirebaseApp, - // which will throw once the app is deleted. - } } - } diff --git a/src/main/java/com/google/firebase/database/FirebaseDatabase.java b/src/main/java/com/google/firebase/database/FirebaseDatabase.java index 0a79932db..306ce9274 100644 --- a/src/main/java/com/google/firebase/database/FirebaseDatabase.java +++ b/src/main/java/com/google/firebase/database/FirebaseDatabase.java @@ -173,7 +173,7 @@ static FirebaseDatabase createForTests( return db; } - /** + /** * @return The version for this build of the Firebase Database client */ public static String getSdkVersion() { @@ -358,6 +358,10 @@ DatabaseConfig getConfig() { return this.config; } + /** + * Tears down the WebSocket connections and background threads started by this {@code + * FirebaseDatabase} instance thus disconnecting from the remote database. + */ void destroy() { synchronized (lock) { if (destroyed.get()) { diff --git a/src/main/java/com/google/firebase/iid/FirebaseInstanceId.java b/src/main/java/com/google/firebase/iid/FirebaseInstanceId.java index 822654402..47026e6f1 100644 --- a/src/main/java/com/google/firebase/iid/FirebaseInstanceId.java +++ b/src/main/java/com/google/firebase/iid/FirebaseInstanceId.java @@ -200,12 +200,5 @@ private static class FirebaseInstanceIdService extends FirebaseService Type of the service */ -public abstract class FirebaseService { +public class FirebaseService { private final String id; protected final T instance; @@ -62,5 +62,7 @@ public final T getInstance() { * Tear down this FirebaseService instance and the service object wrapped in it, cleaning up * any allocated resources in the process. */ - public abstract void destroy(); + public void destroy() { + // Child classes can override this method to implement any service-specific cleanup logic. + } } diff --git a/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java b/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java index e1b4f794c..a08e8cfaf 100644 --- a/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java +++ b/src/main/java/com/google/firebase/messaging/FirebaseMessaging.java @@ -403,13 +403,6 @@ private static class FirebaseMessagingService extends FirebaseService { + MockFirebaseService() { + super("MockFirebaseService", new Object()); + } + } } diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java b/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java index e34fede1f..bd7aa0ff8 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseAuthTest.java @@ -28,7 +28,6 @@ import com.google.api.client.testing.http.MockHttpTransport; import com.google.api.client.testing.http.MockLowLevelHttpResponse; import com.google.api.core.ApiFuture; -import com.google.common.base.Defaults; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableMap; @@ -40,11 +39,6 @@ import com.google.firebase.testing.ServiceAccount; import com.google.firebase.testing.TestResponseInterceptor; import com.google.firebase.testing.TestUtils; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -105,26 +99,33 @@ public void testInvokeAfterAppDelete() throws Exception { assertNotNull(auth); app.delete(); - for (Method method : auth.getClass().getDeclaredMethods()) { - int modifiers = method.getModifiers(); - if (!Modifier.isPublic(modifiers) || Modifier.isStatic(modifiers)) { - continue; - } - - List parameters = new ArrayList<>(method.getParameterTypes().length); - for (Class parameterType : method.getParameterTypes()) { - parameters.add(Defaults.defaultValue(parameterType)); - } - try { - method.invoke(auth, parameters.toArray()); - fail("No error thrown when invoking auth after deleting app; method: " + method.getName()); - } catch (InvocationTargetException expected) { - String message = "FirebaseAuth instance is no longer alive. This happens when " - + "the parent FirebaseApp instance has been deleted."; - Throwable cause = expected.getCause(); - assertTrue(cause instanceof IllegalStateException); - assertEquals(message, cause.getMessage()); - } + String message = "FirebaseApp 'testInvokeAfterAppDelete' was deleted"; + try { + FirebaseAuth.getInstance(app); + fail("No error thrown when invoking auth after deleting app"); + } catch (IllegalStateException ex) { + assertEquals(message, ex.getMessage()); + } + + try { + auth.createCustomToken("uid"); + fail("No error thrown when invoking auth after deleting app"); + } catch (IllegalStateException ex) { + assertEquals(message, ex.getMessage()); + } + + try { + auth.verifyIdToken("idToken"); + fail("No error thrown when invoking auth after deleting app"); + } catch (IllegalStateException ex) { + assertEquals(message, ex.getMessage()); + } + + try { + auth.getUser("uid"); + fail("No error thrown when invoking auth after deleting app"); + } catch (IllegalStateException ex) { + assertEquals(message, ex.getMessage()); } } @@ -461,10 +462,6 @@ public void testVerifySessionCookieWithCheckRevokedAsyncFailure() throws Interru } } - private FirebaseToken getFirebaseToken(String subject) { - return new FirebaseToken(ImmutableMap.of("sub", subject)); - } - private FirebaseToken getFirebaseToken(long issuedAt) { return new FirebaseToken(ImmutableMap.of("sub", TEST_USER, "iat", issuedAt)); } diff --git a/src/test/java/com/google/firebase/iid/FirebaseInstanceIdTest.java b/src/test/java/com/google/firebase/iid/FirebaseInstanceIdTest.java index f15861a62..27864d992 100644 --- a/src/test/java/com/google/firebase/iid/FirebaseInstanceIdTest.java +++ b/src/test/java/com/google/firebase/iid/FirebaseInstanceIdTest.java @@ -50,6 +50,11 @@ public class FirebaseInstanceIdTest { + private static final FirebaseOptions APP_OPTIONS = FirebaseOptions.builder() + .setCredentials(new MockGoogleCredentials("test-token")) + .setProjectId("test-project") + .build(); + private static final Map ERROR_MESSAGES = ImmutableMap.of( 404, "Instance ID \"test-iid\": Failed to find the instance ID.", 409, "Instance ID \"test-iid\": Already deleted.", @@ -88,13 +93,25 @@ public void testNoProjectId() { } } + @Test + public void testInvokeAfterAppDelete() { + FirebaseApp app = FirebaseApp.initializeApp(APP_OPTIONS, "testInvokeAfterAppDelete"); + FirebaseInstanceId instanceId = FirebaseInstanceId.getInstance(app); + assertNotNull(instanceId); + app.delete(); + + try { + FirebaseInstanceId.getInstance(app); + fail("No error thrown when invoking instanceId after deleting app"); + } catch (IllegalStateException ex) { + String message = "FirebaseApp 'testInvokeAfterAppDelete' was deleted"; + assertEquals(message, ex.getMessage()); + } + } + @Test public void testInvalidInstanceId() { - FirebaseOptions options = FirebaseOptions.builder() - .setCredentials(new MockGoogleCredentials("test-token")) - .setProjectId("test-project") - .build(); - FirebaseApp.initializeApp(options); + FirebaseApp.initializeApp(APP_OPTIONS); FirebaseInstanceId instanceId = FirebaseInstanceId.getInstance(); TestResponseInterceptor interceptor = new TestResponseInterceptor(); @@ -122,9 +139,7 @@ public void testDeleteInstanceId() throws Exception { MockHttpTransport transport = new MockHttpTransport.Builder() .setLowLevelHttpResponse(response) .build(); - FirebaseOptions options = FirebaseOptions.builder() - .setCredentials(new MockGoogleCredentials("test-token")) - .setProjectId("test-project") + FirebaseOptions options = APP_OPTIONS.toBuilder() .setHttpTransport(transport) .build(); FirebaseApp app = FirebaseApp.initializeApp(options); @@ -168,9 +183,7 @@ public void testDeleteInstanceIdError() throws Exception { MockHttpTransport transport = new MockHttpTransport.Builder() .setLowLevelHttpResponse(response) .build(); - FirebaseOptions options = FirebaseOptions.builder() - .setCredentials(new MockGoogleCredentials("test-token")) - .setProjectId("test-project") + FirebaseOptions options = APP_OPTIONS.toBuilder() .setHttpTransport(transport) .build(); FirebaseApp app = FirebaseApp.initializeApp(options); From fba507bd4cff639865653da717e7b1a5dafea8fd Mon Sep 17 00:00:00 2001 From: kentengjin Date: Wed, 30 Sep 2020 11:41:22 -0700 Subject: [PATCH 003/390] fix(auth): Migrate IAM SignBlob to IAMCredentials SignBlob (#480) * Migrate IAM SignBlob to IAMCredentials SignBlob Point all SignBlob calls to IAMCredentials * Fix FirebaseToken tests Change proto field to fix the test --- .../google/firebase/auth/internal/CryptoSigners.java | 10 +++++----- .../firebase/auth/FirebaseCustomTokenTest.java | 4 ++-- .../firebase/auth/internal/CryptoSignersTest.java | 12 ++++++------ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/google/firebase/auth/internal/CryptoSigners.java b/src/main/java/com/google/firebase/auth/internal/CryptoSigners.java index 7bc54afdf..f167b09a0 100644 --- a/src/main/java/com/google/firebase/auth/internal/CryptoSigners.java +++ b/src/main/java/com/google/firebase/auth/internal/CryptoSigners.java @@ -65,13 +65,13 @@ public String getAccount() { /** * @ {@link CryptoSigner} implementation that uses the - * - * Google IAM service to sign data. + * + * Google IAMCredentials service to sign data. */ static class IAMCryptoSigner implements CryptoSigner { private static final String IAM_SIGN_BLOB_URL = - "https://iam.googleapis.com/v1/projects/-/serviceAccounts/%s:signBlob"; + "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s:signBlob"; private final String serviceAccount; private final ErrorHandlingHttpClient httpClient; @@ -95,11 +95,11 @@ void setInterceptor(HttpResponseInterceptor interceptor) { @Override public byte[] sign(byte[] payload) throws FirebaseAuthException { String encodedPayload = BaseEncoding.base64().encode(payload); - Map content = ImmutableMap.of("bytesToSign", encodedPayload); + Map content = ImmutableMap.of("payload", encodedPayload); String encodedUrl = String.format(IAM_SIGN_BLOB_URL, serviceAccount); HttpRequestInfo requestInfo = HttpRequestInfo.buildJsonPostRequest(encodedUrl, content); GenericJson parsed = httpClient.sendAndParse(requestInfo, GenericJson.class); - return BaseEncoding.base64().decode((String) parsed.get("signature")); + return BaseEncoding.base64().decode((String) parsed.get("signedBlob")); } @Override diff --git a/src/test/java/com/google/firebase/auth/FirebaseCustomTokenTest.java b/src/test/java/com/google/firebase/auth/FirebaseCustomTokenTest.java index b277dce14..5e37161cd 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseCustomTokenTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseCustomTokenTest.java @@ -92,7 +92,7 @@ public void testCreateCustomTokenWithDeveloperClaims() throws Exception { public void testCreateCustomTokenWithoutServiceAccountCredentials() throws Exception { MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); String content = Utils.getDefaultJsonFactory().toString( - ImmutableMap.of("signature", BaseEncoding.base64().encode("test-signature".getBytes()))); + ImmutableMap.of("signedBlob", BaseEncoding.base64().encode("test-signature".getBytes()))); response.setContent(content); MockHttpTransport transport = new MultiRequestMockHttpTransport(ImmutableList.of(response)); @@ -117,7 +117,7 @@ public void testCreateCustomTokenWithoutServiceAccountCredentials() throws Excep @Test public void testCreateCustomTokenWithDiscoveredServiceAccount() throws Exception { String content = Utils.getDefaultJsonFactory().toString( - ImmutableMap.of("signature", BaseEncoding.base64().encode("test-signature".getBytes()))); + ImmutableMap.of("signedBlob", BaseEncoding.base64().encode("test-signature".getBytes()))); List responses = ImmutableList.of( // Service account discovery response new MockLowLevelHttpResponse().setContent("test@service.account"), 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 32f9fd543..fee4d8fb8 100644 --- a/src/test/java/com/google/firebase/auth/internal/CryptoSignersTest.java +++ b/src/test/java/com/google/firebase/auth/internal/CryptoSignersTest.java @@ -71,7 +71,7 @@ public void testInvalidServiceAccountCryptoSigner() { public void testIAMCryptoSigner() throws Exception { String signature = BaseEncoding.base64().encode("signed-bytes".getBytes()); String response = Utils.getDefaultJsonFactory().toString( - ImmutableMap.of("signature", signature)); + ImmutableMap.of("signedBlob", signature)); MockHttpTransport transport = new MockHttpTransport.Builder() .setLowLevelHttpResponse(new MockLowLevelHttpResponse().setContent(response)) .build(); @@ -84,7 +84,7 @@ public void testIAMCryptoSigner() throws Exception { byte[] data = signer.sign("foo".getBytes()); assertArrayEquals("signed-bytes".getBytes(), data); - final String url = "https://iam.googleapis.com/v1/projects/-/serviceAccounts/" + final String url = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/" + "test-service-account@iam.gserviceaccount.com:signBlob"; assertEquals(url, interceptor.getResponse().getRequest().getUrl().toString()); } @@ -150,7 +150,7 @@ public void testInvalidIAMCryptoSigner() { public void testMetadataService() throws Exception { String signature = BaseEncoding.base64().encode("signed-bytes".getBytes()); String response = Utils.getDefaultJsonFactory().toString( - ImmutableMap.of("signature", signature)); + ImmutableMap.of("signedBlob", signature)); MockHttpTransport transport = new MultiRequestMockHttpTransport( ImmutableList.of( new MockLowLevelHttpResponse().setContent("metadata-server@iam.gserviceaccount.com"), @@ -168,7 +168,7 @@ public void testMetadataService() throws Exception { byte[] data = signer.sign("foo".getBytes()); assertArrayEquals("signed-bytes".getBytes(), data); - final String url = "https://iam.googleapis.com/v1/projects/-/serviceAccounts/" + final String url = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/" + "metadata-server@iam.gserviceaccount.com:signBlob"; HttpRequest request = interceptor.getResponse().getRequest(); assertEquals(url, request.getUrl().toString()); @@ -179,7 +179,7 @@ public void testMetadataService() throws Exception { public void testExplicitServiceAccountEmail() throws Exception { String signature = BaseEncoding.base64().encode("signed-bytes".getBytes()); String response = Utils.getDefaultJsonFactory().toString( - ImmutableMap.of("signature", signature)); + ImmutableMap.of("signedBlob", signature)); // Explicit service account should get precedence MockHttpTransport transport = new MultiRequestMockHttpTransport( @@ -198,7 +198,7 @@ public void testExplicitServiceAccountEmail() throws Exception { byte[] data = signer.sign("foo".getBytes()); assertArrayEquals("signed-bytes".getBytes(), data); - final String url = "https://iam.googleapis.com/v1/projects/-/serviceAccounts/" + final String url = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/" + "explicit-service-account@iam.gserviceaccount.com:signBlob"; HttpRequest request = interceptor.getResponse().getRequest(); assertEquals(url, request.getUrl().toString()); From 408b85f7d0072e8fb3b462aa2317843d683e7ad7 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Wed, 7 Oct 2020 15:50:03 -0400 Subject: [PATCH 004/390] [chore] Release 7.0.1 (#484) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0f40ea078..0bde2d2f4 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 7.0.0 + 7.0.1 jar firebase-admin From ba56dcde18fb6cade53269edcca75f5ac101df4b Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Date: Thu, 15 Oct 2020 14:35:23 -0400 Subject: [PATCH 005/390] Fix comment about max number of events (#488) Multicast message can send up to 500 messages, not 100 https://github.com/firebase/firebase-admin-java/blob/408b85f7d0072e8fb3b462aa2317843d683e7ad7/src/main/java/com/google/firebase/messaging/MulticastMessage.java#L46 --- .../com/google/firebase/snippets/FirebaseMessagingSnippets.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/google/firebase/snippets/FirebaseMessagingSnippets.java b/src/test/java/com/google/firebase/snippets/FirebaseMessagingSnippets.java index f1432e160..4a4a25472 100644 --- a/src/test/java/com/google/firebase/snippets/FirebaseMessagingSnippets.java +++ b/src/test/java/com/google/firebase/snippets/FirebaseMessagingSnippets.java @@ -151,7 +151,7 @@ public void sendAll() throws FirebaseMessagingException { public void sendMulticast() throws FirebaseMessagingException { // [START send_multicast] - // Create a list containing up to 100 registration tokens. + // Create a list containing up to 500 registration tokens. // These registration tokens come from the client FCM SDKs. List registrationTokens = Arrays.asList( "YOUR_REGISTRATION_TOKEN_1", From 5f3b696db4bf4550203a47834dfa19a8a33d750b Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Mon, 14 Dec 2020 14:40:18 -0500 Subject: [PATCH 006/390] feat(rc): Add Remote Config Management API (#502) - Add Remote Config Management API --- .../firebase/remoteconfig/Condition.java | 174 +++ .../remoteconfig/FirebaseRemoteConfig.java | 415 ++++++ .../FirebaseRemoteConfigClient.java | 43 + .../FirebaseRemoteConfigClientImpl.java | 269 ++++ .../FirebaseRemoteConfigException.java | 63 + .../remoteconfig/ListVersionsOptions.java | 198 +++ .../remoteconfig/ListVersionsPage.java | 268 ++++ .../firebase/remoteconfig/Parameter.java | 167 +++ .../firebase/remoteconfig/ParameterGroup.java | 138 ++ .../firebase/remoteconfig/ParameterValue.java | 127 ++ .../firebase/remoteconfig/PublishOptions.java | 33 + .../remoteconfig/RemoteConfigErrorCode.java | 60 + .../remoteconfig/RemoteConfigUtil.java | 93 ++ .../firebase/remoteconfig/TagColor.java | 45 + .../firebase/remoteconfig/Template.java | 281 ++++ .../google/firebase/remoteconfig/User.java | 98 ++ .../google/firebase/remoteconfig/Version.java | 225 +++ .../RemoteConfigServiceErrorResponse.java | 72 + .../internal/TemplateResponse.java | 424 ++++++ .../remoteconfig/internal/package-info.java | 20 + .../firebase/remoteconfig/ConditionTest.java | 93 ++ .../FirebaseRemoteConfigClientImplTest.java | 1234 +++++++++++++++++ .../remoteconfig/FirebaseRemoteConfigIT.java | 199 +++ .../FirebaseRemoteConfigTest.java | 600 ++++++++ .../remoteconfig/ListVersionsPageTest.java | 338 +++++ .../remoteconfig/MockRemoteConfigClient.java | 89 ++ .../remoteconfig/ParameterGroupTest.java | 87 ++ .../firebase/remoteconfig/ParameterTest.java | 91 ++ .../remoteconfig/ParameterValueTest.java | 54 + .../firebase/remoteconfig/TemplateTest.java | 346 +++++ .../firebase/remoteconfig/UserTest.java | 62 + .../firebase/remoteconfig/VersionTest.java | 105 ++ src/test/resources/getRemoteConfig.json | 56 + .../resources/listRemoteConfigVersions.json | 50 + 34 files changed, 6617 insertions(+) create mode 100644 src/main/java/com/google/firebase/remoteconfig/Condition.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClient.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImpl.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigException.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ListVersionsOptions.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ListVersionsPage.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/Parameter.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ParameterGroup.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/ParameterValue.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/PublishOptions.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/RemoteConfigErrorCode.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/RemoteConfigUtil.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/TagColor.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/Template.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/User.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/Version.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/internal/RemoteConfigServiceErrorResponse.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/internal/TemplateResponse.java create mode 100644 src/main/java/com/google/firebase/remoteconfig/internal/package-info.java create mode 100644 src/test/java/com/google/firebase/remoteconfig/ConditionTest.java create mode 100644 src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigClientImplTest.java create mode 100644 src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigIT.java create mode 100644 src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java create mode 100644 src/test/java/com/google/firebase/remoteconfig/ListVersionsPageTest.java create mode 100644 src/test/java/com/google/firebase/remoteconfig/MockRemoteConfigClient.java create mode 100644 src/test/java/com/google/firebase/remoteconfig/ParameterGroupTest.java create mode 100644 src/test/java/com/google/firebase/remoteconfig/ParameterTest.java create mode 100644 src/test/java/com/google/firebase/remoteconfig/ParameterValueTest.java create mode 100644 src/test/java/com/google/firebase/remoteconfig/TemplateTest.java create mode 100644 src/test/java/com/google/firebase/remoteconfig/UserTest.java create mode 100644 src/test/java/com/google/firebase/remoteconfig/VersionTest.java create mode 100644 src/test/resources/getRemoteConfig.json create mode 100644 src/test/resources/listRemoteConfigVersions.json diff --git a/src/main/java/com/google/firebase/remoteconfig/Condition.java b/src/main/java/com/google/firebase/remoteconfig/Condition.java new file mode 100644 index 000000000..6ccde59d9 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/Condition.java @@ -0,0 +1,174 @@ +/* + * Copyright 2020 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.TemplateResponse.ConditionResponse; + +import java.util.Objects; + +/** + * Represents a Remote Config condition that can be included in a {@link Template}. + * A condition targets a specific group of users. A list of these conditions make up + * part of a Remote Config template. + */ +public final class Condition { + + private String name; + private String expression; + private TagColor tagColor; + + /** + * Creates a new {@link Condition}. + * + * @param name A non-null, non-empty, and unique name of this condition. + * @param expression A non-null and non-empty expression of this condition. + */ + public Condition(@NonNull String name, @NonNull String expression) { + this(name, expression, null); + } + + /** + * Creates a new {@link Condition}. + * + * @param name A non-null, non-empty, and unique name of this condition. + * @param expression A non-null and non-empty expression of this condition. + * @param tagColor A color associated with this condition for display purposes in the + * Firebase Console. Not specifying this value results in the console picking an + * arbitrary color to associate with the condition. + */ + public Condition(@NonNull String name, @NonNull String expression, @Nullable TagColor tagColor) { + checkArgument(!Strings.isNullOrEmpty(name), "condition name must not be null or empty"); + checkArgument(!Strings.isNullOrEmpty(expression), + "condition expression must not be null or empty"); + this.name = name; + this.expression = expression; + this.tagColor = tagColor; + } + + Condition(@NonNull ConditionResponse conditionResponse) { + checkNotNull(conditionResponse); + this.name = conditionResponse.getName(); + this.expression = conditionResponse.getExpression(); + if (!Strings.isNullOrEmpty(conditionResponse.getTagColor())) { + this.tagColor = TagColor.valueOf(conditionResponse.getTagColor()); + } + } + + /** + * Gets the name of the condition. + * + * @return The name of the condition. + */ + @NonNull + public String getName() { + return name; + } + + /** + * Gets the expression of the condition. + * + * @return The expression of the condition. + */ + @NonNull + public String getExpression() { + return expression; + } + + /** + * Gets the tag color of the condition used for display purposes in the Firebase Console. + * + * @return The tag color of the condition. + */ + @NonNull + public TagColor getTagColor() { + return tagColor; + } + + /** + * Sets the name of the condition. + * + * @param name A non-empty and unique name of this condition. + * @return This {@link Condition}. + */ + public Condition setName(@NonNull String name) { + checkArgument(!Strings.isNullOrEmpty(name), "condition name must not be null or empty"); + this.name = name; + return this; + } + + /** + * Sets the expression of the condition. + * + *

See + * condition expressions for the expected syntax of this field. + * + * @param expression The logic of this condition. + * @return This {@link Condition}. + */ + public Condition setExpression(@NonNull String expression) { + checkArgument(!Strings.isNullOrEmpty(expression), + "condition expression must not be null or empty"); + this.expression = expression; + return this; + } + + /** + * Sets the tag color of the condition. + * + *

The color associated with this condition for display purposes in the Firebase Console. + * Not specifying this value results in the console picking an arbitrary color to associate + * with the condition. + * + * @param tagColor The tag color of this condition. + * @return This {@link Condition}. + */ + public Condition setTagColor(@Nullable TagColor tagColor) { + this.tagColor = tagColor; + return this; + } + + ConditionResponse toConditionResponse() { + return new ConditionResponse() + .setName(this.name) + .setExpression(this.expression) + .setTagColor(this.tagColor == null ? null : this.tagColor.getColor()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Condition condition = (Condition) o; + return Objects.equals(name, condition.name) + && Objects.equals(expression, condition.expression) && tagColor == condition.tagColor; + } + + @Override + public int hashCode() { + return Objects.hash(name, expression, tagColor); + } +} diff --git a/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java new file mode 100644 index 000000000..41a0afbe4 --- /dev/null +++ b/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java @@ -0,0 +1,415 @@ +/* + * Copyright 2020 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.api.core.ApiFuture; +import com.google.common.annotations.VisibleForTesting; +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; + +/** + * This class is the entry point for all server-side Firebase Remote Config actions. + * + *

You can get an instance of {@link FirebaseRemoteConfig} via {@link #getInstance(FirebaseApp)}, + * and then use it to manage Remote Config templates. + */ +public final class FirebaseRemoteConfig { + + private static final String SERVICE_ID = FirebaseRemoteConfig.class.getName(); + private final FirebaseApp app; + private final FirebaseRemoteConfigClient remoteConfigClient; + + @VisibleForTesting + FirebaseRemoteConfig(FirebaseApp app, FirebaseRemoteConfigClient client) { + this.app = checkNotNull(app); + this.remoteConfigClient = checkNotNull(client); + } + + private FirebaseRemoteConfig(FirebaseApp app) { + this(app, FirebaseRemoteConfigClientImpl.fromApp(app)); + } + + /** + * Gets the {@link FirebaseRemoteConfig} instance for the default {@link FirebaseApp}. + * + * @return The {@link FirebaseRemoteConfig} instance for the default {@link FirebaseApp}. + */ + public static FirebaseRemoteConfig getInstance() { + return getInstance(FirebaseApp.getInstance()); + } + + /** + * Gets the {@link FirebaseRemoteConfig} instance for the specified {@link FirebaseApp}. + * + * @return The {@link FirebaseRemoteConfig} instance for the specified {@link FirebaseApp}. + */ + public static synchronized FirebaseRemoteConfig getInstance(FirebaseApp app) { + FirebaseRemoteConfigService service = ImplFirebaseTrampolines.getService(app, SERVICE_ID, + FirebaseRemoteConfigService.class); + if (service == null) { + service = ImplFirebaseTrampolines.addService(app, new FirebaseRemoteConfigService(app)); + } + return service.getInstance(); + } + + /** + * Gets the current active version of the Remote Config template. + * + * @return A {@link Template}. + * @throws FirebaseRemoteConfigException If an error occurs while getting the template. + */ + public Template getTemplate() throws FirebaseRemoteConfigException { + return getTemplateOp().call(); + } + + /** + * Similar to {@link #getTemplate()} but performs the operation asynchronously. + * + * @return An {@code ApiFuture} that completes with a {@link Template} when + * the template is available. + */ + public ApiFuture