diff --git a/app_check/integration_test/src/integration_test.cc b/app_check/integration_test/src/integration_test.cc index d0cc4ee8cd..321e71ff69 100644 --- a/app_check/integration_test/src/integration_test.cc +++ b/app_check/integration_test/src/integration_test.cc @@ -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>(); + 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(); diff --git a/app_check/src/android/app_check_android.cc b/app_check/src/android/app_check_android.cc index f7295d525a..e76455db57 100644 --- a/app_check/src/android/app_check_android.cc +++ b/app_check/src/android/app_check_android.cc @@ -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(JniAppCheckProvider_nativeGetToken)}, -}; + {"nativeGetLimitedUseToken", + "(JLcom/google/android/gms/tasks/TaskCompletionSource;)V", + reinterpret_cast(JniAppCheckProvider_nativeGetLimitedUseToken)}}; // clang-format off #define JNI_APP_CHECK_LISTENER_METHODS(X) \ @@ -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(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); @@ -263,7 +297,7 @@ JNIEXPORT void JNICALL JniAppCheckProvider_nativeGetToken( env->DeleteGlobalRef(task_completion_source_global); }}; AppCheckProvider* provider = reinterpret_cast(c_provider); - provider->GetToken(token_callback); + provider->GetLimitedUseToken(token_callback); } JNIEXPORT void JNICALL JniAppCheckListener_nativeOnAppCheckTokenChanged( diff --git a/app_check/src/common/app_check.cc b/app_check/src/common/app_check.cc index 057b8985b4..7c4a25d360 100644 --- a/app_check/src/common/app_check.cc +++ b/app_check/src/common/app_check.cc @@ -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 + 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 { diff --git a/app_check/src/desktop/debug_provider_desktop.cc b/app_check/src/desktop/debug_provider_desktop.cc index 26754ede26..e0e558b47a 100644 --- a/app_check/src/desktop/debug_provider_desktop.cc +++ b/app_check/src/desktop/debug_provider_desktop.cc @@ -41,7 +41,16 @@ class DebugAppCheckProvider : public AppCheckProvider { void GetToken(std::function completion_callback) override; + void GetLimitedUseToken( + std::function + completion_callback) override; + private: + void GetTokenInternal( + bool limited_use, + std::function + completion_callback); + App* app_; scheduler::Scheduler scheduler_; @@ -92,6 +101,19 @@ void GetTokenAsync(std::shared_ptr request, void DebugAppCheckProvider::GetToken( std::function completion_callback) { + GetTokenInternal(false, completion_callback); +} + +void DebugAppCheckProvider::GetLimitedUseToken( + std::function + completion_callback) { + GetTokenInternal(true, completion_callback); +} + +void DebugAppCheckProvider::GetTokenInternal( + bool limited_use, + std::function + completion_callback) { // Identify the user's debug token const char* debug_token_cstr; if (!debug_token_.empty()) { @@ -109,6 +131,7 @@ void DebugAppCheckProvider::GetToken( // Exchange debug token with the backend to get a proper attestation token. auto request = std::make_shared(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 = diff --git a/app_check/src/desktop/debug_token_request.fbs b/app_check/src/desktop/debug_token_request.fbs index 65b2413757..8bcaca2dba 100644 --- a/app_check/src/desktop/debug_token_request.fbs +++ b/app_check/src/desktop/debug_token_request.fbs @@ -16,6 +16,7 @@ namespace firebase.app_check.fbs; table DebugTokenRequest { debug_token:string; + limited_use:bool; } root_type DebugTokenRequest; diff --git a/app_check/src/desktop/debug_token_request.h b/app_check/src/desktop/debug_token_request.h index b2ea2b5cf0..6688e90761 100644 --- a/app_check/src/desktop/debug_token_request.h +++ b/app_check/src/desktop/debug_token_request.h @@ -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 diff --git a/app_check/src/include/firebase/app_check.h b/app_check/src/include/firebase/app_check.h index 19167bcf3b..7073e4861b 100644 --- a/app_check/src/include/firebase/app_check.h +++ b/app_check/src/include/firebase/app_check.h @@ -78,6 +78,16 @@ class AppCheckProvider { virtual void GetToken( std::function 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 + completion_callback); }; /// Interface for a factory that generates {@link AppCheckProvider}s. diff --git a/app_check/src/ios/app_check_ios.mm b/app_check/src/ios/app_check_ios.mm index 1b6d37fd11..307c6b33d5 100644 --- a/app_check/src/ios/app_check_ios.mm +++ b/app_check/src/ios/app_check_ios.mm @@ -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(error_code), error_message); + FIRAppCheckToken* ios_token = + firebase::app_check::internal::AppCheckTokenToFIRAppCheckToken(token); + handler(ios_token, ios_error); + }}; + _cppProvider->GetLimitedUseToken(token_callback); +} + @end // Defines an iOS AppCheckProviderFactory that wraps a given C++ Factory. diff --git a/app_check/src_java/com/google/firebase/appcheck/internal/cpp/JniAppCheckProvider.java b/app_check/src_java/com/google/firebase/appcheck/internal/cpp/JniAppCheckProvider.java index 365c61c823..43acd2e08a 100644 --- a/app_check/src_java/com/google/firebase/appcheck/internal/cpp/JniAppCheckProvider.java +++ b/app_check/src_java/com/google/firebase/appcheck/internal/cpp/JniAppCheckProvider.java @@ -41,6 +41,15 @@ public Task getToken() { return taskCompletionSource.getTask(); } + public Task getLimitedUseToken() { + TaskCompletionSource 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. + nativeGetLimitedUseToken(cProvider, taskCompletionSource); + return taskCompletionSource.getTask(); + } + /** * Called by C++ with a token in order to complete the java task. */ @@ -58,4 +67,10 @@ public void handleGetTokenResult(TaskCompletionSource taskComplet */ private native void nativeGetToken( long cProvider, TaskCompletionSource task_completion_source); + + /** + * This function is implemented in the AppCheck C++ library (app_check_android.cc). + */ + private native void nativeGetLimitedUseToken( + long cProvider, TaskCompletionSource task_completion_source); } diff --git a/release_build_files/readme.md b/release_build_files/readme.md index 416e2d7e6b..f7923bbc89 100644 --- a/release_build_files/readme.md +++ b/release_build_files/readme.md @@ -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.