Skip to content

Commit 6bf0cf6

Browse files
Add support for limited use tokens (#1811)
* Feat: Add limited use token suport * Create an intergration test for the limited use tokens * Add in release notes * Fix comments on get limted use tokens * Flush out the implementation for limited use tokens * remove the appcheckprovider limited from android The limited use token is not provided in the appcheck provider class * format fix * Clean up for review and simplify app check loader * Fix comment to reflect what code is doing. * Update app_check/src/common/app_check.cc Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 128896e commit 6bf0cf6

19 files changed

+306
-2
lines changed

app_check/integration_test/src/integration_test.cc

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,28 @@ TEST_F(FirebaseAppCheckTest, TestGetTokenLastResult) {
516516
future2.result()->expire_time_millis);
517517
}
518518

519+
TEST_F(FirebaseAppCheckTest, TestGetLimitedUseAppCheckToken) {
520+
InitializeAppCheckWithDebug();
521+
InitializeApp();
522+
::firebase::app_check::AppCheck* app_check =
523+
::firebase::app_check::AppCheck::GetInstance(app_);
524+
ASSERT_NE(app_check, nullptr);
525+
526+
firebase::Future<::firebase::app_check::AppCheckToken> future =
527+
app_check->GetLimitedUseAppCheckToken();
528+
EXPECT_TRUE(WaitForCompletion(future, "GetLimitedUseAppCheckToken"));
529+
::firebase::app_check::AppCheckToken token = *future.result();
530+
EXPECT_NE(token.token, "");
531+
EXPECT_NE(token.expire_time_millis, 0);
532+
533+
firebase::Future<::firebase::app_check::AppCheckToken> future2 =
534+
app_check->GetLimitedUseAppCheckTokenLastResult();
535+
EXPECT_TRUE(
536+
WaitForCompletion(future2, "GetLimitedUseAppCheckTokenLastResult"));
537+
EXPECT_EQ(future.result()->expire_time_millis,
538+
future2.result()->expire_time_millis);
539+
}
540+
519541
TEST_F(FirebaseAppCheckTest, TestAddTokenChangedListener) {
520542
InitializeAppCheckWithDebug();
521543
InitializeApp();
@@ -588,6 +610,32 @@ TEST_F(FirebaseAppCheckTest, TestDebugProviderValidToken) {
588610
got_token_future.wait_for(kGetTokenTimeout));
589611
}
590612

613+
TEST_F(FirebaseAppCheckTest, TestDebugProviderValidLimitedUseToken) {
614+
firebase::app_check::DebugAppCheckProviderFactory* factory =
615+
firebase::app_check::DebugAppCheckProviderFactory::GetInstance();
616+
ASSERT_NE(factory, nullptr);
617+
InitializeAppCheckWithDebug();
618+
InitializeApp();
619+
620+
firebase::app_check::AppCheckProvider* provider =
621+
factory->CreateProvider(app_);
622+
ASSERT_NE(provider, nullptr);
623+
auto got_token_promise = std::make_shared<std::promise<void>>();
624+
auto token_callback{
625+
[got_token_promise](firebase::app_check::AppCheckToken token,
626+
int error_code, const std::string& error_message) {
627+
EXPECT_EQ(firebase::app_check::kAppCheckErrorNone, error_code);
628+
EXPECT_EQ("", error_message);
629+
EXPECT_NE(0, token.expire_time_millis);
630+
EXPECT_NE("", token.token);
631+
got_token_promise->set_value();
632+
}};
633+
provider->GetLimitedUseToken(token_callback);
634+
auto got_token_future = got_token_promise->get_future();
635+
ASSERT_EQ(std::future_status::ready,
636+
got_token_future.wait_for(kGetTokenTimeout));
637+
}
638+
591639
TEST_F(FirebaseAppCheckTest, TestAppAttestProvider) {
592640
firebase::app_check::AppAttestProviderFactory* factory =
593641
firebase::app_check::AppAttestProviderFactory::GetInstance();

app_check/src/android/app_check_android.cc

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ namespace internal {
4747
"(Z)V"), \
4848
X(GetToken, "getAppCheckToken", \
4949
"(Z)Lcom/google/android/gms/tasks/Task;"), \
50+
X(GetLimitedUseToken, "getLimitedUseAppCheckToken", \
51+
"()Lcom/google/android/gms/tasks/Task;"), \
5052
X(AddAppCheckListener, "addAppCheckListener", \
5153
"(Lcom/google/firebase/appcheck/FirebaseAppCheck$AppCheckListener;)V"), \
5254
X(RemoveAppCheckListener, "removeAppCheckListener", \
@@ -112,8 +114,7 @@ JNIEXPORT void JNICALL JniAppCheckProvider_nativeGetToken(
112114
static const JNINativeMethod kNativeJniAppCheckProviderMethods[] = {
113115
{"nativeGetToken",
114116
"(JLcom/google/android/gms/tasks/TaskCompletionSource;)V",
115-
reinterpret_cast<void*>(JniAppCheckProvider_nativeGetToken)},
116-
};
117+
reinterpret_cast<void*>(JniAppCheckProvider_nativeGetToken)}};
117118

118119
// clang-format off
119120
#define JNI_APP_CHECK_LISTENER_METHODS(X) \
@@ -452,6 +453,33 @@ Future<AppCheckToken> AppCheckInternal::GetAppCheckTokenLastResult() {
452453
future()->LastResult(kAppCheckFnGetAppCheckToken));
453454
}
454455

456+
Future<AppCheckToken> AppCheckInternal::GetLimitedUseAppCheckToken() {
457+
JNIEnv* env = app_->GetJNIEnv();
458+
auto handle =
459+
future()->SafeAlloc<AppCheckToken>(kAppCheckFnGetLimitedUseAppCheckToken);
460+
jobject j_task = env->CallObjectMethod(
461+
app_check_impl_, app_check::GetMethodId(app_check::kGetLimitedUseToken));
462+
463+
std::string error = util::GetAndClearExceptionMessage(env);
464+
if (error.empty()) {
465+
auto data_handle = new FutureDataHandle(future(), handle);
466+
util::RegisterCallbackOnTask(env, j_task, TokenResultCallback,
467+
reinterpret_cast<void*>(data_handle),
468+
jni_task_id_.c_str());
469+
} else {
470+
AppCheckToken empty_token;
471+
future()->CompleteWithResult(handle, kAppCheckErrorUnknown, error.c_str(),
472+
empty_token);
473+
}
474+
env->DeleteLocalRef(j_task);
475+
return MakeFuture(future(), handle);
476+
}
477+
478+
Future<AppCheckToken> AppCheckInternal::GetLimitedUseAppCheckTokenLastResult() {
479+
return static_cast<const Future<AppCheckToken>&>(
480+
future()->LastResult(kAppCheckFnGetLimitedUseAppCheckToken));
481+
}
482+
455483
void AppCheckInternal::AddAppCheckListener(AppCheckListener* listener) {
456484
MutexLock lock(listeners_mutex_);
457485
auto it = std::find(listeners_.begin(), listeners_.end(), listener);

app_check/src/android/app_check_android.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ class AppCheckInternal {
4545

4646
Future<AppCheckToken> GetAppCheckTokenLastResult();
4747

48+
Future<AppCheckToken> GetLimitedUseAppCheckToken();
49+
50+
Future<AppCheckToken> GetLimitedUseAppCheckTokenLastResult();
51+
4852
void AddAppCheckListener(AppCheckListener* listener);
4953

5054
void RemoveAppCheckListener(AppCheckListener* listener);

app_check/src/android/common_android.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,16 @@ void AndroidAppCheckProvider::GetToken(
161161
env->DeleteLocalRef(j_task);
162162
}
163163

164+
void AndroidAppCheckProvider::GetLimitedUseToken(
165+
std::function<void(AppCheckToken, int, const std::string&)>
166+
completion_callback) {
167+
LogWarning(
168+
"GetLimitedUseToken() was called, but the AppCheckProvider interface on "
169+
"Android does not yet support limited-use tokens. Falling back to "
170+
"GetToken().");
171+
GetToken(completion_callback);
172+
}
173+
164174
} // namespace internal
165175
} // namespace app_check
166176
} // namespace firebase

app_check/src/android/common_android.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ class AndroidAppCheckProvider : public AppCheckProvider {
5959
void GetToken(std::function<void(AppCheckToken, int, const std::string&)>
6060
completion_callback) override;
6161

62+
/// Fetches an AppCheckToken via a fallback method to the GetToken. The
63+
/// current Android implementation does not support limited use tokens. For
64+
/// custom App Check providers.
65+
void GetLimitedUseToken(
66+
std::function<void(AppCheckToken, int, const std::string&)>
67+
completion_callback) override;
68+
6269
private:
6370
jobject android_provider_;
6471

app_check/src/common/app_check.cc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,18 @@ static std::map<::firebase::App*, AppCheck*>* g_app_check_map = nullptr;
6767
// Define the destructors for the virtual listener/provider/factory classes.
6868
AppCheckListener::~AppCheckListener() {}
6969
AppCheckProvider::~AppCheckProvider() {}
70+
71+
// Default implementation. Can be overridden by App check providers that support
72+
// limited use tokens
73+
void AppCheckProvider::GetLimitedUseToken(
74+
std::function<void(AppCheckToken, int, const std::string&)>
75+
completion_callback) {
76+
LogWarning(
77+
"A limited-use token was requested, but the custom provider did not "
78+
"implement the GetLimitedUseToken method. The default implementation is "
79+
"triggered as a result, and GetToken has been invoked instead.");
80+
GetToken(completion_callback);
81+
}
7082
AppCheckProviderFactory::~AppCheckProviderFactory() {}
7183

7284
namespace internal {
@@ -149,6 +161,16 @@ Future<AppCheckToken> AppCheck::GetAppCheckTokenLastResult() {
149161
: Future<AppCheckToken>();
150162
}
151163

164+
Future<AppCheckToken> AppCheck::GetLimitedUseAppCheckToken() {
165+
return internal_ ? internal_->GetLimitedUseAppCheckToken()
166+
: Future<AppCheckToken>();
167+
}
168+
169+
Future<AppCheckToken> AppCheck::GetLimitedUseAppCheckTokenLastResult() {
170+
return internal_ ? internal_->GetLimitedUseAppCheckTokenLastResult()
171+
: Future<AppCheckToken>();
172+
}
173+
152174
void AppCheck::AddAppCheckListener(AppCheckListener* listener) {
153175
if (!internal_) return;
154176
internal_->AddAppCheckListener(listener);

app_check/src/common/common.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ namespace internal {
2323
enum AppCheckFn {
2424
kAppCheckFnGetAppCheckToken = 0,
2525
kAppCheckFnGetAppCheckStringInternal,
26+
kAppCheckFnGetLimitedUseAppCheckToken,
2627
kAppCheckFnCount,
2728
};
2829

app_check/src/desktop/app_check_desktop.cc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,36 @@ Future<AppCheckToken> AppCheckInternal::GetAppCheckTokenLastResult() {
120120
future()->LastResult(kAppCheckFnGetAppCheckToken));
121121
}
122122

123+
Future<AppCheckToken> AppCheckInternal::GetLimitedUseAppCheckToken() {
124+
auto handle =
125+
future()->SafeAlloc<AppCheckToken>(kAppCheckFnGetLimitedUseAppCheckToken);
126+
// Get a new token, and pass the result into the future.
127+
AppCheckProvider* provider = GetProvider();
128+
if (provider != nullptr) {
129+
auto token_callback{[this, handle](firebase::app_check::AppCheckToken token,
130+
int error_code,
131+
const std::string& error_message) {
132+
if (error_code == firebase::app_check::kAppCheckErrorNone) {
133+
// Note that we do NOT update the cached token for limited-use tokens.
134+
future()->CompleteWithResult(handle, 0, token);
135+
} else {
136+
future()->Complete(handle, error_code, error_message.c_str());
137+
}
138+
}};
139+
provider->GetLimitedUseToken(token_callback);
140+
} else {
141+
future()->Complete(handle,
142+
firebase::app_check::kAppCheckErrorInvalidConfiguration,
143+
"No AppCheckProvider installed.");
144+
}
145+
return MakeFuture(future(), handle);
146+
}
147+
148+
Future<AppCheckToken> AppCheckInternal::GetLimitedUseAppCheckTokenLastResult() {
149+
return static_cast<const Future<AppCheckToken>&>(
150+
future()->LastResult(kAppCheckFnGetLimitedUseAppCheckToken));
151+
}
152+
123153
Future<std::string> AppCheckInternal::GetAppCheckTokenStringInternal() {
124154
auto handle =
125155
future()->SafeAlloc<std::string>(kAppCheckFnGetAppCheckStringInternal);

app_check/src/desktop/app_check_desktop.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ class AppCheckInternal {
6262

6363
Future<AppCheckToken> GetAppCheckTokenLastResult();
6464

65+
Future<AppCheckToken> GetLimitedUseAppCheckToken();
66+
67+
Future<AppCheckToken> GetLimitedUseAppCheckTokenLastResult();
68+
6569
// Gets the App Check token as just the string, to be used by
6670
// internal methods to not conflict with the publicly returned future.
6771
Future<std::string> GetAppCheckTokenStringInternal();

app_check/src/desktop/debug_provider_desktop.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,16 @@ class DebugAppCheckProvider : public AppCheckProvider {
4141
void GetToken(std::function<void(AppCheckToken, int, const std::string&)>
4242
completion_callback) override;
4343

44+
void GetLimitedUseToken(
45+
std::function<void(AppCheckToken, int, const std::string&)>
46+
completion_callback) override;
47+
4448
private:
49+
void GetTokenInternal(
50+
bool limited_use,
51+
std::function<void(AppCheckToken, int, const std::string&)>
52+
completion_callback);
53+
4554
App* app_;
4655

4756
scheduler::Scheduler scheduler_;
@@ -92,6 +101,19 @@ void GetTokenAsync(std::shared_ptr<DebugTokenRequest> request,
92101
void DebugAppCheckProvider::GetToken(
93102
std::function<void(AppCheckToken, int, const std::string&)>
94103
completion_callback) {
104+
GetTokenInternal(false, completion_callback);
105+
}
106+
107+
void DebugAppCheckProvider::GetLimitedUseToken(
108+
std::function<void(AppCheckToken, int, const std::string&)>
109+
completion_callback) {
110+
GetTokenInternal(true, completion_callback);
111+
}
112+
113+
void DebugAppCheckProvider::GetTokenInternal(
114+
bool limited_use,
115+
std::function<void(AppCheckToken, int, const std::string&)>
116+
completion_callback) {
95117
// Identify the user's debug token
96118
const char* debug_token_cstr;
97119
if (!debug_token_.empty()) {
@@ -109,6 +131,7 @@ void DebugAppCheckProvider::GetToken(
109131
// Exchange debug token with the backend to get a proper attestation token.
110132
auto request = std::make_shared<DebugTokenRequest>(app_);
111133
request->SetDebugToken(debug_token_cstr);
134+
request->SetLimitedUse(limited_use);
112135

113136
// Use an async call, since we don't want to block on the server response.
114137
auto async_call =

0 commit comments

Comments
 (0)