Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions app_check/integration_test/src/integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,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
38 changes: 36 additions & 2 deletions app_check/src/android/app_check_android.cc
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,17 @@ JNIEXPORT void JNICALL JniAppCheckProvider_nativeGetToken(
JNIEnv* env, jobject j_provider, jlong c_provider,
jobject task_completion_source);

JNIEXPORT void JNICALL JniAppCheckProvider_nativeGetLimitedUseToken(
JNIEnv* env, jobject j_provider, jlong c_provider,
jobject task_completion_source);

static const JNINativeMethod kNativeJniAppCheckProviderMethods[] = {
{"nativeGetToken",
"(JLcom/google/android/gms/tasks/TaskCompletionSource;)V",
reinterpret_cast<void*>(JniAppCheckProvider_nativeGetToken)},
};
{"nativeGetLimitedUseToken",
"(JLcom/google/android/gms/tasks/TaskCompletionSource;)V",
reinterpret_cast<void*>(JniAppCheckProvider_nativeGetLimitedUseToken)}};

// clang-format off
#define JNI_APP_CHECK_LISTENER_METHODS(X) \
Expand Down Expand Up @@ -236,6 +242,34 @@ JNIEXPORT jlong JNICALL JniAppCheckProviderFactory_nativeCreateProvider(
JNIEXPORT void JNICALL JniAppCheckProvider_nativeGetToken(
JNIEnv* env, jobject j_provider, jlong c_provider,
jobject task_completion_source) {
jobject j_provider_global = env->NewGlobalRef(j_provider);
jobject task_completion_source_global =
env->NewGlobalRef(task_completion_source);

auto token_callback{[j_provider_global, task_completion_source_global](
firebase::app_check::AppCheckToken token,
int error_code, const std::string& error_message) {
JNIEnv* env = firebase::util::GetJNIEnvFromApp();
jstring error_string = env->NewStringUTF(error_message.c_str());
jstring token_string = env->NewStringUTF(token.token.c_str());
env->CallVoidMethod(
j_provider_global,
jni_provider::GetMethodId(jni_provider::kHandleGetTokenResult),
task_completion_source_global, token_string, token.expire_time_millis,
error_code, error_string);
FIREBASE_ASSERT(!util::CheckAndClearJniExceptions(env));
env->DeleteLocalRef(token_string);
env->DeleteLocalRef(error_string);
env->DeleteGlobalRef(j_provider_global);
env->DeleteGlobalRef(task_completion_source_global);
}};
AppCheckProvider* provider = reinterpret_cast<AppCheckProvider*>(c_provider);
provider->GetToken(token_callback);
}

JNIEXPORT void JNICALL JniAppCheckProvider_nativeGetLimitedUseToken(
JNIEnv* env, jobject j_provider, jlong c_provider,
jobject task_completion_source) {
// Create GlobalReferences to the provider and task. These references will be
// deleted in the completion callback.
jobject j_provider_global = env->NewGlobalRef(j_provider);
Expand Down Expand Up @@ -263,7 +297,7 @@ JNIEXPORT void JNICALL JniAppCheckProvider_nativeGetToken(
env->DeleteGlobalRef(task_completion_source_global);
}};
AppCheckProvider* provider = reinterpret_cast<AppCheckProvider*>(c_provider);
provider->GetToken(token_callback);
provider->GetLimitedUseToken(token_callback);
}

JNIEXPORT void JNICALL JniAppCheckListener_nativeOnAppCheckTokenChanged(
Expand Down
9 changes: 9 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,15 @@ 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() {}
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
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
10 changes: 10 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
13 changes: 13 additions & 0 deletions app_check/src/ios/app_check_ios.mm
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@ - (void)getTokenWithCompletion:(nonnull void (^)(FIRAppCheckToken* _Nullable,
_cppProvider->GetToken(token_callback);
}

- (void)getLimitedUseTokenWithCompletion:(nonnull void (^)(FIRAppCheckToken* _Nullable,
NSError* _Nullable))handler {
auto token_callback{[handler](firebase::app_check::AppCheckToken token, int error_code,
const std::string& error_message) {
NSError* ios_error = firebase::app_check::internal::AppCheckErrorToNSError(
static_cast<firebase::app_check::AppCheckError>(error_code), error_message);
FIRAppCheckToken* ios_token =
firebase::app_check::internal::AppCheckTokenToFIRAppCheckToken(token);
handler(ios_token, ios_error);
}};
_cppProvider->GetLimitedUseToken(token_callback);
}
Comment on lines +62 to +73
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This new method getLimitedUseTokenWithCompletion: is almost identical to the existing getTokenWithCompletion: method. To avoid code duplication, consider extracting the common logic into a private helper method that takes a boolean flag to decide whether to call GetToken or GetLimitedUseToken.

For example:

- (void)getTokenInternalWithLimitedUse:(BOOL)limitedUse
                            completion:(nonnull void (^)(FIRAppCheckToken* _Nullable,
                                                         NSError* _Nullable))handler {
  auto token_callback{[handler](firebase::app_check::AppCheckToken token, int error_code,
                                const std::string& error_message) {
    NSError* ios_error = firebase::app_check::internal::AppCheckErrorToNSError(
        static_cast<firebase::app_check::AppCheckError>(error_code), error_message);
    FIRAppCheckToken* ios_token =
        firebase::app_check::internal::AppCheckTokenToFIRAppCheckToken(token);
    handler(ios_token, ios_error);
  }};
  if (limitedUse) {
    _cppProvider->GetLimitedUseToken(token_callback);
  } else {
    _cppProvider->GetToken(token_callback);
  }
}

Then both getTokenWithCompletion: and getLimitedUseTokenWithCompletion: can call this helper.


@end

// Defines an iOS AppCheckProviderFactory that wraps a given C++ Factory.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ public Task<AppCheckToken> getToken() {
return taskCompletionSource.getTask();
}

public Task<AppCheckToken> getLimitedUseToken() {
TaskCompletionSource<AppCheckToken> taskCompletionSource =
new TaskCompletionSource<AppCheckToken>();
// Call the C++ provider to get an AppCheckToken and set the task result.
// The C++ code will call handleGetTokenResult with the resulting token.
nativeGetLimitedUseToken(cProvider, taskCompletionSource);
return taskCompletionSource.getTask();
}
Comment on lines +44 to +51
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The new getLimitedUseToken() method is very similar to the existing getToken() method. To reduce code duplication, you could extract the common logic into a private helper method. This helper could accept a functional interface that wraps the native method call.

For example (assuming Java 8+):

  private Task<AppCheckToken> getTokenInternal(
      java.util.function.BiConsumer<Long, TaskCompletionSource<AppCheckToken>> nativeMethod) {
    TaskCompletionSource<AppCheckToken> taskCompletionSource = new TaskCompletionSource<>();
    // Call the C++ provider to get an AppCheckToken and set the task result.
    // The C++ code will call handleGetTokenResult with the resulting token.
    nativeMethod.accept(cProvider, taskCompletionSource);
    return taskCompletionSource.getTask();
  }

  @Override
  public Task<AppCheckToken> getToken() {
    return getTokenInternal(this::nativeGetToken);
  }

  public Task<AppCheckToken> getLimitedUseToken() {
    return getTokenInternal(this::nativeGetLimitedUseToken);
  }


/**
* Called by C++ with a token in order to complete the java task.
*/
Expand All @@ -58,4 +67,10 @@ public void handleGetTokenResult(TaskCompletionSource<AppCheckToken> taskComplet
*/
private native void nativeGetToken(
long cProvider, TaskCompletionSource<AppCheckToken> task_completion_source);

/**
* This function is implemented in the AppCheck C++ library (app_check_android.cc).
*/
private native void nativeGetLimitedUseToken(
long cProvider, TaskCompletionSource<AppCheckToken> task_completion_source);
}
5 changes: 5 additions & 0 deletions release_build_files/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,11 @@ workflow use only during the development of your app, not for publicly shipping
code.

## Release Notes

### Upcoming
- Changes
- App Check: Add in support for Limited Use Tokens.

### 13.3.0
- Changes
- General (Android): Update to Firebase Android BoM version 34.6.0.
Expand Down
Loading