diff --git a/firebase-installations/firebase-installations.gradle b/firebase-installations/firebase-installations.gradle index d44b7af0ab2..57d78c7ce9c 100644 --- a/firebase-installations/firebase-installations.gradle +++ b/firebase-installations/firebase-installations.gradle @@ -44,6 +44,7 @@ dependencies { implementation 'androidx.multidex:multidex:2.0.1' implementation 'com.google.android.gms:play-services-tasks:17.0.0' + implementation 'com.google.re2j:re2j:1.0' compileOnly "com.google.auto.value:auto-value-annotations:1.6.5" diff --git a/firebase-installations/src/main/java/com/google/firebase/installations/FirebaseInstallations.java b/firebase-installations/src/main/java/com/google/firebase/installations/FirebaseInstallations.java index 00bba324921..7aaa5518cd1 100644 --- a/firebase-installations/src/main/java/com/google/firebase/installations/FirebaseInstallations.java +++ b/firebase-installations/src/main/java/com/google/firebase/installations/FirebaseInstallations.java @@ -143,18 +143,19 @@ public Thread newThread(Runnable r) { * or empty. */ private void preConditionChecks() { - Preconditions.checkNotEmpty( - getApplicationId(), - "Please set your Application ID. A valid Firebase App ID is required to communicate " - + "with Firebase server APIs: It identifies your application with Firebase."); - Preconditions.checkNotEmpty( - getProjectIdentifier(), - "Please set your project ID. A valid Firebase project ID is required to communicate " - + "with Firebase server APIs: It identifies your project with Google."); - Preconditions.checkNotEmpty( - getApiKey(), - "Please set a valid API key. A Firebase API key is required to communicate with " - + "Firebase server APIs: It authenticates your project with Google."); + Preconditions.checkNotEmpty(getApplicationId()); + Preconditions.checkNotEmpty(getProjectIdentifier()); + Preconditions.checkNotEmpty(getApiKey()); + Preconditions.checkArgument( + Utils.isValidAppIdFormat(getApplicationId()), + "Please set your Application ID. A valid Firebase App ID is required to communicate " + + "with Firebase server APIs: It identifies your application with Firebase." + + "Please refer to https://firebase.google.com/support/privacy/init-options."); + Preconditions.checkArgument( + Utils.isValidApiKeyFormat(getApiKey()), + "Please set a valid API key. A Firebase API key is required to communicate with " + + "Firebase server APIs: It authenticates your project with Google." + + "Please refer to https://firebase.google.com/support/privacy/init-options."); } /** Returns the Project Id or Project Number for the Firebase Project. */ diff --git a/firebase-installations/src/main/java/com/google/firebase/installations/Utils.java b/firebase-installations/src/main/java/com/google/firebase/installations/Utils.java index fb8b0deeb19..5b228527195 100644 --- a/firebase-installations/src/main/java/com/google/firebase/installations/Utils.java +++ b/firebase-installations/src/main/java/com/google/firebase/installations/Utils.java @@ -15,13 +15,21 @@ package com.google.firebase.installations; import android.text.TextUtils; + +import androidx.annotation.NonNull; + import com.google.firebase.installations.local.PersistedInstallationEntry; import java.util.concurrent.TimeUnit; +import com.google.re2j.Pattern; + /** Util methods used for {@link FirebaseInstallations} */ class Utils { public static final long AUTH_TOKEN_EXPIRATION_BUFFER_IN_SECS = TimeUnit.HOURS.toSeconds(1); + private static final String APP_ID_IDENTIFICATION_SUBSTRING = ":"; + private static final Pattern API_KEY_FORMAT = Pattern.compile("\\AA[\\w-]{38}\\z"); + /** * Checks if the FIS Auth token is expired or going to expire in next 1 hour {@link * #AUTH_TOKEN_EXPIRATION_BUFFER_IN_SECS}. @@ -41,4 +49,12 @@ public boolean isAuthTokenExpired(PersistedInstallationEntry entry) { public long currentTimeInSecs() { return TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()); } + + static boolean isValidAppIdFormat(@NonNull String appId) { + return appId.contains(APP_ID_IDENTIFICATION_SUBSTRING); + } + + static boolean isValidApiKeyFormat(@NonNull String apiKey) { + return API_KEY_FORMAT.matcher(apiKey).matches(); + } } diff --git a/firebase-installations/src/test/java/com/google/firebase/installations/FirebaseInstallationsTest.java b/firebase-installations/src/test/java/com/google/firebase/installations/FirebaseInstallationsTest.java index afd26f91b0b..88c820e46c1 100644 --- a/firebase-installations/src/test/java/com/google/firebase/installations/FirebaseInstallationsTest.java +++ b/firebase-installations/src/test/java/com/google/firebase/installations/FirebaseInstallationsTest.java @@ -17,6 +17,7 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -82,7 +83,7 @@ public class FirebaseInstallationsTest { public static final String TEST_AUTH_TOKEN_3 = "fis.auth.token3"; public static final String TEST_AUTH_TOKEN_4 = "fis.auth.token4"; - public static final String TEST_API_KEY = "apiKey"; + public static final String TEST_API_KEY = "AIzaSyabcdefghijklmnopqrstuvwxyz1234567"; public static final String TEST_REFRESH_TOKEN = "1:test-refresh-token"; @@ -965,4 +966,43 @@ public void testDelete_networkError() throws Exception { entry.isRegistered()); } } + + @Test + public void testAppIdCheck() { + // valid appid + assertTrue(Utils.isValidAppIdFormat("1:123456789:android:abcdef")); + assertTrue(Utils.isValidAppIdFormat("1:515438998704:android:e78ec19738058349")); + assertTrue(Utils.isValidAppIdFormat("1:208472424340:android:a243f98a00873753")); + assertTrue(Utils.isValidAppIdFormat("1:755541669657:ios:4d6d5a5ce71e9d30")); + assertTrue(Utils.isValidAppIdFormat("1:1086610230652:ios:852c7f6ee799ff89")); + assertTrue(Utils.isValidAppIdFormat("1:35006771263:web:32b6f4a5b95acd2c")); + // invalid appid + assertFalse(Utils.isValidAppIdFormat("abc.abc.abc")); + assertFalse( + Utils.isValidAppIdFormat( + "com.google.firebase.samples.messaging.advanced")); // using pakage name as App ID + } + + @Test + public void testApiKeyCheck() { + // valid ApiKey + assertTrue(Utils.isValidApiKeyFormat("AIzaSyabcdefghijklmnopqrstuvwxyz1234567")); + assertTrue(Utils.isValidApiKeyFormat("AIzaSyA4UrcGxgwQFTfaI3no3t7Lt1sjmdnP5sQ")); + assertTrue(Utils.isValidApiKeyFormat("AIzaSyA5_iVawFQ8ABuTZNUdcwERLJv_a_p4wtM")); + assertTrue(Utils.isValidApiKeyFormat("AIzaSyANUvH9H9BsUccjsu2pCmEkOPjjaXeDQgY")); + assertTrue(Utils.isValidApiKeyFormat("AIzaSyASWm6HmTMdYWpgMnjRBjxcQ9CKctWmLd4")); + assertTrue(Utils.isValidApiKeyFormat("AIzaSyAdOS2zB6NCsk1pCdZ4-P6GBdi_UUPwX7c")); + assertTrue(Utils.isValidApiKeyFormat("AIzaSyAnLA7NfeLquW1tJFpx_eQCxoX-oo6YyIs")); + // invalid ApiKey + assertFalse( + Utils.isValidApiKeyFormat("BIzaSyabcdefghijklmnopqrstuvwxyz1234567")); // wrong prefix + assertFalse(Utils.isValidApiKeyFormat("AIzaSyabcdefghijklmnopqrstuvwxyz")); // wrong length + assertFalse(Utils.isValidApiKeyFormat("AIzaSyabcdefghijklmno:qrstuvwxyzabcdefg")); // wrong char + assertFalse(Utils.isValidApiKeyFormat("AIzaSyabcdefghijklmno qrstuvwxyzabcdefg")); // wrong char + assertFalse( + Utils.isValidApiKeyFormat( + "AAAAdpB7anM:APA91bFFK03DIT8y3l5uymwbKcUDJdYqTRSP9Qcxg8SU5kKPalEpObdx0C0xv8gQttdWlL" + + "W4hLvvHA0JoDKA6Lrvbi-edUjFCPY_WJkuvHxFwGWXjnj4yI4sPQ27mXuSVIyAbgX4aTK0QY" + + "pIKq2j1NBi7ZU75gunQg")); // using FCM server key as API key. + } }