Skip to content

Commit 51175b5

Browse files
committed
Merge branch 'master' into firebase-sessions
2 parents 259fa0c + 4f4ede7 commit 51175b5

File tree

17 files changed

+315
-42
lines changed

17 files changed

+315
-42
lines changed

appcheck/firebase-appcheck-interop/src/main/java/com/google/firebase/appcheck/interop/InteropAppCheckTokenProvider.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ public interface InteropAppCheckTokenProvider {
2929
@NonNull
3030
Task<AppCheckTokenResult> getToken(boolean forceRefresh);
3131

32+
/**
33+
* Requests an {@link AppCheckTokenResult} from the installed {@code AppCheckFactory}. This will
34+
* always return a successful task, with an {@link AppCheckTokenResult} that contains either a
35+
* valid token, or a dummy token and an error string. The token returned from this method will be
36+
* a one-time use token.
37+
*/
38+
@NonNull
39+
Task<AppCheckTokenResult> getLimitedUseToken();
40+
3241
/**
3342
* Registers a listener to changes in the token state. There can be more than one listener
3443
* registered at the same time for one or more FirebaseAppAuth instances. The listeners call back

appcheck/firebase-appcheck/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Unreleased
2-
2+
* [changed] Internal updates to allow Firebase SDKs to obtain limited-use tokens.
33

44
# 17.0.0
55
* [feature] Added [`getLimitedUseAppCheckToken()`](/docs/reference/android/com/google/firebase/appcheck/FirebaseAppCheck#getLimitedUseAppCheckToken())

appcheck/firebase-appcheck/firebase-appcheck.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ dependencies {
4646
implementation 'com.google.firebase:firebase-annotations:16.2.0'
4747
implementation 'com.google.firebase:firebase-common:20.3.1'
4848
implementation 'com.google.firebase:firebase-components:17.1.0'
49-
implementation 'com.google.firebase:firebase-appcheck-interop:17.0.0'
49+
implementation project(':appcheck:firebase-appcheck-interop')
5050
implementation 'com.google.android.gms:play-services-base:18.0.1'
5151
implementation 'com.google.android.gms:play-services-tasks:18.0.1'
5252

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,28 @@ public Task<AppCheckTokenResult> getToken(boolean forceRefresh) {
213213
});
214214
}
215215

216+
@NonNull
217+
@Override
218+
public Task<AppCheckTokenResult> getLimitedUseToken() {
219+
return getLimitedUseAppCheckToken()
220+
.continueWithTask(
221+
liteExecutor,
222+
appCheckTokenTask -> {
223+
if (appCheckTokenTask.isSuccessful()) {
224+
return Tasks.forResult(
225+
DefaultAppCheckTokenResult.constructFromAppCheckToken(
226+
appCheckTokenTask.getResult()));
227+
}
228+
// If the token exchange failed, return a dummy token for integrators to attach
229+
// in their headers.
230+
return Tasks.forResult(
231+
DefaultAppCheckTokenResult.constructFromError(
232+
new FirebaseException(
233+
appCheckTokenTask.getException().getMessage(),
234+
appCheckTokenTask.getException())));
235+
});
236+
}
237+
216238
@NonNull
217239
@Override
218240
public Task<AppCheckToken> getAppCheckToken(boolean forceRefresh) {

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,15 @@ public void testGetLimitedUseAppCheckToken_noFactoryInstalled_taskFails() throws
235235
assertThat(tokenTask.isSuccessful()).isFalse();
236236
}
237237

238+
@Test
239+
public void testGetLimitedUseToken_noFactoryInstalled_returnResultWithError() throws Exception {
240+
Task<AppCheckTokenResult> tokenTask = defaultFirebaseAppCheck.getLimitedUseToken();
241+
assertThat(tokenTask.isComplete()).isTrue();
242+
assertThat(tokenTask.isSuccessful()).isTrue();
243+
assertThat(tokenTask.getResult().getToken()).isNotNull();
244+
assertThat(tokenTask.getResult().getError()).isNotNull();
245+
}
246+
238247
@Test
239248
public void testGetToken_factoryInstalled_proxiesToAppCheckFactory() {
240249
defaultFirebaseAppCheck.installAppCheckProviderFactory(mockAppCheckProviderFactory);
@@ -422,4 +431,23 @@ public void testGetLimitedUseAppCheckToken_existingToken_requestsNewToken() {
422431

423432
verify(mockAppCheckProvider).getToken();
424433
}
434+
435+
@Test
436+
public void testGetLimitedUseToken_noExistingToken_requestsNewToken() {
437+
defaultFirebaseAppCheck.installAppCheckProviderFactory(mockAppCheckProviderFactory);
438+
439+
defaultFirebaseAppCheck.getLimitedUseToken();
440+
441+
verify(mockAppCheckProvider).getToken();
442+
}
443+
444+
@Test
445+
public void testGetLimitedUseToken_existingToken_requestsNewToken() {
446+
defaultFirebaseAppCheck.setCachedToken(validDefaultAppCheckToken);
447+
defaultFirebaseAppCheck.installAppCheckProviderFactory(mockAppCheckProviderFactory);
448+
449+
defaultFirebaseAppCheck.getLimitedUseToken();
450+
451+
verify(mockAppCheckProvider).getToken();
452+
}
425453
}

firebase-functions/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Unreleased
2-
2+
* [changed] Added support for App Check limited-use tokens in HTTPS Callable Functions.
33

44
# 20.3.0
55
* [changed] Internal changes to ensure alignment with other SDK releases.

firebase-functions/api.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package com.google.firebase.functions {
33

44
public class FirebaseFunctions {
55
method @NonNull public com.google.firebase.functions.HttpsCallableReference getHttpsCallable(@NonNull String);
6+
method @NonNull public com.google.firebase.functions.HttpsCallableReference getHttpsCallable(@NonNull String, @NonNull com.google.firebase.functions.HttpsCallableOptions);
67
method @NonNull public com.google.firebase.functions.HttpsCallableReference getHttpsCallableFromUrl(@NonNull java.net.URL);
8+
method @NonNull public com.google.firebase.functions.HttpsCallableReference getHttpsCallableFromUrl(@NonNull java.net.URL, @NonNull com.google.firebase.functions.HttpsCallableOptions);
79
method @NonNull public static com.google.firebase.functions.FirebaseFunctions getInstance(@NonNull com.google.firebase.FirebaseApp, @NonNull String);
810
method @NonNull public static com.google.firebase.functions.FirebaseFunctions getInstance(@NonNull com.google.firebase.FirebaseApp);
911
method @NonNull public static com.google.firebase.functions.FirebaseFunctions getInstance(@NonNull String);
@@ -37,6 +39,17 @@ package com.google.firebase.functions {
3739
enum_constant public static final com.google.firebase.functions.FirebaseFunctionsException.Code UNKNOWN;
3840
}
3941

42+
public class HttpsCallableOptions {
43+
method public boolean getLimitedUseAppCheckTokens();
44+
}
45+
46+
public static class HttpsCallableOptions.Builder {
47+
ctor public HttpsCallableOptions.Builder();
48+
method @NonNull public com.google.firebase.functions.HttpsCallableOptions build();
49+
method public boolean getLimitedUseAppCheckTokens();
50+
method @NonNull public com.google.firebase.functions.HttpsCallableOptions.Builder setLimitedUseAppCheckTokens(boolean);
51+
}
52+
4053
public class HttpsCallableReference {
4154
method @NonNull public com.google.android.gms.tasks.Task<com.google.firebase.functions.HttpsCallableResult> call(@Nullable Object);
4255
method @NonNull public com.google.android.gms.tasks.Task<com.google.firebase.functions.HttpsCallableResult> call();

firebase-functions/src/androidTest/backend/functions/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ exports.appCheckTest = functions.https.onRequest((request, response) => {
6464
response.send({data: {}});
6565
});
6666

67+
exports.appCheckLimitedUseTest = functions.https.onRequest((request, response) => {
68+
assert.equal(request.get('X-Firebase-AppCheck'), 'appCheck-limited-use');
69+
assert.deepEqual(request.body, {data: {}});
70+
response.send({data: {}});
71+
});
72+
6773
exports.nullTest = functions.https.onRequest((request, response) => {
6874
assert.deepEqual(request.body, {data: null});
6975
response.send({data: null});

firebase-functions/src/androidTest/java/com/google/firebase/functions/CallTest.java

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public void testToken() throws InterruptedException, ExecutionException {
8888
app.getApplicationContext(),
8989
app.getOptions().getProjectId(),
9090
"us-central1",
91-
() -> {
91+
(unused) -> {
9292
HttpsCallableContext context = new HttpsCallableContext("token", null, null);
9393
return Tasks.forResult(context);
9494
},
@@ -110,7 +110,7 @@ public void testInstanceId() throws InterruptedException, ExecutionException {
110110
app.getApplicationContext(),
111111
app.getOptions().getProjectId(),
112112
"us-central1",
113-
() -> {
113+
(unused) -> {
114114
HttpsCallableContext context = new HttpsCallableContext(null, "iid", null);
115115
return Tasks.forResult(context);
116116
},
@@ -132,7 +132,7 @@ public void testAppCheck() throws InterruptedException, ExecutionException {
132132
app.getApplicationContext(),
133133
app.getOptions().getProjectId(),
134134
"us-central1",
135-
() -> {
135+
(unused) -> {
136136
HttpsCallableContext context = new HttpsCallableContext(null, null, "appCheck");
137137
return Tasks.forResult(context);
138138
},
@@ -146,6 +146,32 @@ public void testAppCheck() throws InterruptedException, ExecutionException {
146146
assertEquals(new HashMap<>(), actual);
147147
}
148148

149+
@Test
150+
public void testAppCheckLimitedUse() throws InterruptedException, ExecutionException {
151+
// Override the normal token provider to simulate FirebaseAuth being logged in.
152+
FirebaseFunctions functions =
153+
new FirebaseFunctions(
154+
app.getApplicationContext(),
155+
app.getOptions().getProjectId(),
156+
"us-central1",
157+
(unused) -> {
158+
HttpsCallableContext context =
159+
new HttpsCallableContext(null, null, "appCheck-limited-use");
160+
return Tasks.forResult(context);
161+
},
162+
TestOnlyExecutors.lite(),
163+
TestOnlyExecutors.ui());
164+
165+
HttpsCallableReference function =
166+
functions.getHttpsCallable(
167+
"appCheckLimitedUseTest",
168+
new HttpsCallableOptions.Builder().setLimitedUseAppCheckTokens(true).build());
169+
Task<HttpsCallableResult> result = function.call(new HashMap<>());
170+
Object actual = Tasks.await(result).getData();
171+
172+
assertEquals(new HashMap<>(), actual);
173+
}
174+
149175
@Test
150176
public void testNull() throws InterruptedException, ExecutionException {
151177
FirebaseFunctions functions = FirebaseFunctions.getInstance(app);

firebase-functions/src/androidTest/java/com/google/firebase/functions/FirebaseContextProviderTest.java

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ public class FirebaseContextProviderTest {
3434
private static final String AUTH_TOKEN = "authToken";
3535
private static final String IID_TOKEN = "iidToken";
3636
private static final String APP_CHECK_TOKEN = "appCheckToken";
37+
38+
private static final String APP_CHECK_LIMITED_USE_TOKEN = "appCheckLimitedUseToken";
3739
private static final String ERROR = "errorString";
3840

3941
private static final InternalAuthProvider fixedAuthProvider =
@@ -46,9 +48,9 @@ public class FirebaseContextProviderTest {
4648
private static final FirebaseInstanceIdInternal fixedIidProvider =
4749
new TestFirebaseInstanceIdInternal(IID_TOKEN);
4850
private static final InteropAppCheckTokenProvider fixedAppCheckProvider =
49-
new TestInteropAppCheckTokenProvider(APP_CHECK_TOKEN);
51+
new TestInteropAppCheckTokenProvider(APP_CHECK_TOKEN, APP_CHECK_LIMITED_USE_TOKEN);
5052
private static final InteropAppCheckTokenProvider errorAppCheckProvider =
51-
new TestInteropAppCheckTokenProvider(APP_CHECK_TOKEN, ERROR);
53+
new TestInteropAppCheckTokenProvider(APP_CHECK_TOKEN, APP_CHECK_LIMITED_USE_TOKEN, ERROR);
5254

5355
@Test
5456
public void getContext_whenAuthAndAppCheckAreNotAvailable_shouldContainOnlyIid()
@@ -60,7 +62,7 @@ public void getContext_whenAuthAndAppCheckAreNotAvailable_shouldContainOnlyIid()
6062
absentDeferred(),
6163
TestOnlyExecutors.lite());
6264

63-
HttpsCallableContext context = Tasks.await(contextProvider.getContext());
65+
HttpsCallableContext context = Tasks.await(contextProvider.getContext(false));
6466
assertThat(context.getAuthToken()).isNull();
6567
assertThat(context.getAppCheckToken()).isNull();
6668
assertThat(context.getInstanceIdToken()).isEqualTo(IID_TOKEN);
@@ -76,7 +78,7 @@ public void getContext_whenOnlyAuthIsAvailable_shouldContainOnlyAuthTokenAndIid(
7678
absentDeferred(),
7779
TestOnlyExecutors.lite());
7880

79-
HttpsCallableContext context = Tasks.await(contextProvider.getContext());
81+
HttpsCallableContext context = Tasks.await(contextProvider.getContext(false));
8082
assertThat(context.getAuthToken()).isEqualTo(AUTH_TOKEN);
8183
assertThat(context.getAppCheckToken()).isNull();
8284
assertThat(context.getInstanceIdToken()).isEqualTo(IID_TOKEN);
@@ -92,7 +94,7 @@ public void getContext_whenOnlyAppCheckIsAvailable_shouldContainOnlyAppCheckToke
9294
deferredOf(fixedAppCheckProvider),
9395
TestOnlyExecutors.lite());
9496

95-
HttpsCallableContext context = Tasks.await(contextProvider.getContext());
97+
HttpsCallableContext context = Tasks.await(contextProvider.getContext(false));
9698
assertThat(context.getAuthToken()).isNull();
9799
assertThat(context.getAppCheckToken()).isEqualTo(APP_CHECK_TOKEN);
98100
assertThat(context.getInstanceIdToken()).isEqualTo(IID_TOKEN);
@@ -108,7 +110,7 @@ public void getContext_whenOnlyAuthIsAvailableAndNotSignedIn_shouldContainOnlyIi
108110
absentDeferred(),
109111
TestOnlyExecutors.lite());
110112

111-
HttpsCallableContext context = Tasks.await(contextProvider.getContext());
113+
HttpsCallableContext context = Tasks.await(contextProvider.getContext(false));
112114
assertThat(context.getAuthToken()).isNull();
113115
assertThat(context.getAppCheckToken()).isNull();
114116
assertThat(context.getInstanceIdToken()).isEqualTo(IID_TOKEN);
@@ -124,12 +126,44 @@ public void getContext_whenOnlyAppCheckIsAvailableAndHasError_shouldContainOnlyI
124126
deferredOf(errorAppCheckProvider),
125127
TestOnlyExecutors.lite());
126128

127-
HttpsCallableContext context = Tasks.await(contextProvider.getContext());
129+
HttpsCallableContext context = Tasks.await(contextProvider.getContext(false));
130+
assertThat(context.getAuthToken()).isNull();
131+
assertThat(context.getInstanceIdToken()).isEqualTo(IID_TOKEN);
132+
assertThat(context.getAppCheckToken()).isNull();
133+
}
134+
135+
@Test
136+
public void getContext_facLimitedUse_whenOnlyAppCheckIsAvailableAndHasError_shouldContainOnlyIid()
137+
throws ExecutionException, InterruptedException {
138+
FirebaseContextProvider contextProvider =
139+
new FirebaseContextProvider(
140+
absentProvider(),
141+
providerOf(fixedIidProvider),
142+
deferredOf(errorAppCheckProvider),
143+
TestOnlyExecutors.lite());
144+
145+
HttpsCallableContext context = Tasks.await(contextProvider.getContext(true));
128146
assertThat(context.getAuthToken()).isNull();
129147
assertThat(context.getInstanceIdToken()).isEqualTo(IID_TOKEN);
130148
assertThat(context.getAppCheckToken()).isNull();
131149
}
132150

151+
@Test
152+
public void getContext_facLimitedUse_whenOnlyAppCheckIsAvailable_shouldContainToken()
153+
throws ExecutionException, InterruptedException {
154+
FirebaseContextProvider contextProvider =
155+
new FirebaseContextProvider(
156+
absentProvider(),
157+
providerOf(fixedIidProvider),
158+
deferredOf(fixedAppCheckProvider),
159+
TestOnlyExecutors.lite());
160+
161+
HttpsCallableContext context = Tasks.await(contextProvider.getContext(true));
162+
assertThat(context.getAuthToken()).isNull();
163+
assertThat(context.getInstanceIdToken()).isEqualTo(IID_TOKEN);
164+
assertThat(context.getAppCheckToken()).isEqualTo(APP_CHECK_LIMITED_USE_TOKEN);
165+
}
166+
133167
@Test
134168
public void getContext_whenAuthAndAppCheckAreAvailable_shouldContainAuthAppCheckTokensAndIid()
135169
throws ExecutionException, InterruptedException {
@@ -140,7 +174,7 @@ public void getContext_whenAuthAndAppCheckAreAvailable_shouldContainAuthAppCheck
140174
deferredOf(fixedAppCheckProvider),
141175
TestOnlyExecutors.lite());
142176

143-
HttpsCallableContext context = Tasks.await(contextProvider.getContext());
177+
HttpsCallableContext context = Tasks.await(contextProvider.getContext(false));
144178
assertThat(context.getAuthToken()).isEqualTo(AUTH_TOKEN);
145179
assertThat(context.getAppCheckToken()).isEqualTo(APP_CHECK_TOKEN);
146180
assertThat(context.getInstanceIdToken()).isEqualTo(IID_TOKEN);

firebase-functions/src/androidTest/java/com/google/firebase/functions/TestInteropAppCheckTokenProvider.java

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@
2525

2626
public class TestInteropAppCheckTokenProvider implements InteropAppCheckTokenProvider {
2727
private final AppCheckTokenResult testToken;
28+
private final AppCheckTokenResult testLimitedUseToken;
2829

29-
public TestInteropAppCheckTokenProvider(String testToken) {
30+
public TestInteropAppCheckTokenProvider(String testToken, String testLimitedUseToken) {
3031
this.testToken =
3132
new AppCheckTokenResult() {
3233
@NonNull
@@ -35,6 +36,21 @@ public String getToken() {
3536
return testToken;
3637
}
3738

39+
@Nullable
40+
@Override
41+
public Exception getError() {
42+
return null;
43+
}
44+
};
45+
46+
this.testLimitedUseToken =
47+
new AppCheckTokenResult() {
48+
@NonNull
49+
@Override
50+
public String getToken() {
51+
return testLimitedUseToken;
52+
}
53+
3854
@Nullable
3955
@Override
4056
public Exception getError() {
@@ -43,7 +59,8 @@ public Exception getError() {
4359
};
4460
}
4561

46-
public TestInteropAppCheckTokenProvider(String testToken, String error) {
62+
public TestInteropAppCheckTokenProvider(
63+
String testToken, String testLimitedUseToken, String error) {
4764
this.testToken =
4865
new AppCheckTokenResult() {
4966
@NonNull
@@ -52,6 +69,20 @@ public String getToken() {
5269
return testToken;
5370
}
5471

72+
@Nullable
73+
@Override
74+
public Exception getError() {
75+
return new FirebaseException(error);
76+
}
77+
};
78+
this.testLimitedUseToken =
79+
new AppCheckTokenResult() {
80+
@NonNull
81+
@Override
82+
public String getToken() {
83+
return testLimitedUseToken;
84+
}
85+
5586
@Nullable
5687
@Override
5788
public Exception getError() {
@@ -66,6 +97,12 @@ public Task<AppCheckTokenResult> getToken(boolean forceRefresh) {
6697
return Tasks.forResult(testToken);
6798
}
6899

100+
@NonNull
101+
@Override
102+
public Task<AppCheckTokenResult> getLimitedUseToken() {
103+
return Tasks.forResult(testLimitedUseToken);
104+
}
105+
69106
@Override
70107
public void addAppCheckTokenListener(@NonNull AppCheckTokenListener listener) {}
71108

0 commit comments

Comments
 (0)