Skip to content

Commit d0045e3

Browse files
authored
Resolve StrictMode violation in App Check. (#4085)
* Resolve StrictMode violation in App Check. * Attempt to fix some tests. * Fix unit tests. * Make `retrieveStoredAppCheckTokenInBackground` private instead of package-private. * Move listener invocations back to the main thread while keeping disk write on background thread. * Refactor to use lambda syntax.
1 parent d1ea5b6 commit d0045e3

File tree

2 files changed

+76
-36
lines changed

2 files changed

+76
-36
lines changed

appcheck/firebase-appcheck/src/main/java/com/google/firebase/appcheck/internal/DefaultFirebaseAppCheck.java

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import androidx.annotation.VisibleForTesting;
2222
import com.google.android.gms.tasks.Continuation;
2323
import com.google.android.gms.tasks.Task;
24+
import com.google.android.gms.tasks.TaskCompletionSource;
2425
import com.google.android.gms.tasks.Tasks;
2526
import com.google.firebase.FirebaseApp;
2627
import com.google.firebase.FirebaseException;
@@ -35,6 +36,8 @@
3536
import com.google.firebase.inject.Provider;
3637
import java.util.ArrayList;
3738
import java.util.List;
39+
import java.util.concurrent.ExecutorService;
40+
import java.util.concurrent.Executors;
3841

3942
public class DefaultFirebaseAppCheck extends FirebaseAppCheck {
4043

@@ -46,6 +49,8 @@ public class DefaultFirebaseAppCheck extends FirebaseAppCheck {
4649
private final List<AppCheckListener> appCheckListenerList;
4750
private final StorageHelper storageHelper;
4851
private final TokenRefreshManager tokenRefreshManager;
52+
private final ExecutorService backgroundExecutor;
53+
private final Task<Void> retrieveStoredTokenTask;
4954
private final Clock clock;
5055

5156
private AppCheckProviderFactory appCheckProviderFactory;
@@ -55,6 +60,17 @@ public class DefaultFirebaseAppCheck extends FirebaseAppCheck {
5560
public DefaultFirebaseAppCheck(
5661
@NonNull FirebaseApp firebaseApp,
5762
@NonNull Provider<HeartBeatController> heartBeatController) {
63+
this(
64+
checkNotNull(firebaseApp),
65+
checkNotNull(heartBeatController),
66+
Executors.newCachedThreadPool());
67+
}
68+
69+
@VisibleForTesting
70+
DefaultFirebaseAppCheck(
71+
@NonNull FirebaseApp firebaseApp,
72+
@NonNull Provider<HeartBeatController> heartBeatController,
73+
@NonNull ExecutorService backgroundExecutor) {
5874
checkNotNull(firebaseApp);
5975
checkNotNull(heartBeatController);
6076
this.firebaseApp = firebaseApp;
@@ -65,8 +81,22 @@ public DefaultFirebaseAppCheck(
6581
new StorageHelper(firebaseApp.getApplicationContext(), firebaseApp.getPersistenceKey());
6682
this.tokenRefreshManager =
6783
new TokenRefreshManager(firebaseApp.getApplicationContext(), /* firebaseAppCheck= */ this);
84+
this.backgroundExecutor = backgroundExecutor;
85+
this.retrieveStoredTokenTask = retrieveStoredAppCheckTokenInBackground(backgroundExecutor);
6886
this.clock = new Clock.DefaultClock();
69-
setCachedToken(storageHelper.retrieveAppCheckToken());
87+
}
88+
89+
private Task<Void> retrieveStoredAppCheckTokenInBackground(@NonNull ExecutorService executor) {
90+
TaskCompletionSource<Void> taskCompletionSource = new TaskCompletionSource<>();
91+
executor.execute(
92+
() -> {
93+
AppCheckToken token = storageHelper.retrieveAppCheckToken();
94+
if (token != null) {
95+
setCachedToken(token);
96+
}
97+
taskCompletionSource.setResult(null);
98+
});
99+
return taskCompletionSource.getTask();
70100
}
71101

72102
@Override
@@ -146,44 +176,50 @@ public void removeAppCheckListener(@NonNull AppCheckListener listener) {
146176
@NonNull
147177
@Override
148178
public Task<AppCheckTokenResult> getToken(boolean forceRefresh) {
149-
if (!forceRefresh && hasValidToken()) {
150-
return Tasks.forResult(DefaultAppCheckTokenResult.constructFromAppCheckToken(cachedToken));
151-
}
152-
if (appCheckProvider == null) {
153-
return Tasks.forResult(
154-
DefaultAppCheckTokenResult.constructFromError(
155-
new FirebaseException("No AppCheckProvider installed.")));
156-
}
157-
// TODO: Cache the in-flight task.
158-
return fetchTokenFromProvider()
159-
.continueWithTask(
160-
new Continuation<AppCheckToken, Task<AppCheckTokenResult>>() {
161-
@Override
162-
public Task<AppCheckTokenResult> then(@NonNull Task<AppCheckToken> task) {
163-
if (task.isSuccessful()) {
164-
return Tasks.forResult(
165-
DefaultAppCheckTokenResult.constructFromAppCheckToken(task.getResult()));
166-
}
167-
// If the token exchange failed, return a dummy token for integrators to attach in
168-
// their headers.
169-
return Tasks.forResult(
170-
DefaultAppCheckTokenResult.constructFromError(
171-
new FirebaseException(
172-
task.getException().getMessage(), task.getException())));
173-
}
174-
});
179+
return retrieveStoredTokenTask.continueWithTask(
180+
unused -> {
181+
if (!forceRefresh && hasValidToken()) {
182+
return Tasks.forResult(
183+
DefaultAppCheckTokenResult.constructFromAppCheckToken(cachedToken));
184+
}
185+
if (appCheckProvider == null) {
186+
return Tasks.forResult(
187+
DefaultAppCheckTokenResult.constructFromError(
188+
new FirebaseException("No AppCheckProvider installed.")));
189+
}
190+
// TODO: Cache the in-flight task.
191+
return fetchTokenFromProvider()
192+
.continueWithTask(
193+
appCheckTokenTask -> {
194+
if (appCheckTokenTask.isSuccessful()) {
195+
return Tasks.forResult(
196+
DefaultAppCheckTokenResult.constructFromAppCheckToken(
197+
appCheckTokenTask.getResult()));
198+
}
199+
// If the token exchange failed, return a dummy token for integrators to attach
200+
// in their headers.
201+
return Tasks.forResult(
202+
DefaultAppCheckTokenResult.constructFromError(
203+
new FirebaseException(
204+
appCheckTokenTask.getException().getMessage(),
205+
appCheckTokenTask.getException())));
206+
});
207+
});
175208
}
176209

177210
@NonNull
178211
@Override
179212
public Task<AppCheckToken> getAppCheckToken(boolean forceRefresh) {
180-
if (!forceRefresh && hasValidToken()) {
181-
return Tasks.forResult(cachedToken);
182-
}
183-
if (appCheckProvider == null) {
184-
return Tasks.forException(new FirebaseException("No AppCheckProvider installed."));
185-
}
186-
return fetchTokenFromProvider();
213+
return retrieveStoredTokenTask.continueWithTask(
214+
unused -> {
215+
if (!forceRefresh && hasValidToken()) {
216+
return Tasks.forResult(cachedToken);
217+
}
218+
if (appCheckProvider == null) {
219+
return Tasks.forException(new FirebaseException("No AppCheckProvider installed."));
220+
}
221+
return fetchTokenFromProvider();
222+
});
187223
}
188224

189225
/** Fetches an {@link AppCheckToken} via the installed {@link AppCheckProvider}. */
@@ -227,7 +263,7 @@ void setCachedToken(@NonNull AppCheckToken token) {
227263
* well as the in-memory cached {@link AppCheckToken}.
228264
*/
229265
private void updateStoredToken(@NonNull AppCheckToken token) {
230-
storageHelper.saveAppCheckToken(token);
266+
backgroundExecutor.execute(() -> storageHelper.saveAppCheckToken(token));
231267
setCachedToken(token);
232268

233269
tokenRefreshManager.maybeScheduleTokenRefresh(token);

appcheck/firebase-appcheck/src/test/java/com/google/firebase/appcheck/internal/DefaultFirebaseAppCheckTest.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import androidx.test.core.app.ApplicationProvider;
2525
import com.google.android.gms.tasks.Task;
2626
import com.google.android.gms.tasks.Tasks;
27+
import com.google.common.util.concurrent.MoreExecutors;
2728
import com.google.firebase.FirebaseApp;
2829
import com.google.firebase.appcheck.AppCheckProvider;
2930
import com.google.firebase.appcheck.AppCheckProviderFactory;
@@ -74,7 +75,10 @@ public void setup() {
7475
when(mockAppCheckProvider.getToken()).thenReturn(Tasks.forResult(validDefaultAppCheckToken));
7576

7677
defaultFirebaseAppCheck =
77-
new DefaultFirebaseAppCheck(mockFirebaseApp, () -> mockHeartBeatController);
78+
new DefaultFirebaseAppCheck(
79+
mockFirebaseApp,
80+
() -> mockHeartBeatController,
81+
MoreExecutors.newDirectExecutorService());
7882
}
7983

8084
@Test

0 commit comments

Comments
 (0)