Skip to content

Commit c9fa461

Browse files
authored
Force validation or rotation of FIDs (#5812)
Force validation or rotation of FIDs. This also cleans up the tests a bit more, and isolated Crashlytics integration tests from Sessions tests.
1 parent 936112f commit c9fa461

File tree

16 files changed

+230
-109
lines changed

16 files changed

+230
-109
lines changed

firebase-crashlytics/CHANGELOG.md

Lines changed: 109 additions & 55 deletions
Large diffs are not rendered by default.

firebase-crashlytics/ktx/src/androidTest/kotlin/com/google/firebase/crashlytics/ktx/CrashlyticsTests.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,14 @@ import org.junit.runner.RunWith
3535
class CrashlyticsTests {
3636
@Before
3737
fun setUp() {
38+
@Suppress("DEPRECATION")
3839
Firebase.initialize(
3940
ApplicationProvider.getApplicationContext(),
4041
FirebaseOptions.Builder()
4142
.setApplicationId(APP_ID)
4243
.setApiKey(API_KEY)
4344
.setProjectId(PROJECT_ID)
44-
.build()
45+
.build(),
4546
)
4647
}
4748

@@ -57,7 +58,7 @@ class CrashlyticsTests {
5758

5859
@Test
5960
fun libraryRegistrationAtRuntime() {
60-
val publisher = Firebase.app.get(UserAgentPublisher::class.java)
61+
Firebase.app.get(UserAgentPublisher::class.java)
6162
}
6263

6364
companion object {
Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
2-
3-
<application>
4-
5-
<meta-data android:name="io.fabric.ApiKey"
6-
android:value="a000000000000000000000000000000000000000"/>
7-
8-
</application>
9-
1+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2+
xmlns:tools="http://schemas.android.com/tools">
3+
4+
<application>
5+
<service
6+
android:enabled="false"
7+
android:name="com.google.firebase.sessions.SessionLifecycleService"
8+
tools:replace="android:enabled" />
9+
</application>
1010
</manifest>

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/CrashlyticsTests.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class CrashlyticsTests {
4040
.setApplicationId(APP_ID)
4141
.setApiKey(API_KEY)
4242
.setProjectId(PROJECT_ID)
43-
.build()
43+
.build(),
4444
)
4545
}
4646

@@ -56,7 +56,7 @@ class CrashlyticsTests {
5656

5757
@Test
5858
fun libraryRegistrationAtRuntime() {
59-
val publisher = Firebase.app.get(UserAgentPublisher::class.java)
59+
Firebase.app.get(UserAgentPublisher::class.java)
6060
}
6161

6262
companion object {

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/SessionReportingCoordinatorTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ public void onReportSend_successfulReportsAreDeleted() {
497497
when(reportSender.enqueueReport(mockReport1, false)).thenReturn(successfulTask);
498498
when(reportSender.enqueueReport(mockReport2, false)).thenReturn(failedTask);
499499

500-
when(idManager.fetchTrueFid()).thenReturn("fid");
500+
when(idManager.fetchTrueFid()).thenReturn(new FirebaseInstallationId("fid", "authToken"));
501501
reportingCoordinator.sendReports(Runnable::run);
502502

503503
verify(reportSender).enqueueReport(mockReport1, false);
@@ -548,6 +548,7 @@ private static CrashlyticsReport mockReport(String sessionId) {
548548
when(mockSession.getIdentifier()).thenReturn(sessionId);
549549
when(mockReport.getSession()).thenReturn(mockSession);
550550
when(mockReport.withFirebaseInstallationId(anyString())).thenReturn(mockReport);
551+
when(mockReport.withFirebaseAuthenticationToken(anyString())).thenReturn(mockReport);
551552
return mockReport;
552553
}
553554

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/persistence/CrashlyticsReportPersistenceTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,8 @@ private static CrashlyticsReport.Builder makeIncompleteReport() {
837837
.setGmpAppId("gmpAppId")
838838
.setPlatform(1)
839839
.setInstallationUuid("installationId")
840+
.setFirebaseInstallationId("firebaseInstallationId")
841+
.setFirebaseAuthenticationToken("firebaseAuthenticationToken")
840842
.setBuildVersion("1")
841843
.setDisplayVersion("1.0.0");
842844
}

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/ktx/CrashlyticsTests.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,14 @@ import org.junit.runner.RunWith
3535
class CrashlyticsTests {
3636
@Before
3737
fun setUp() {
38+
@Suppress("DEPRECATION")
3839
Firebase.initialize(
3940
ApplicationProvider.getApplicationContext(),
4041
FirebaseOptions.Builder()
4142
.setApplicationId(APP_ID)
4243
.setApiKey(API_KEY)
4344
.setProjectId(PROJECT_ID)
44-
.build()
45+
.build(),
4546
)
4647
}
4748

@@ -57,7 +58,7 @@ class CrashlyticsTests {
5758

5859
@Test
5960
fun libraryRegistrationAtRuntime() {
60-
val publisher = Firebase.app.get(UserAgentPublisher::class.java)
61+
Firebase.app.get(UserAgentPublisher::class.java)
6162
}
6263

6364
companion object {

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/CrashlyticsReportDataCapture.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ private CrashlyticsReport.Builder buildReportData() {
172172
.setGmpAppId(appData.googleAppId)
173173
.setInstallationUuid(idManager.getInstallIds().getCrashlyticsInstallId())
174174
.setFirebaseInstallationId(idManager.getInstallIds().getFirebaseInstallationId())
175+
.setFirebaseAuthenticationToken(idManager.getInstallIds().getFirebaseAuthenticationToken())
175176
.setBuildVersion(appData.versionCode)
176177
.setDisplayVersion(appData.versionName)
177178
.setPlatform(REPORT_ANDROID_PLATFORM);
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.crashlytics.internal.common
18+
19+
/**
20+
* Simple data class to contain the true Firebase installation id and Firebase authentication token.
21+
*
22+
* <p>This is not the Crashlytics installation id.
23+
*/
24+
data class FirebaseInstallationId(val fid: String?, val authToken: String?)

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/IdManager.java

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@
1414

1515
package com.google.firebase.crashlytics.internal.common;
1616

17+
import static com.google.firebase.crashlytics.internal.common.Utils.awaitEvenIfOnMainThread;
18+
1719
import android.content.Context;
1820
import android.content.SharedPreferences;
1921
import android.os.Build;
2022
import androidx.annotation.NonNull;
21-
import androidx.annotation.Nullable;
22-
import androidx.annotation.VisibleForTesting;
23-
import com.google.android.gms.tasks.Task;
2423
import com.google.firebase.crashlytics.internal.Logger;
2524
import com.google.firebase.installations.FirebaseInstallationsApi;
2625
import java.util.Locale;
26+
import java.util.Objects;
2727
import java.util.UUID;
2828
import java.util.regex.Pattern;
2929

@@ -48,7 +48,7 @@ public class IdManager implements InstallIdProvider {
4848
private final String appIdentifier;
4949

5050
// The FirebaseInstallationsApi encapsulates a Firebase-wide install id
51-
private final FirebaseInstallationsApi firebaseInstallationsApi;
51+
private final FirebaseInstallationsApi firebaseInstallations;
5252

5353
private final DataCollectionArbiter dataCollectionArbiter;
5454

@@ -65,7 +65,7 @@ public class IdManager implements InstallIdProvider {
6565
public IdManager(
6666
Context appContext,
6767
String appIdentifier,
68-
FirebaseInstallationsApi firebaseInstallationsApi,
68+
FirebaseInstallationsApi firebaseInstallations,
6969
DataCollectionArbiter dataCollectionArbiter) {
7070
if (appContext == null) {
7171
throw new IllegalArgumentException("appContext must not be null");
@@ -75,18 +75,16 @@ public IdManager(
7575
}
7676
this.appContext = appContext;
7777
this.appIdentifier = appIdentifier;
78-
this.firebaseInstallationsApi = firebaseInstallationsApi;
78+
this.firebaseInstallations = firebaseInstallations;
7979
this.dataCollectionArbiter = dataCollectionArbiter;
8080

8181
installerPackageNameProvider = new InstallerPackageNameProvider();
8282
}
8383

84-
/**
85-
* Apply consistent formatting and stripping of special characters. Null input is allowed, will
86-
* return null.
87-
*/
88-
private static String formatId(String id) {
89-
return (id == null) ? null : ID_PATTERN.matcher(id).replaceAll("").toLowerCase(Locale.US);
84+
/** Apply consistent formatting and stripping of special characters. */
85+
@NonNull
86+
private static String formatId(@NonNull String id) {
87+
return ID_PATTERN.matcher(id).replaceAll("").toLowerCase(Locale.US);
9088
}
9189

9290
/**
@@ -115,21 +113,23 @@ public synchronized InstallIds getInstallIds() {
115113
// We only look at the FID if Crashlytics data collection is enabled, since querying it can
116114
// result in a network call that registers the FID with Firebase.
117115
if (dataCollectionArbiter.isAutomaticDataCollectionEnabled()) {
118-
String trueFid = fetchTrueFid();
116+
FirebaseInstallationId trueFid = fetchTrueFid();
119117
Logger.getLogger().v("Fetched Firebase Installation ID: " + trueFid);
120118

121-
if (trueFid == null) {
119+
if (trueFid.getFid() == null) {
122120
// This shouldn't happen often. We will assume the cached FID is valid, if it exists.
123121
// Otherwise, the safest thing to do is to create a synthetic ID instead
124-
trueFid = (cachedFid == null ? createSyntheticFid() : cachedFid);
122+
trueFid =
123+
new FirebaseInstallationId(cachedFid == null ? createSyntheticFid() : cachedFid, null);
125124
}
126125

127-
if (trueFid.equals(cachedFid)) {
126+
if (Objects.equals(trueFid.getFid(), cachedFid)) {
128127
// the current FID is the same as the cached FID, so we keep the cached Crashlytics ID
129128
installIds = InstallIds.create(readCachedCrashlyticsInstallId(prefs), trueFid);
130129
} else {
131130
// the current FID has changed, so we generate a new Crashlytics ID
132-
installIds = InstallIds.create(createAndCacheCrashlyticsInstallId(trueFid, prefs), trueFid);
131+
installIds =
132+
InstallIds.create(createAndCacheCrashlyticsInstallId(trueFid.getFid(), prefs), trueFid);
133133
}
134134
} else { // data collection is NOT enabled; we can't use the FID
135135
if (isSyntheticFid(cachedFid)) {
@@ -171,19 +171,29 @@ private String readCachedCrashlyticsInstallId(SharedPreferences prefs) {
171171
return prefs.getString(PREFKEY_INSTALLATION_UUID, null);
172172
}
173173

174-
/** Makes a blocking call to query FID. If the call fails, logs a warning and returns null. */
175-
@Nullable
176-
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
177-
public String fetchTrueFid() {
178-
Task<String> currentFidTask = firebaseInstallationsApi.getId();
179-
String currentFid = null;
174+
/**
175+
* Makes a blocking call to query the Firebase installation id and Firebase authentication token.
176+
*
177+
* <p>If either call fails for any reason, logs a warning and sets a null value for that field.
178+
*/
179+
@NonNull
180+
public FirebaseInstallationId fetchTrueFid() {
181+
String fid = null;
182+
String authToken = null;
180183

184+
// Fetch the auth token first, so the fid will be validated.
181185
try {
182-
currentFid = Utils.awaitEvenIfOnMainThread(currentFidTask);
183-
} catch (Exception e) {
184-
Logger.getLogger().w("Failed to retrieve Firebase Installation ID.", e);
186+
authToken = awaitEvenIfOnMainThread(firebaseInstallations.getToken(false)).getToken();
187+
} catch (Exception ex) {
188+
Logger.getLogger().w("Error getting Firebase authentication token.", ex);
185189
}
186-
return currentFid;
190+
try {
191+
fid = awaitEvenIfOnMainThread(firebaseInstallations.getId());
192+
} catch (Exception ex) {
193+
Logger.getLogger().w("Error getting Firebase installation id.", ex);
194+
}
195+
196+
return new FirebaseInstallationId(fid, authToken);
187197
}
188198

189199
@NonNull

0 commit comments

Comments
 (0)