Skip to content
Open
48 changes: 48 additions & 0 deletions app_check/integration_test/src/integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,28 @@ TEST_F(FirebaseAppCheckTest, TestGetTokenLastResult) {
future2.result()->expire_time_millis);
}

TEST_F(FirebaseAppCheckTest, TestGetLimitedUseAppCheckToken) {
InitializeAppCheckWithDebug();
InitializeApp();
::firebase::app_check::AppCheck* app_check =
::firebase::app_check::AppCheck::GetInstance(app_);
ASSERT_NE(app_check, nullptr);

firebase::Future<::firebase::app_check::AppCheckToken> future =
app_check->GetLimitedUseAppCheckToken();
EXPECT_TRUE(WaitForCompletion(future, "GetLimitedUseAppCheckToken"));
::firebase::app_check::AppCheckToken token = *future.result();
EXPECT_NE(token.token, "");
EXPECT_NE(token.expire_time_millis, 0);

firebase::Future<::firebase::app_check::AppCheckToken> future2 =
app_check->GetLimitedUseAppCheckTokenLastResult();
EXPECT_TRUE(
WaitForCompletion(future2, "GetLimitedUseAppCheckTokenLastResult"));
EXPECT_EQ(future.result()->expire_time_millis,
future2.result()->expire_time_millis);
}

TEST_F(FirebaseAppCheckTest, TestAddTokenChangedListener) {
InitializeAppCheckWithDebug();
InitializeApp();
Expand Down Expand Up @@ -588,6 +610,32 @@ TEST_F(FirebaseAppCheckTest, TestDebugProviderValidToken) {
got_token_future.wait_for(kGetTokenTimeout));
}

TEST_F(FirebaseAppCheckTest, TestDebugProviderValidLimitedUseToken) {
firebase::app_check::DebugAppCheckProviderFactory* factory =
firebase::app_check::DebugAppCheckProviderFactory::GetInstance();
ASSERT_NE(factory, nullptr);
InitializeAppCheckWithDebug();
InitializeApp();

firebase::app_check::AppCheckProvider* provider =
factory->CreateProvider(app_);
ASSERT_NE(provider, nullptr);
auto got_token_promise = std::make_shared<std::promise<void>>();
auto token_callback{
[got_token_promise](firebase::app_check::AppCheckToken token,
int error_code, const std::string& error_message) {
EXPECT_EQ(firebase::app_check::kAppCheckErrorNone, error_code);
EXPECT_EQ("", error_message);
EXPECT_NE(0, token.expire_time_millis);
EXPECT_NE("", token.token);
got_token_promise->set_value();
}};
provider->GetLimitedUseToken(token_callback);
auto got_token_future = got_token_promise->get_future();
ASSERT_EQ(std::future_status::ready,
got_token_future.wait_for(kGetTokenTimeout));
}

TEST_F(FirebaseAppCheckTest, TestAppAttestProvider) {
firebase::app_check::AppAttestProviderFactory* factory =
firebase::app_check::AppAttestProviderFactory::GetInstance();
Expand Down
32 changes: 30 additions & 2 deletions app_check/src/android/app_check_android.cc
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ namespace internal {
"(Z)V"), \
X(GetToken, "getAppCheckToken", \
"(Z)Lcom/google/android/gms/tasks/Task;"), \
X(GetLimitedUseToken, "getLimitedUseAppCheckToken", \
"()Lcom/google/android/gms/tasks/Task;"), \
X(AddAppCheckListener, "addAppCheckListener", \
"(Lcom/google/firebase/appcheck/FirebaseAppCheck$AppCheckListener;)V"), \
X(RemoveAppCheckListener, "removeAppCheckListener", \
Expand Down Expand Up @@ -112,8 +114,7 @@ JNIEXPORT void JNICALL JniAppCheckProvider_nativeGetToken(
static const JNINativeMethod kNativeJniAppCheckProviderMethods[] = {
{"nativeGetToken",
"(JLcom/google/android/gms/tasks/TaskCompletionSource;)V",
reinterpret_cast<void*>(JniAppCheckProvider_nativeGetToken)},
};
reinterpret_cast<void*>(JniAppCheckProvider_nativeGetToken)}};

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

Future<AppCheckToken> AppCheckInternal::GetLimitedUseAppCheckToken() {
JNIEnv* env = app_->GetJNIEnv();
auto handle =
future()->SafeAlloc<AppCheckToken>(kAppCheckFnGetLimitedUseAppCheckToken);
jobject j_task = env->CallObjectMethod(
app_check_impl_, app_check::GetMethodId(app_check::kGetLimitedUseToken));

std::string error = util::GetAndClearExceptionMessage(env);
if (error.empty()) {
auto data_handle = new FutureDataHandle(future(), handle);
util::RegisterCallbackOnTask(env, j_task, TokenResultCallback,
reinterpret_cast<void*>(data_handle),
jni_task_id_.c_str());
} else {
AppCheckToken empty_token;
future()->CompleteWithResult(handle, kAppCheckErrorUnknown, error.c_str(),
empty_token);
}
env->DeleteLocalRef(j_task);
return MakeFuture(future(), handle);
}

Future<AppCheckToken> AppCheckInternal::GetLimitedUseAppCheckTokenLastResult() {
return static_cast<const Future<AppCheckToken>&>(
future()->LastResult(kAppCheckFnGetLimitedUseAppCheckToken));
}

void AppCheckInternal::AddAppCheckListener(AppCheckListener* listener) {
MutexLock lock(listeners_mutex_);
auto it = std::find(listeners_.begin(), listeners_.end(), listener);
Expand Down
4 changes: 4 additions & 0 deletions app_check/src/android/app_check_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ class AppCheckInternal {

Future<AppCheckToken> GetAppCheckTokenLastResult();

Future<AppCheckToken> GetLimitedUseAppCheckToken();

Future<AppCheckToken> GetLimitedUseAppCheckTokenLastResult();

void AddAppCheckListener(AppCheckListener* listener);

void RemoveAppCheckListener(AppCheckListener* listener);
Expand Down
10 changes: 10 additions & 0 deletions app_check/src/android/common_android.cc
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,16 @@ void AndroidAppCheckProvider::GetToken(
env->DeleteLocalRef(j_task);
}

void AndroidAppCheckProvider::GetLimitedUseToken(
std::function<void(AppCheckToken, int, const std::string&)>
completion_callback) {
LogWarning(
"GetLimitedUseToken() was called, but the AppCheckProvider interface on "
"Android does not yet support limited-use tokens. Falling back to "
"GetToken().");
GetToken(completion_callback);
}

} // namespace internal
} // namespace app_check
} // namespace firebase
7 changes: 7 additions & 0 deletions app_check/src/android/common_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ class AndroidAppCheckProvider : public AppCheckProvider {
void GetToken(std::function<void(AppCheckToken, int, const std::string&)>
completion_callback) override;

/// Fetches an AppCheckToken via a fallback method to the GetToken. The
/// current Android implementation does not support limited use tokens. For
/// custom App Check providers.
void GetLimitedUseToken(
std::function<void(AppCheckToken, int, const std::string&)>
completion_callback) override;

private:
jobject android_provider_;

Expand Down
22 changes: 22 additions & 0 deletions app_check/src/common/app_check.cc
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ static std::map<::firebase::App*, AppCheck*>* g_app_check_map = nullptr;
// Define the destructors for the virtual listener/provider/factory classes.
AppCheckListener::~AppCheckListener() {}
AppCheckProvider::~AppCheckProvider() {}

// Default implementation. Can be overridden by App check providers that support
// limited use tokens
void AppCheckProvider::GetLimitedUseToken(
std::function<void(AppCheckToken, int, const std::string&)>
completion_callback) {
LogWarning(
"A limited-use token was requested, but the custom provider did not "
"implement the GetLimitedUseToken method. The default implementation is "
"triggered as a result, and GetToken has been invoked instead.");
GetToken(completion_callback);
}
AppCheckProviderFactory::~AppCheckProviderFactory() {}

namespace internal {
Expand Down Expand Up @@ -149,6 +161,16 @@ Future<AppCheckToken> AppCheck::GetAppCheckTokenLastResult() {
: Future<AppCheckToken>();
}

Future<AppCheckToken> AppCheck::GetLimitedUseAppCheckToken() {
return internal_ ? internal_->GetLimitedUseAppCheckToken()
: Future<AppCheckToken>();
}

Future<AppCheckToken> AppCheck::GetLimitedUseAppCheckTokenLastResult() {
return internal_ ? internal_->GetLimitedUseAppCheckTokenLastResult()
: Future<AppCheckToken>();
}

void AppCheck::AddAppCheckListener(AppCheckListener* listener) {
if (!internal_) return;
internal_->AddAppCheckListener(listener);
Expand Down
1 change: 1 addition & 0 deletions app_check/src/common/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ namespace internal {
enum AppCheckFn {
kAppCheckFnGetAppCheckToken = 0,
kAppCheckFnGetAppCheckStringInternal,
kAppCheckFnGetLimitedUseAppCheckToken,
kAppCheckFnCount,
};

Expand Down
30 changes: 30 additions & 0 deletions app_check/src/desktop/app_check_desktop.cc
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,36 @@ Future<AppCheckToken> AppCheckInternal::GetAppCheckTokenLastResult() {
future()->LastResult(kAppCheckFnGetAppCheckToken));
}

Future<AppCheckToken> AppCheckInternal::GetLimitedUseAppCheckToken() {
auto handle =
future()->SafeAlloc<AppCheckToken>(kAppCheckFnGetLimitedUseAppCheckToken);
// Get a new token, and pass the result into the future.
AppCheckProvider* provider = GetProvider();
if (provider != nullptr) {
auto token_callback{[this, handle](firebase::app_check::AppCheckToken token,
int error_code,
const std::string& error_message) {
if (error_code == firebase::app_check::kAppCheckErrorNone) {
// Note that we do NOT update the cached token for limited-use tokens.
future()->CompleteWithResult(handle, 0, token);
} else {
future()->Complete(handle, error_code, error_message.c_str());
}
}};
provider->GetLimitedUseToken(token_callback);
} else {
future()->Complete(handle,
firebase::app_check::kAppCheckErrorInvalidConfiguration,
"No AppCheckProvider installed.");
}
return MakeFuture(future(), handle);
}

Future<AppCheckToken> AppCheckInternal::GetLimitedUseAppCheckTokenLastResult() {
return static_cast<const Future<AppCheckToken>&>(
future()->LastResult(kAppCheckFnGetLimitedUseAppCheckToken));
}

Future<std::string> AppCheckInternal::GetAppCheckTokenStringInternal() {
auto handle =
future()->SafeAlloc<std::string>(kAppCheckFnGetAppCheckStringInternal);
Expand Down
4 changes: 4 additions & 0 deletions app_check/src/desktop/app_check_desktop.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ class AppCheckInternal {

Future<AppCheckToken> GetAppCheckTokenLastResult();

Future<AppCheckToken> GetLimitedUseAppCheckToken();

Future<AppCheckToken> GetLimitedUseAppCheckTokenLastResult();

// Gets the App Check token as just the string, to be used by
// internal methods to not conflict with the publicly returned future.
Future<std::string> GetAppCheckTokenStringInternal();
Expand Down
23 changes: 23 additions & 0 deletions app_check/src/desktop/debug_provider_desktop.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,16 @@ class DebugAppCheckProvider : public AppCheckProvider {
void GetToken(std::function<void(AppCheckToken, int, const std::string&)>
completion_callback) override;

void GetLimitedUseToken(
std::function<void(AppCheckToken, int, const std::string&)>
completion_callback) override;

private:
void GetTokenInternal(
bool limited_use,
std::function<void(AppCheckToken, int, const std::string&)>
completion_callback);

App* app_;

scheduler::Scheduler scheduler_;
Expand Down Expand Up @@ -92,6 +101,19 @@ void GetTokenAsync(std::shared_ptr<DebugTokenRequest> request,
void DebugAppCheckProvider::GetToken(
std::function<void(AppCheckToken, int, const std::string&)>
completion_callback) {
GetTokenInternal(false, completion_callback);
}

void DebugAppCheckProvider::GetLimitedUseToken(
std::function<void(AppCheckToken, int, const std::string&)>
completion_callback) {
GetTokenInternal(true, completion_callback);
}

void DebugAppCheckProvider::GetTokenInternal(
bool limited_use,
std::function<void(AppCheckToken, int, const std::string&)>
completion_callback) {
// Identify the user's debug token
const char* debug_token_cstr;
if (!debug_token_.empty()) {
Expand All @@ -109,6 +131,7 @@ void DebugAppCheckProvider::GetToken(
// Exchange debug token with the backend to get a proper attestation token.
auto request = std::make_shared<DebugTokenRequest>(app_);
request->SetDebugToken(debug_token_cstr);
request->SetLimitedUse(limited_use);

// Use an async call, since we don't want to block on the server response.
auto async_call =
Expand Down
1 change: 1 addition & 0 deletions app_check/src/desktop/debug_token_request.fbs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace firebase.app_check.fbs;

table DebugTokenRequest {
debug_token:string;
limited_use:bool;
}

root_type DebugTokenRequest;
5 changes: 5 additions & 0 deletions app_check/src/desktop/debug_token_request.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ class DebugTokenRequest
application_data_->debug_token = std::move(debug_token);
UpdatePostFields();
}

void SetLimitedUse(bool limited_use) {
application_data_->limited_use = limited_use;
UpdatePostFields();
}
};

} // namespace internal
Expand Down
19 changes: 19 additions & 0 deletions app_check/src/include/firebase/app_check.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ class AppCheckProvider {
virtual void GetToken(
std::function<void(AppCheckToken, int, const std::string&)>
completion_callback) = 0;

/// Fetches an AppCheckToken suitable for consumption in limited-use scenarios
/// and then calls the provided callback function with the token or with an
/// error code and error message.
///
/// If you do not implement this method, the default implementation invokes
/// the GetToken method whenever a limited-use token is requested.
virtual void GetLimitedUseToken(
std::function<void(AppCheckToken, int, const std::string&)>
completion_callback);
};

/// Interface for a factory that generates {@link AppCheckProvider}s.
Expand Down Expand Up @@ -143,6 +153,15 @@ class AppCheck {
/// Returns the result of the most recent call to GetAppCheckToken();
Future<AppCheckToken> GetAppCheckTokenLastResult();

/// Requests a limited-use Firebase App Check token. This method should be
/// used ONLY if you need to authorize requests to a non-Firebase backend.
/// Requests to Firebase backends are authorized automatically if configured.
Future<AppCheckToken> GetLimitedUseAppCheckToken();

/// Returns the result of the most recent call to
/// GetLimitedUseAppCheckToken();
Future<AppCheckToken> GetLimitedUseAppCheckTokenLastResult();

/// Registers an {@link AppCheckListener} to changes in the token state. This
/// method should be used ONLY if you need to authorize requests to a
/// non-Firebase backend. Requests to Firebase backends are authorized
Expand Down
16 changes: 16 additions & 0 deletions app_check/src/ios/app_attest_provider_ios.mm
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@
virtual void GetToken(
std::function<void(AppCheckToken, int, const std::string&)> completion_callback) override;

/// Fetches an AppCheckToken suitable for consumption in limited-use scenarios
/// and then calls the provided callback function with the token or with an
/// error code and error message.
virtual void GetLimitedUseToken(
std::function<void(AppCheckToken, int, const std::string&)> completion_callback) override;

private:
FIRAppAttestProvider* ios_provider_;
};
Expand All @@ -55,6 +61,16 @@ virtual void GetToken(
}];
}

void AppAttestProvider::GetLimitedUseToken(
std::function<void(AppCheckToken, int, const std::string&)> completion_callback) {
[ios_provider_ getLimitedUseTokenWithCompletion:^(FIRAppCheckToken* _Nullable token,
NSError* _Nullable error) {
completion_callback(firebase::app_check::internal::AppCheckTokenFromFIRAppCheckToken(token),
firebase::app_check::internal::AppCheckErrorFromNSError(error),
util::NSStringToString(error.localizedDescription).c_str());
}];
}

AppAttestProviderFactoryInternal::AppAttestProviderFactoryInternal() : created_providers_() {}

AppAttestProviderFactoryInternal::~AppAttestProviderFactoryInternal() {
Expand Down
4 changes: 4 additions & 0 deletions app_check/src/ios/app_check_ios.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ class AppCheckInternal {

Future<AppCheckToken> GetAppCheckTokenLastResult();

Future<AppCheckToken> GetLimitedUseAppCheckToken();

Future<AppCheckToken> GetLimitedUseAppCheckTokenLastResult();

void AddAppCheckListener(AppCheckListener* listener);

void RemoveAppCheckListener(AppCheckListener* listener);
Expand Down
Loading
Loading