Skip to content

Commit ff414cb

Browse files
AppCheck integration with Firestore (#3027)
* AppCheck integration with Firestore. * Add unit tests. * Apply suggestions from code review Co-authored-by: Sebastian Schmidt <[email protected]> * Simplify the applyRequestMetadata logic. * Switch to using Deferred. Co-authored-by: Sebastian Schmidt <[email protected]>
1 parent 0f2e98a commit ff414cb

20 files changed

+559
-55
lines changed

firebase-firestore/firebase-firestore.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ dependencies {
141141
implementation project(':protolite-well-known-types')
142142
implementation project(':firebase-database-collection')
143143
implementation project(':firebase-components')
144+
implementation project(':appcheck:firebase-appcheck-interop')
144145

145146

146147
//To provide @Generated annotations

firebase-firestore/src/androidTest/java/com/google/firebase/firestore/AccessHelper.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import com.google.android.gms.tasks.Task;
1919
import com.google.firebase.FirebaseApp;
2020
import com.google.firebase.firestore.auth.CredentialsProvider;
21+
import com.google.firebase.firestore.auth.User;
2122
import com.google.firebase.firestore.model.DatabaseId;
2223
import com.google.firebase.firestore.util.AsyncQueue;
2324

@@ -29,15 +30,17 @@ public static FirebaseFirestore newFirebaseFirestore(
2930
Context context,
3031
DatabaseId databaseId,
3132
String persistenceKey,
32-
CredentialsProvider credentialsProvider,
33+
CredentialsProvider<User> authProvider,
34+
CredentialsProvider<String> appCheckProvider,
3335
AsyncQueue asyncQueue,
3436
FirebaseApp firebaseApp,
3537
FirebaseFirestore.InstanceRegistry instanceRegistry) {
3638
return new FirebaseFirestore(
3739
context,
3840
databaseId,
3941
persistenceKey,
40-
credentialsProvider,
42+
authProvider,
43+
appCheckProvider,
4144
asyncQueue,
4245
firebaseApp,
4346
instanceRegistry,

firebase-firestore/src/androidTest/java/com/google/firebase/firestore/remote/RemoteStoreTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public void testRemoteStoreStreamStopsWhenNetworkUnreachable() {
4545
IntegrationTestUtil.testEnvDatabaseInfo(),
4646
testQueue,
4747
null,
48+
null,
4849
ApplicationProvider.getApplicationContext(),
4950
null);
5051
Semaphore networkChangeSemaphore = new Semaphore(0);

firebase-firestore/src/androidTest/java/com/google/firebase/firestore/remote/StreamTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import com.google.firebase.firestore.model.SnapshotVersion;
3333
import com.google.firebase.firestore.model.mutation.Mutation;
3434
import com.google.firebase.firestore.model.mutation.MutationResult;
35+
import com.google.firebase.firestore.testutil.EmptyAppCheckTokenProvider;
3536
import com.google.firebase.firestore.testutil.EmptyCredentialsProvider;
3637
import com.google.firebase.firestore.testutil.IntegrationTestUtil;
3738
import com.google.firebase.firestore.util.AsyncQueue;
@@ -113,6 +114,7 @@ private WriteStream createAndOpenWriteStream(
113114
IntegrationTestUtil.testEnvDatabaseInfo(),
114115
testQueue,
115116
new EmptyCredentialsProvider(),
117+
new EmptyAppCheckTokenProvider(),
116118
ApplicationProvider.getApplicationContext(),
117119
null);
118120
final WriteStream writeStream = datastore.createWriteStream(callback);
@@ -138,6 +140,7 @@ public void testWatchStreamStopBeforeHandshake() throws Exception {
138140
IntegrationTestUtil.testEnvDatabaseInfo(),
139141
testQueue,
140142
new EmptyCredentialsProvider(),
143+
new EmptyAppCheckTokenProvider(),
141144
ApplicationProvider.getApplicationContext(),
142145
mockGrpcProvider);
143146
StreamStatusCallback streamCallback = new StreamStatusCallback() {};
@@ -160,6 +163,7 @@ public void testWriteStreamStopAfterHandshake() throws Exception {
160163
IntegrationTestUtil.testEnvDatabaseInfo(),
161164
testQueue,
162165
new EmptyCredentialsProvider(),
166+
new EmptyAppCheckTokenProvider(),
163167
ApplicationProvider.getApplicationContext(),
164168
null);
165169
final WriteStream[] writeStreamWrapper = new WriteStream[1];
@@ -207,6 +211,7 @@ public void testWriteStreamStopPartial() throws Exception {
207211
IntegrationTestUtil.testEnvDatabaseInfo(),
208212
testQueue,
209213
new EmptyCredentialsProvider(),
214+
new EmptyAppCheckTokenProvider(),
210215
ApplicationProvider.getApplicationContext(),
211216
null);
212217
StreamStatusCallback streamCallback = new StreamStatusCallback() {};
@@ -287,6 +292,7 @@ public void testStreamRefreshesTokenUponExpiration() throws Exception {
287292
IntegrationTestUtil.testEnvDatabaseInfo(),
288293
testQueue,
289294
mockCredentialsProvider,
295+
new EmptyAppCheckTokenProvider(),
290296
ApplicationProvider.getApplicationContext(),
291297
null);
292298
StreamStatusCallback callback = new StreamStatusCallback();
@@ -316,6 +322,7 @@ public void testTokenIsNotInvalidatedOnceStreamIsHealthy() throws Exception {
316322
IntegrationTestUtil.testEnvDatabaseInfo(),
317323
testQueue,
318324
mockCredentialsProvider,
325+
new EmptyAppCheckTokenProvider(),
319326
ApplicationProvider.getApplicationContext(),
320327
null);
321328
StreamStatusCallback callback = new StreamStatusCallback();

firebase-firestore/src/androidTest/java/com/google/firebase/firestore/testutil/IntegrationTestUtil.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ public static FirebaseFirestore testFirestore(
272272
databaseId,
273273
persistenceKey,
274274
MockCredentialsProvider.instance(),
275+
new EmptyAppCheckTokenProvider(),
275276
asyncQueue,
276277
/*firebaseApp=*/ null,
277278
/*instanceRegistry=*/ (dbId) -> {});

firebase-firestore/src/main/java/com/google/firebase/firestore/FirebaseFirestore.java

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@
2727
import com.google.android.gms.tasks.TaskCompletionSource;
2828
import com.google.android.gms.tasks.Tasks;
2929
import com.google.firebase.FirebaseApp;
30+
import com.google.firebase.appcheck.interop.InternalAppCheckTokenProvider;
3031
import com.google.firebase.auth.internal.InternalAuthProvider;
3132
import com.google.firebase.emulators.EmulatedServiceSettings;
3233
import com.google.firebase.firestore.FirebaseFirestoreException.Code;
3334
import com.google.firebase.firestore.auth.CredentialsProvider;
35+
import com.google.firebase.firestore.auth.FirebaseAppCheckTokenProvider;
3436
import com.google.firebase.firestore.auth.FirebaseAuthCredentialsProvider;
37+
import com.google.firebase.firestore.auth.User;
3538
import com.google.firebase.firestore.core.ActivityScope;
3639
import com.google.firebase.firestore.core.AsyncEventListener;
3740
import com.google.firebase.firestore.core.DatabaseInfo;
@@ -85,7 +88,8 @@ public interface InstanceRegistry {
8588
// databaseId itself that needs locking; it just saves us creating a separate lock object.
8689
private final DatabaseId databaseId;
8790
private final String persistenceKey;
88-
private final CredentialsProvider credentialsProvider;
91+
private final CredentialsProvider<User> authProvider;
92+
private final CredentialsProvider<String> appCheckProvider;
8993
private final AsyncQueue asyncQueue;
9094
private final FirebaseApp firebaseApp;
9195
private final UserDataReader userDataReader;
@@ -124,7 +128,8 @@ private static FirebaseFirestore getInstance(@NonNull FirebaseApp app, @NonNull
124128
static FirebaseFirestore newInstance(
125129
@NonNull Context context,
126130
@NonNull FirebaseApp app,
127-
@NonNull Deferred<InternalAuthProvider> authProvider,
131+
@NonNull Deferred<InternalAuthProvider> deferredAuthProvider,
132+
@NonNull Deferred<InternalAppCheckTokenProvider> deferredAppCheckTokenProvider,
128133
@NonNull String database,
129134
@NonNull InstanceRegistry instanceRegistry,
130135
@Nullable GrpcMetadataProvider metadataProvider) {
@@ -136,7 +141,10 @@ static FirebaseFirestore newInstance(
136141

137142
AsyncQueue queue = new AsyncQueue();
138143

139-
CredentialsProvider provider = new FirebaseAuthCredentialsProvider(authProvider);
144+
CredentialsProvider<User> authProvider =
145+
new FirebaseAuthCredentialsProvider(deferredAuthProvider);
146+
CredentialsProvider<String> appCheckProvider =
147+
new FirebaseAppCheckTokenProvider(deferredAppCheckTokenProvider);
140148

141149
// Firestore uses a different database for each app name. Note that we don't use
142150
// app.getPersistenceKey() here because it includes the application ID which is related
@@ -149,7 +157,8 @@ static FirebaseFirestore newInstance(
149157
context,
150158
databaseId,
151159
persistenceKey,
152-
provider,
160+
authProvider,
161+
appCheckProvider,
153162
queue,
154163
app,
155164
instanceRegistry,
@@ -162,7 +171,8 @@ static FirebaseFirestore newInstance(
162171
Context context,
163172
DatabaseId databaseId,
164173
String persistenceKey,
165-
CredentialsProvider credentialsProvider,
174+
CredentialsProvider<User> authProvider,
175+
CredentialsProvider<String> appCheckProvider,
166176
AsyncQueue asyncQueue,
167177
@Nullable FirebaseApp firebaseApp,
168178
InstanceRegistry instanceRegistry,
@@ -171,7 +181,8 @@ static FirebaseFirestore newInstance(
171181
this.databaseId = checkNotNull(checkNotNull(databaseId));
172182
this.userDataReader = new UserDataReader(databaseId);
173183
this.persistenceKey = checkNotNull(persistenceKey);
174-
this.credentialsProvider = checkNotNull(credentialsProvider);
184+
this.authProvider = checkNotNull(authProvider);
185+
this.appCheckProvider = checkNotNull(appCheckProvider);
175186
this.asyncQueue = checkNotNull(asyncQueue);
176187
// NOTE: We allow firebaseApp to be null in tests only.
177188
this.firebaseApp = firebaseApp;
@@ -242,7 +253,13 @@ private void ensureClientConfigured() {
242253

243254
client =
244255
new FirestoreClient(
245-
context, databaseInfo, settings, credentialsProvider, asyncQueue, metadataProvider);
256+
context,
257+
databaseInfo,
258+
settings,
259+
authProvider,
260+
appCheckProvider,
261+
asyncQueue,
262+
metadataProvider);
246263
}
247264
}
248265

firebase-firestore/src/main/java/com/google/firebase/firestore/FirestoreMultiDbComponent.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.google.firebase.FirebaseApp;
2323
import com.google.firebase.FirebaseAppLifecycleListener;
2424
import com.google.firebase.FirebaseOptions;
25+
import com.google.firebase.appcheck.interop.InternalAppCheckTokenProvider;
2526
import com.google.firebase.auth.internal.InternalAuthProvider;
2627
import com.google.firebase.firestore.remote.GrpcMetadataProvider;
2728
import com.google.firebase.inject.Deferred;
@@ -42,16 +43,19 @@ class FirestoreMultiDbComponent
4243
private final FirebaseApp app;
4344
private final Context context;
4445
private final Deferred<InternalAuthProvider> authProvider;
46+
private final Deferred<InternalAppCheckTokenProvider> appCheckProvider;
4547
private final GrpcMetadataProvider metadataProvider;
4648

4749
FirestoreMultiDbComponent(
4850
@NonNull Context context,
4951
@NonNull FirebaseApp app,
5052
@NonNull Deferred<InternalAuthProvider> authProvider,
53+
@NonNull Deferred<InternalAppCheckTokenProvider> appCheckProvider,
5154
@Nullable GrpcMetadataProvider metadataProvider) {
5255
this.context = context;
5356
this.app = app;
5457
this.authProvider = authProvider;
58+
this.appCheckProvider = appCheckProvider;
5559
this.metadataProvider = metadataProvider;
5660
this.app.addLifecycleEventListener(this);
5761
}
@@ -63,7 +67,7 @@ synchronized FirebaseFirestore get(@NonNull String databaseId) {
6367
if (firestore == null) {
6468
firestore =
6569
FirebaseFirestore.newInstance(
66-
context, app, authProvider, databaseId, this, metadataProvider);
70+
context, app, authProvider, appCheckProvider, databaseId, this, metadataProvider);
6771
instances.put(databaseId, firestore);
6872
}
6973
return firestore;

firebase-firestore/src/main/java/com/google/firebase/firestore/FirestoreRegistrar.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import androidx.annotation.RestrictTo;
2020
import com.google.firebase.FirebaseApp;
2121
import com.google.firebase.FirebaseOptions;
22+
import com.google.firebase.appcheck.interop.InternalAppCheckTokenProvider;
2223
import com.google.firebase.auth.internal.InternalAuthProvider;
2324
import com.google.firebase.components.Component;
2425
import com.google.firebase.components.ComponentRegistrar;
@@ -48,13 +49,15 @@ public List<Component<?>> getComponents() {
4849
.add(Dependency.optionalProvider(HeartBeatInfo.class))
4950
.add(Dependency.optionalProvider(UserAgentPublisher.class))
5051
.add(Dependency.deferred(InternalAuthProvider.class))
52+
.add(Dependency.deferred(InternalAppCheckTokenProvider.class))
5153
.add(Dependency.optional(FirebaseOptions.class))
5254
.factory(
5355
c ->
5456
new FirestoreMultiDbComponent(
5557
c.get(Context.class),
5658
c.get(FirebaseApp.class),
5759
c.getDeferred(InternalAuthProvider.class),
60+
c.getDeferred(InternalAppCheckTokenProvider.class),
5861
new FirebaseClientGrpcMetadataProvider(
5962
c.getProvider(UserAgentPublisher.class),
6063
c.getProvider(HeartBeatInfo.class),

firebase-firestore/src/main/java/com/google/firebase/firestore/auth/CredentialsProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import com.google.firebase.firestore.util.Listener;
1919

2020
/** A CredentialsProvider has a method to fetch an authorization token. */
21-
public abstract class CredentialsProvider {
21+
public abstract class CredentialsProvider<T> {
2222
/**
2323
* Requests token for the current user. Use {@link #invalidateToken} to force-refresh the token.
2424
*
@@ -36,7 +36,7 @@ public abstract class CredentialsProvider {
3636
* Sets the listener to be notified of credential changes (sign-in / sign-out, token changes). It
3737
* is immediately called once with the initial user.
3838
*/
39-
public abstract void setChangeListener(Listener<User> changeListener);
39+
public abstract void setChangeListener(Listener<T> changeListener);
4040

4141
/** Removes the listener set with {@link #setChangeListener}. */
4242
public abstract void removeChangeListener();

0 commit comments

Comments
 (0)