From 6cc75cdff865392385f75239852cf5f370ec7698 Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Paz Date: Wed, 27 May 2026 17:37:19 -0400 Subject: [PATCH 1/2] [AppCheck] Recaptcha: use site key from FirebaseOptions Removed the requirement to provide the site key manually during instantiation of RecaptchaEnterpriseAppCheckProviderFactory. The factory now retrieves the site key directly from FirebaseOptions via the FirebaseApp instance provided during the create method. --- .../api.txt | 2 +- ...rebase-appcheck-recaptchaenterprise.gradle | 2 +- ...tchaEnterpriseAppCheckProviderFactory.java | 16 ++++---- ...EnterpriseAppCheckProviderFactoryTest.java | 39 ++++++++++++------- 4 files changed, 35 insertions(+), 24 deletions(-) diff --git a/appcheck/firebase-appcheck-recaptchaenterprise/api.txt b/appcheck/firebase-appcheck-recaptchaenterprise/api.txt index 99d21ff038c..eb46074a5fe 100644 --- a/appcheck/firebase-appcheck-recaptchaenterprise/api.txt +++ b/appcheck/firebase-appcheck-recaptchaenterprise/api.txt @@ -3,7 +3,7 @@ package com.google.firebase.appcheck.recaptchaenterprise { public class RecaptchaEnterpriseAppCheckProviderFactory implements com.google.firebase.appcheck.AppCheckProviderFactory { method public com.google.firebase.appcheck.AppCheckProvider create(com.google.firebase.FirebaseApp); - method public static com.google.firebase.appcheck.recaptchaenterprise.RecaptchaEnterpriseAppCheckProviderFactory getInstance(String); + method public static com.google.firebase.appcheck.recaptchaenterprise.RecaptchaEnterpriseAppCheckProviderFactory getInstance(); } } diff --git a/appcheck/firebase-appcheck-recaptchaenterprise/firebase-appcheck-recaptchaenterprise.gradle b/appcheck/firebase-appcheck-recaptchaenterprise/firebase-appcheck-recaptchaenterprise.gradle index 045736b6f4f..07142dbd23e 100644 --- a/appcheck/firebase-appcheck-recaptchaenterprise/firebase-appcheck-recaptchaenterprise.gradle +++ b/appcheck/firebase-appcheck-recaptchaenterprise/firebase-appcheck-recaptchaenterprise.gradle @@ -50,7 +50,7 @@ dependencies { implementation(libs.dagger.dagger) api project(':appcheck:firebase-appcheck') - api libs.firebase.common + api project(':firebase-common') api libs.firebase.components api 'com.google.android.recaptcha:recaptcha:18.7.1' diff --git a/appcheck/firebase-appcheck-recaptchaenterprise/src/main/java/com/google/firebase/appcheck/recaptchaenterprise/RecaptchaEnterpriseAppCheckProviderFactory.java b/appcheck/firebase-appcheck-recaptchaenterprise/src/main/java/com/google/firebase/appcheck/recaptchaenterprise/RecaptchaEnterpriseAppCheckProviderFactory.java index e051f54d0e9..ddb0d1e0815 100644 --- a/appcheck/firebase-appcheck-recaptchaenterprise/src/main/java/com/google/firebase/appcheck/recaptchaenterprise/RecaptchaEnterpriseAppCheckProviderFactory.java +++ b/appcheck/firebase-appcheck-recaptchaenterprise/src/main/java/com/google/firebase/appcheck/recaptchaenterprise/RecaptchaEnterpriseAppCheckProviderFactory.java @@ -15,13 +15,13 @@ package com.google.firebase.appcheck.recaptchaenterprise; import androidx.annotation.NonNull; +import com.google.android.gms.common.internal.Preconditions; import com.google.firebase.FirebaseApp; import com.google.firebase.appcheck.AppCheckProvider; import com.google.firebase.appcheck.AppCheckProviderFactory; import com.google.firebase.appcheck.FirebaseAppCheck; import com.google.firebase.appcheck.recaptchaenterprise.internal.ProviderMultiResourceComponent; import com.google.firebase.appcheck.recaptchaenterprise.internal.RecaptchaEnterpriseAppCheckProvider; -import java.util.Objects; /** * Implementation of an {@link AppCheckProviderFactory} that builds
@@ -29,18 +29,14 @@ */ public class RecaptchaEnterpriseAppCheckProviderFactory implements AppCheckProviderFactory { - private final String siteKey; private volatile RecaptchaEnterpriseAppCheckProvider provider; - private RecaptchaEnterpriseAppCheckProviderFactory(@NonNull String siteKey) { - this.siteKey = siteKey; - } + private RecaptchaEnterpriseAppCheckProviderFactory() {} /** Gets an instance of this class for installation into a {@link FirebaseAppCheck} instance. */ @NonNull - public static RecaptchaEnterpriseAppCheckProviderFactory getInstance(@NonNull String siteKey) { - Objects.requireNonNull(siteKey, "siteKey cannot be null"); - return new RecaptchaEnterpriseAppCheckProviderFactory(siteKey); + public static RecaptchaEnterpriseAppCheckProviderFactory getInstance() { + return new RecaptchaEnterpriseAppCheckProviderFactory(); } @NonNull @@ -50,6 +46,10 @@ public AppCheckProvider create(@NonNull FirebaseApp firebaseApp) { if (provider == null) { synchronized (this) { if (provider == null) { + String siteKey = firebaseApp.getOptions().getRecaptchaSiteKey(); + Preconditions.checkNotEmpty( + siteKey, + "Missing site key from configuration. Verify your google-services.json file is updated."); ProviderMultiResourceComponent component = firebaseApp.get(ProviderMultiResourceComponent.class); provider = component.get(siteKey); diff --git a/appcheck/firebase-appcheck-recaptchaenterprise/src/test/java/com/google/firebase/appcheck/recaptchaenterprise/RecaptchaEnterpriseAppCheckProviderFactoryTest.java b/appcheck/firebase-appcheck-recaptchaenterprise/src/test/java/com/google/firebase/appcheck/recaptchaenterprise/RecaptchaEnterpriseAppCheckProviderFactoryTest.java index 865b69e409c..84bf10994fb 100644 --- a/appcheck/firebase-appcheck-recaptchaenterprise/src/test/java/com/google/firebase/appcheck/recaptchaenterprise/RecaptchaEnterpriseAppCheckProviderFactoryTest.java +++ b/appcheck/firebase-appcheck-recaptchaenterprise/src/test/java/com/google/firebase/appcheck/recaptchaenterprise/RecaptchaEnterpriseAppCheckProviderFactoryTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.when; import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; import com.google.firebase.appcheck.AppCheckProvider; import com.google.firebase.appcheck.recaptchaenterprise.internal.ProviderMultiResourceComponent; import com.google.firebase.appcheck.recaptchaenterprise.internal.RecaptchaEnterpriseAppCheckProvider; @@ -31,50 +32,60 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; /** Tests for {@link RecaptchaEnterpriseAppCheckProviderFactory}. */ -@RunWith(MockitoJUnitRunner.class) +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) public class RecaptchaEnterpriseAppCheckProviderFactoryTest { static final String SITE_KEY_1 = "siteKey1"; @Mock private FirebaseApp mockFirebaseApp; + @Mock private FirebaseOptions mockFirebaseOptions; @Mock private ProviderMultiResourceComponent mockComponent; @Mock private RecaptchaEnterpriseAppCheckProvider mockProvider; @Before public void setUp() { + MockitoAnnotations.openMocks(this); when(mockFirebaseApp.get(eq(ProviderMultiResourceComponent.class))).thenReturn(mockComponent); when(mockComponent.get(anyString())).thenReturn(mockProvider); + when(mockFirebaseApp.getOptions()).thenReturn(mockFirebaseOptions); } @Test - public void getInstance_nonNullSiteKey_returnsNonNullInstance() { + public void getInstance_returnsNonNullInstance() { RecaptchaEnterpriseAppCheckProviderFactory factory = - RecaptchaEnterpriseAppCheckProviderFactory.getInstance(SITE_KEY_1); + RecaptchaEnterpriseAppCheckProviderFactory.getInstance(); assertNotNull(factory); } @Test - public void getInstance_nullSiteKey_expectThrows() { - assertThrows( - NullPointerException.class, - () -> RecaptchaEnterpriseAppCheckProviderFactory.getInstance(null)); - } - - @Test - public void create_nonNullFirebaseApp_returnsRecaptchaEnterpriseAppCheckProvider() { + public void create_siteKeyInOptions_returnsRecaptchaEnterpriseAppCheckProvider() { + when(mockFirebaseOptions.getRecaptchaSiteKey()).thenReturn(SITE_KEY_1); RecaptchaEnterpriseAppCheckProviderFactory factory = - RecaptchaEnterpriseAppCheckProviderFactory.getInstance(SITE_KEY_1); + RecaptchaEnterpriseAppCheckProviderFactory.getInstance(); AppCheckProvider provider = factory.create(mockFirebaseApp); assertNotNull(provider); assertEquals(RecaptchaEnterpriseAppCheckProvider.class, provider.getClass()); + verify(mockComponent).get(SITE_KEY_1); + } + + @Test + public void create_noSiteKeyInOptionsOrFactory_expectThrows() { + when(mockFirebaseOptions.getRecaptchaSiteKey()).thenReturn(null); + RecaptchaEnterpriseAppCheckProviderFactory factory = + RecaptchaEnterpriseAppCheckProviderFactory.getInstance(); + assertThrows(IllegalArgumentException.class, () -> factory.create(mockFirebaseApp)); } @Test public void create_callMultipleTimes_providerIsInitializedOnlyOnce() { + when(mockFirebaseOptions.getRecaptchaSiteKey()).thenReturn(SITE_KEY_1); RecaptchaEnterpriseAppCheckProviderFactory factory = - RecaptchaEnterpriseAppCheckProviderFactory.getInstance(SITE_KEY_1); + RecaptchaEnterpriseAppCheckProviderFactory.getInstance(); factory.create(mockFirebaseApp); factory.create(mockFirebaseApp); From c704097123717044cb43700e0788cab8505e2a58 Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Paz Date: Wed, 27 May 2026 18:24:43 -0400 Subject: [PATCH 2/2] Make `RecaptchaEnterpriseAppCheckProviderFactory` stateless --- ...tchaEnterpriseAppCheckProviderFactory.java | 24 +++++++------------ ...EnterpriseAppCheckProviderFactoryTest.java | 6 +++-- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/appcheck/firebase-appcheck-recaptchaenterprise/src/main/java/com/google/firebase/appcheck/recaptchaenterprise/RecaptchaEnterpriseAppCheckProviderFactory.java b/appcheck/firebase-appcheck-recaptchaenterprise/src/main/java/com/google/firebase/appcheck/recaptchaenterprise/RecaptchaEnterpriseAppCheckProviderFactory.java index ddb0d1e0815..6a559ee37bf 100644 --- a/appcheck/firebase-appcheck-recaptchaenterprise/src/main/java/com/google/firebase/appcheck/recaptchaenterprise/RecaptchaEnterpriseAppCheckProviderFactory.java +++ b/appcheck/firebase-appcheck-recaptchaenterprise/src/main/java/com/google/firebase/appcheck/recaptchaenterprise/RecaptchaEnterpriseAppCheckProviderFactory.java @@ -29,8 +29,6 @@ */ public class RecaptchaEnterpriseAppCheckProviderFactory implements AppCheckProviderFactory { - private volatile RecaptchaEnterpriseAppCheckProvider provider; - private RecaptchaEnterpriseAppCheckProviderFactory() {} /** Gets an instance of this class for installation into a {@link FirebaseAppCheck} instance. */ @@ -43,20 +41,14 @@ public static RecaptchaEnterpriseAppCheckProviderFactory getInstance() { @Override @SuppressWarnings("FirebaseUseExplicitDependencies") public AppCheckProvider create(@NonNull FirebaseApp firebaseApp) { - if (provider == null) { - synchronized (this) { - if (provider == null) { - String siteKey = firebaseApp.getOptions().getRecaptchaSiteKey(); - Preconditions.checkNotEmpty( - siteKey, - "Missing site key from configuration. Verify your google-services.json file is updated."); - ProviderMultiResourceComponent component = - firebaseApp.get(ProviderMultiResourceComponent.class); - provider = component.get(siteKey); - provider.initializeRecaptchaClient(); - } - } - } + String siteKey = firebaseApp.getOptions().getRecaptchaSiteKey(); + Preconditions.checkNotEmpty( + siteKey, + "Missing site key from configuration. Verify your google-services.json file is updated."); + ProviderMultiResourceComponent component = + firebaseApp.get(ProviderMultiResourceComponent.class); + RecaptchaEnterpriseAppCheckProvider provider = component.get(siteKey); + provider.initializeRecaptchaClient(); return provider; } } diff --git a/appcheck/firebase-appcheck-recaptchaenterprise/src/test/java/com/google/firebase/appcheck/recaptchaenterprise/RecaptchaEnterpriseAppCheckProviderFactoryTest.java b/appcheck/firebase-appcheck-recaptchaenterprise/src/test/java/com/google/firebase/appcheck/recaptchaenterprise/RecaptchaEnterpriseAppCheckProviderFactoryTest.java index 84bf10994fb..cca7f71832d 100644 --- a/appcheck/firebase-appcheck-recaptchaenterprise/src/test/java/com/google/firebase/appcheck/recaptchaenterprise/RecaptchaEnterpriseAppCheckProviderFactoryTest.java +++ b/appcheck/firebase-appcheck-recaptchaenterprise/src/test/java/com/google/firebase/appcheck/recaptchaenterprise/RecaptchaEnterpriseAppCheckProviderFactoryTest.java @@ -82,7 +82,7 @@ public void create_noSiteKeyInOptionsOrFactory_expectThrows() { } @Test - public void create_callMultipleTimes_providerIsInitializedOnlyOnce() { + public void create_callMultipleTimes_initializesProviderEveryTime() { when(mockFirebaseOptions.getRecaptchaSiteKey()).thenReturn(SITE_KEY_1); RecaptchaEnterpriseAppCheckProviderFactory factory = RecaptchaEnterpriseAppCheckProviderFactory.getInstance(); @@ -90,6 +90,8 @@ public void create_callMultipleTimes_providerIsInitializedOnlyOnce() { factory.create(mockFirebaseApp); factory.create(mockFirebaseApp); factory.create(mockFirebaseApp); - verify(mockProvider, times(1)).initializeRecaptchaClient(); + + verify(mockComponent, times(3)).get(SITE_KEY_1); + verify(mockProvider, times(3)).initializeRecaptchaClient(); } }