diff --git a/app/src/function_registry.h b/app/src/function_registry.h index 63d3e82a1a..2a1fafad2a 100644 --- a/app/src/function_registry.h +++ b/app/src/function_registry.h @@ -36,6 +36,8 @@ enum FunctionId { FnAuthAddAuthStateListener, FnAuthRemoveAuthStateListener, FnAppCheckGetTokenAsync, + FnAppCheckAddListener, + FnAppCheckRemoveListener, }; // Class for providing a generic way for firebase libraries to expose their diff --git a/app_check/integration_test/CMakeLists.txt b/app_check/integration_test/CMakeLists.txt index 135a987fe8..7eb6eb8f3d 100644 --- a/app_check/integration_test/CMakeLists.txt +++ b/app_check/integration_test/CMakeLists.txt @@ -234,7 +234,7 @@ endif() # Add the Firebase libraries to the target using the function from the SDK. add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL) # Note that firebase_app needs to be last in the list. -set(firebase_libs firebase_app_check firebase_database firebase_storage firebase_functions firebase_auth firebase_app) +set(firebase_libs firebase_app_check firebase_database firebase_storage firebase_functions firebase_firestore firebase_auth firebase_app) set(gtest_libs gtest gmock) target_link_libraries(${integration_test_target_name} ${firebase_libs} ${gtest_libs} ${ADDITIONAL_LIBS}) diff --git a/app_check/integration_test/Podfile b/app_check/integration_test/Podfile index 23b7ceac21..6430c41962 100644 --- a/app_check/integration_test/Podfile +++ b/app_check/integration_test/Podfile @@ -9,6 +9,7 @@ target 'integration_test' do pod 'Firebase/Auth', '10.6.0' pod 'Firebase/Storage', '10.6.0' pod 'Firebase/Functions', '10.6.0' + pod 'Firebase/Firestore', '10.6.0' end target 'integration_test_tvos' do @@ -18,6 +19,7 @@ target 'integration_test_tvos' do pod 'Firebase/Auth', '10.6.0' pod 'Firebase/Storage', '10.6.0' pod 'Firebase/Functions', '10.6.0' + pod 'Firebase/Firestore', '10.6.0' end post_install do |installer| diff --git a/app_check/integration_test/build.gradle b/app_check/integration_test/build.gradle index a2da5c9589..599400f4a2 100644 --- a/app_check/integration_test/build.gradle +++ b/app_check/integration_test/build.gradle @@ -65,7 +65,8 @@ android { "-DFIREBASE_INCLUDE_AUTH=ON", "-DFIREBASE_INCLUDE_DATABASE=ON", "-DFIREBASE_INCLUDE_STORAGE=ON", - "-DFIREBASE_INCLUDE_FUNCTIONS=ON" + "-DFIREBASE_INCLUDE_FUNCTIONS=ON", + "-DFIREBASE_INCLUDE_FIRESTORE=ON" } } externalNativeBuild.cmake { @@ -87,6 +88,7 @@ firebaseCpp.dependencies { database storage functions + firestore } apply plugin: 'com.google.gms.google-services' diff --git a/app_check/integration_test/src/integration_test.cc b/app_check/integration_test/src/integration_test.cc index 4220b79967..c98dc80ce7 100644 --- a/app_check/integration_test/src/integration_test.cc +++ b/app_check/integration_test/src/integration_test.cc @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -23,6 +24,7 @@ #include #include #include +#include #include #include @@ -35,6 +37,7 @@ #include "firebase/app_check/play_integrity_provider.h" #include "firebase/auth.h" #include "firebase/database.h" +#include "firebase/firestore.h" #include "firebase/functions.h" #include "firebase/internal/platform.h" #include "firebase/storage.h" @@ -121,19 +124,32 @@ class FirebaseAppCheckTest : public FirebaseTest { // Shut down Firebase Functions. void TerminateFunctions(); + // Initialize Firestore. + void InitializeFirestore(); + // Shut down Firestore. + void TerminateFirestore(); + firebase::database::DatabaseReference CreateWorkingPath( bool suppress_cleanup = false); + firebase::firestore::CollectionReference GetFirestoreCollection(); + firebase::firestore::DocumentReference CreateFirestoreDoc(); + void CleanupFirestore(int expected_error); + firebase::App* app_; firebase::auth::Auth* auth_; bool initialized_; firebase::database::Database* database_; - std::vector cleanup_paths_; + std::vector database_cleanup_; firebase::storage::Storage* storage_; firebase::functions::Functions* functions_; + + firebase::firestore::Firestore* firestore_; + std::string collection_name_; + std::vector firestore_cleanup_; }; // Listens for token changed notifications @@ -211,7 +227,9 @@ FirebaseAppCheckTest::FirebaseAppCheckTest() database_(nullptr), storage_(nullptr), functions_(nullptr), - cleanup_paths_() { + firestore_(nullptr), + database_cleanup_(), + firestore_cleanup_() { FindFirebaseConfig(FIREBASE_CONFIG_STRING); } @@ -225,6 +243,7 @@ void FirebaseAppCheckTest::TearDown() { TerminateDatabase(); TerminateStorage(); TerminateFunctions(); + TerminateFirestore(); TerminateAuth(); TerminateAppCheck(); TerminateApp(); @@ -293,18 +312,19 @@ void FirebaseAppCheckTest::TerminateDatabase() { if (!initialized_) return; if (database_) { - if (!cleanup_paths_.empty() && database_ && app_) { + if (!database_cleanup_.empty() && database_ && app_) { LogDebug("Cleaning up..."); std::vector> cleanups; - cleanups.reserve(cleanup_paths_.size()); - for (int i = 0; i < cleanup_paths_.size(); ++i) { - cleanups.push_back(cleanup_paths_[i].RemoveValue()); + cleanups.reserve(database_cleanup_.size()); + for (int i = 0; i < database_cleanup_.size(); ++i) { + cleanups.push_back(database_cleanup_[i].RemoveValue()); } for (int i = 0; i < cleanups.size(); ++i) { - std::string cleanup_name = "Cleanup (" + cleanup_paths_[i].url() + ")"; + std::string cleanup_name = + "Cleanup (" + database_cleanup_[i].url() + ")"; WaitForCompletion(cleanups[i], cleanup_name.c_str()); } - cleanup_paths_.clear(); + database_cleanup_.clear(); } LogDebug("Shutdown the Database library."); @@ -390,6 +410,86 @@ void FirebaseAppCheckTest::TerminateFunctions() { ProcessEvents(100); } +void FirebaseAppCheckTest::InitializeFirestore() { + LogDebug("Initializing Firebase Firestore."); + + ::firebase::ModuleInitializer initializer; + initializer.Initialize( + app_, &firestore_, [](::firebase::App* app, void* target) { + LogDebug("Attempting to initialize Firebase Firestore."); + ::firebase::InitResult result; + *reinterpret_cast(target) = + firebase::firestore::Firestore::GetInstance(app, &result); + return result; + }); + + WaitForCompletion(initializer.InitializeLastResult(), "InitializeFirestore"); + + ASSERT_EQ(initializer.InitializeLastResult().error(), 0) + << initializer.InitializeLastResult().error_message(); + + LogDebug("Successfully initialized Firebase Firestore."); +} + +void FirebaseAppCheckTest::TerminateFirestore() { + if (firestore_) { + LogDebug("Shutdown the Firestore library."); + + CleanupFirestore(firebase::firestore::kErrorNone); + + delete firestore_; + firestore_ = nullptr; + } + + ProcessEvents(100); +} + +firebase::firestore::CollectionReference +FirebaseAppCheckTest::GetFirestoreCollection() { + if (collection_name_.empty()) { + // Generate a collection for the test data based on the time in + // milliseconds. + int64_t time_in_microseconds = + app_framework::GetCurrentTimeInMicroseconds(); + + char buffer[21] = {0}; + snprintf(buffer, sizeof(buffer), "test%lld", + static_cast(time_in_microseconds)); // NOLINT + collection_name_ = buffer; + } + return firestore_->Collection(collection_name_.c_str()); +} + +firebase::firestore::DocumentReference +FirebaseAppCheckTest::CreateFirestoreDoc() { + std::string path = std::string( + ::testing::UnitTest::GetInstance()->current_test_info()->name()); + firebase::firestore::DocumentReference doc = + GetFirestoreCollection().Document(path); + // Only add to the cleanup set if it doesn't exist yet + if (find(firestore_cleanup_.begin(), firestore_cleanup_.end(), doc) == + firestore_cleanup_.end()) { + firestore_cleanup_.push_back(doc); + } + return doc; +} + +void FirebaseAppCheckTest::CleanupFirestore(int expected_error = 0) { + if (!firestore_cleanup_.empty()) { + LogDebug("Cleaning up documents."); + std::vector> cleanups; + cleanups.reserve(firestore_cleanup_.size()); + for (int i = 0; i < firestore_cleanup_.size(); ++i) { + cleanups.push_back(firestore_cleanup_[i].Delete()); + } + for (int i = 0; i < cleanups.size(); ++i) { + WaitForCompletion(cleanups[i], "Cleanup Firestore Document", + expected_error); + } + firestore_cleanup_.clear(); + } +} + void FirebaseAppCheckTest::SignIn() { if (auth_->current_user() != nullptr) { // Already signed in. @@ -439,7 +539,7 @@ firebase::database::DatabaseReference FirebaseAppCheckTest::CreateWorkingPath( bool suppress_cleanup) { auto ref = database_->GetReference(kIntegrationTestRootPath).PushChild(); if (!suppress_cleanup) { - cleanup_paths_.push_back(ref); + database_cleanup_.push_back(ref); } return ref; } @@ -815,4 +915,215 @@ TEST_F(FirebaseAppCheckTest, TestFunctionsFailure) { firebase::functions::kErrorUnauthenticated); } +TEST_F(FirebaseAppCheckTest, TestFirestoreSetGet) { + InitializeAppCheckWithDebug(); + InitializeApp(); + InitializeFirestore(); + + firebase::firestore::DocumentReference document = CreateFirestoreDoc(); + + WaitForCompletion( + document.Set(firebase::firestore::MapFieldValue{ + {"str", firebase::firestore::FieldValue::String("foo")}, + {"int", firebase::firestore::FieldValue::Integer(123)}}), + "document.Set"); + firebase::Future future = + document.Get(firebase::firestore::Source::kServer); + WaitForCompletion(future, "document.Get"); + ASSERT_NE(future.result(), nullptr); + EXPECT_THAT(future.result()->GetData(), + UnorderedElementsAre( + Pair("str", firebase::firestore::FieldValue::String("foo")), + Pair("int", firebase::firestore::FieldValue::Integer(123)))); +} + +TEST_F(FirebaseAppCheckTest, TestFirestoreSetGetFailure) { + // Don't set up AppCheck + InitializeApp(); + InitializeFirestore(); + + firebase::firestore::DocumentReference document = CreateFirestoreDoc(); + + // Both operations should fail because AppCheck isn't configured. + WaitForCompletion( + document.Set(firebase::firestore::MapFieldValue{ + {"str", firebase::firestore::FieldValue::String("badfoo")}, + {"int", firebase::firestore::FieldValue::Integer(456)}}), + "document.Set", firebase::firestore::kErrorPermissionDenied); + WaitForCompletion(document.Get(firebase::firestore::Source::kServer), + "document.Get", + firebase::firestore::kErrorPermissionDenied); + + CleanupFirestore(firebase::firestore::kErrorPermissionDenied); +} + +TEST_F(FirebaseAppCheckTest, TestFirestoreListener) { + // NOTE: This test assumes that the SnapshotListener will be called + // before the future returned by Set is completed. If this does + // start to fail because of changes to that logic, it will need to + // be rewritten to handle that. + InitializeAppCheckWithDebug(); + InitializeApp(); + InitializeFirestore(); + + firebase::firestore::DocumentReference document = CreateFirestoreDoc(); + + WaitForCompletion( + document.Set(firebase::firestore::MapFieldValue{ + {"val", firebase::firestore::FieldValue::String("start")}}), + "document.Set 0"); + + struct ListenerSnapshots { + std::mutex mutex; + std::vector snapshots; + }; + auto listener_snapshots = std::make_shared(); + firebase::firestore::ListenerRegistration registration = + document.AddSnapshotListener( + [listener_snapshots]( + const firebase::firestore::DocumentSnapshot& result, + firebase::firestore::Error error_code, + const std::string& error_message) { + std::lock_guard lock(listener_snapshots->mutex); + SCOPED_TRACE("Listener called, current size: " + + std::to_string(listener_snapshots->snapshots.size())); + EXPECT_EQ(error_code, firebase::firestore::kErrorOk); + EXPECT_EQ(error_message, ""); + listener_snapshots->snapshots.push_back(result.GetData()); + }); + + WaitForCompletion( + document.Set(firebase::firestore::MapFieldValue{ + {"val", firebase::firestore::FieldValue::String("update")}}), + "document.Set 1"); + + registration.Remove(); + WaitForCompletion( + document.Set(firebase::firestore::MapFieldValue{ + {"val", firebase::firestore::FieldValue::String("final")}}), + "document.Set 2"); + { + std::lock_guard lock(listener_snapshots->mutex); + EXPECT_THAT( + listener_snapshots->snapshots, + testing::ElementsAre( + firebase::firestore::MapFieldValue{ + {"val", firebase::firestore::FieldValue::String("start")}}, + firebase::firestore::MapFieldValue{ + {"val", firebase::firestore::FieldValue::String("update")}})); + } +} + +TEST_F(FirebaseAppCheckTest, TestFirestoreListenerFailure) { + // Don't set up AppCheck + InitializeApp(); + InitializeFirestore(); + + firebase::firestore::DocumentReference document = CreateFirestoreDoc(); + + std::mutex mutex; + std::condition_variable cond_var; + bool received_permission_denied = false; + firebase::firestore::ListenerRegistration registration = + document.AddSnapshotListener( + [&received_permission_denied, &mutex, &cond_var]( + const firebase::firestore::DocumentSnapshot& result, + firebase::firestore::Error error_code, + const std::string& error_message) { + if (error_code == firebase::firestore::kErrorNone) { + // If we receive a success, it should only be for the cache. + EXPECT_TRUE(result.metadata().has_pending_writes()); + EXPECT_TRUE(result.metadata().is_from_cache()); + } else { + // We expect one call with a Permission Denied error, from the + // server. + std::lock_guard lock(mutex); + EXPECT_FALSE(received_permission_denied); + EXPECT_EQ(error_code, + firebase::firestore::kErrorPermissionDenied); + received_permission_denied = true; + cond_var.notify_one(); + } + }); + + WaitForCompletion( + document.Set(firebase::firestore::MapFieldValue{ + {"val", firebase::firestore::FieldValue::String("transaction")}}), + "document.Set transaction", firebase::firestore::kErrorPermissionDenied); + + registration.Remove(); + + WaitForCompletion( + document.Set(firebase::firestore::MapFieldValue{ + {"val", firebase::firestore::FieldValue::String("final")}}), + "document.Set final", firebase::firestore::kErrorPermissionDenied); + + { + // Use a condition variable to guarantee that the listener has received + // the call. + std::unique_lock lock(mutex); + if (!received_permission_denied) { + cond_var.wait_for(lock, std::chrono::seconds(30)); + } + EXPECT_TRUE(received_permission_denied); + } + + CleanupFirestore(firebase::firestore::kErrorPermissionDenied); +} + +TEST_F(FirebaseAppCheckTest, TestRunTransaction) { + InitializeAppCheckWithDebug(); + InitializeApp(); + InitializeFirestore(); + + firebase::firestore::DocumentReference document = CreateFirestoreDoc(); + + WaitForCompletion( + document.Set(firebase::firestore::MapFieldValue{ + {"str", firebase::firestore::FieldValue::String("foo")}}), + "document.Set"); + + auto transaction_future = firestore_->RunTransaction( + [&](firebase::firestore::Transaction& transaction, + std::string&) -> firebase::firestore::Error { + transaction.Update( + document, + firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(123)}}); + return firebase::firestore::kErrorOk; + }); + + WaitForCompletion(transaction_future, "firestore.RunTransaction"); + + // Confirm the updated doc is correct. + auto future = document.Get(firebase::firestore::Source::kServer); + WaitForCompletion(future, "document.Get"); + ASSERT_NE(future.result(), nullptr); + EXPECT_THAT(future.result()->GetData(), + UnorderedElementsAre( + Pair("str", firebase::firestore::FieldValue::String("foo")), + Pair("int", firebase::firestore::FieldValue::Integer(123)))); +} + +TEST_F(FirebaseAppCheckTest, TestRunTransactionFailure) { + // Don't set up AppCheck + InitializeApp(); + InitializeFirestore(); + + firebase::firestore::DocumentReference document = CreateFirestoreDoc(); + + auto transaction_future = firestore_->RunTransaction( + [](firebase::firestore::Transaction& transaction, + std::string&) -> firebase::firestore::Error { + // This might be called due to updating the cache, but in the end we + // only care that the transaction future is rejected by the server. + return firebase::firestore::kErrorOk; + }); + + WaitForCompletion(transaction_future, "firestore.RunTransaction", + firebase::firestore::kErrorPermissionDenied); + + CleanupFirestore(firebase::firestore::kErrorPermissionDenied); +} + } // namespace firebase_testapp_automated diff --git a/app_check/src/desktop/app_check_desktop.cc b/app_check/src/desktop/app_check_desktop.cc index 05cc5eec38..f46f908177 100644 --- a/app_check/src/desktop/app_check_desktop.cc +++ b/app_check/src/desktop/app_check_desktop.cc @@ -14,6 +14,7 @@ #include "app_check/src/desktop/app_check_desktop.h" +#include #include #include @@ -33,6 +34,7 @@ AppCheckInternal::AppCheckInternal(App* app) cached_provider_(), is_token_auto_refresh_enabled_(true) { future_manager().AllocFutureApi(this, kAppCheckFnCount); + AddAppCheckListener(&internal_listener_); InitRegistryCalls(); } @@ -178,6 +180,12 @@ void AppCheckInternal::InitRegistryCalls() { app_->function_registry()->RegisterFunction( ::firebase::internal::FnAppCheckGetTokenAsync, AppCheckInternal::GetAppCheckTokenAsyncForRegistry); + app_->function_registry()->RegisterFunction( + ::firebase::internal::FnAppCheckAddListener, + AppCheckInternal::AddAppCheckListenerForRegistry); + app_->function_registry()->RegisterFunction( + ::firebase::internal::FnAppCheckRemoveListener, + AppCheckInternal::RemoveAppCheckListenerForRegistry); } g_app_check_registry_count++; } @@ -187,6 +195,10 @@ void AppCheckInternal::CleanupRegistryCalls() { if (g_app_check_registry_count == 0) { app_->function_registry()->UnregisterFunction( ::firebase::internal::FnAppCheckGetTokenAsync); + app_->function_registry()->UnregisterFunction( + ::firebase::internal::FnAppCheckAddListener); + app_->function_registry()->UnregisterFunction( + ::firebase::internal::FnAppCheckRemoveListener); } } @@ -207,6 +219,67 @@ bool AppCheckInternal::GetAppCheckTokenAsyncForRegistry(App* app, return false; } +void FunctionRegistryAppCheckListener::AddListener( + FunctionRegistryCallback callback, void* context) { + callbacks_.emplace_back(callback, context); +} + +void FunctionRegistryAppCheckListener::RemoveListener( + FunctionRegistryCallback callback, void* context) { + Entry entry = {callback, context}; + + auto iter = std::find(callbacks_.begin(), callbacks_.end(), entry); + if (iter != callbacks_.end()) { + callbacks_.erase(iter); + } +} + +void FunctionRegistryAppCheckListener::OnAppCheckTokenChanged( + const AppCheckToken& token) { + for (const Entry& entry : callbacks_) { + entry.first(token.token, entry.second); + } +} + +// static +bool AppCheckInternal::AddAppCheckListenerForRegistry(App* app, void* callback, + void* context) { + auto typed_callback = reinterpret_cast(callback); + if (!app || !typed_callback) { + return false; + } + + AppCheck* app_check = AppCheck::GetInstance(app); + if (app_check && app_check->internal_) { + app_check->internal_->internal_listener_.AddListener(typed_callback, + context); + // If there is a cached token, pass it along to the callback + if (app_check->internal_->HasValidCacheToken()) { + typed_callback(app_check->internal_->cached_token_.token, context); + } + return true; + } + return false; +} + +// static +bool AppCheckInternal::RemoveAppCheckListenerForRegistry(App* app, + void* callback, + void* context) { + auto typed_callback = reinterpret_cast(callback); + if (!app || !typed_callback) { + return false; + } + + AppCheck* app_check = AppCheck::GetInstance(app); + if (app_check && app_check->internal_) { + app_check->internal_->internal_listener_.RemoveListener(typed_callback, + context); + return true; + } + return false; +} + } // namespace internal } // namespace app_check } // namespace firebase diff --git a/app_check/src/desktop/app_check_desktop.h b/app_check/src/desktop/app_check_desktop.h index 6d3650f0a4..dc464e09e3 100644 --- a/app_check/src/desktop/app_check_desktop.h +++ b/app_check/src/desktop/app_check_desktop.h @@ -17,6 +17,8 @@ #include #include +#include +#include #include "app/src/future_manager.h" #include "app/src/include/firebase/app.h" @@ -27,6 +29,23 @@ namespace firebase { namespace app_check { namespace internal { +// The callback type for psuedo-AppCheckListeners added via the +// function registry. +using FunctionRegistryCallback = void (*)(std::string, void*); + +class FunctionRegistryAppCheckListener : public AppCheckListener { + public: + void AddListener(FunctionRegistryCallback callback, void* context); + + void RemoveListener(FunctionRegistryCallback callback, void* context); + + void OnAppCheckTokenChanged(const AppCheckToken& token) override; + + private: + using Entry = std::pair; + std::vector callbacks_; +}; + class AppCheckInternal { public: explicit AppCheckInternal(::firebase::App* app); @@ -77,6 +96,12 @@ class AppCheckInternal { static bool GetAppCheckTokenAsyncForRegistry(App* app, void* /*unused*/, void* out_future); + static bool AddAppCheckListenerForRegistry(App* app, void* callback, + void* context); + + static bool RemoveAppCheckListenerForRegistry(App* app, void* callback, + void* context); + ::firebase::App* app_; FutureManager future_manager_; @@ -87,6 +112,8 @@ class AppCheckInternal { AppCheckToken cached_token_; // List of registered listeners for Token changes. std::list token_listeners_; + // Internal listener used by the function registry to track Token changes. + FunctionRegistryAppCheckListener internal_listener_; // Should it automatically get an App Check token if there is not a valid // cached token. bool is_token_auto_refresh_enabled_; diff --git a/firestore/CMakeLists.txt b/firestore/CMakeLists.txt index 1d9c9c5f07..dc9b1219e9 100644 --- a/firestore/CMakeLists.txt +++ b/firestore/CMakeLists.txt @@ -222,12 +222,18 @@ set(main_SRCS # the interface actually comes from the FirebaseFirestore CocoaPod, so there's # no implementation listed here. set(credentials_provider_desktop_SRCS + src/main/app_check_credentials_provider_desktop.cc + src/main/app_check_credentials_provider_desktop.h + src/main/create_app_check_credentials_provider.h + src/main/create_app_check_credentials_provider_desktop.cc src/main/create_credentials_provider.h src/main/create_credentials_provider_desktop.cc src/main/credentials_provider_desktop.cc src/main/credentials_provider_desktop.h) set(credentials_provider_ios_SRCS + src/main/create_app_check_credentials_provider.h + src/main/create_app_check_credentials_provider_ios.mm src/main/create_credentials_provider.h src/main/create_credentials_provider_ios.mm) diff --git a/firestore/integration_test_internal/src/util/integration_test_util.cc b/firestore/integration_test_internal/src/util/integration_test_util.cc index 2846645166..d16dc418d3 100644 --- a/firestore/integration_test_internal/src/util/integration_test_util.cc +++ b/firestore/integration_test_internal/src/util/integration_test_util.cc @@ -37,7 +37,8 @@ struct TestFriend { static FirestoreInternal* CreateTestFirestoreInternal(App* app) { #if !defined(__ANDROID__) return new FirestoreInternal( - app, absl::make_unique()); + app, absl::make_unique(), + absl::make_unique()); #else return new FirestoreInternal(app); #endif // !defined(__ANDROID__) diff --git a/firestore/src/main/app_check_credentials_provider_desktop.cc b/firestore/src/main/app_check_credentials_provider_desktop.cc new file mode 100644 index 0000000000..3ccf4bb227 --- /dev/null +++ b/firestore/src/main/app_check_credentials_provider_desktop.cc @@ -0,0 +1,102 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "firestore/src/main/app_check_credentials_provider_desktop.h" + +#include +#include + +#include "Firestore/core/src/util/status.h" +#include "app/src/function_registry.h" +#include "app/src/reference_counted_future_impl.h" +#include "firebase/firestore/firestore_errors.h" +#include "firestore/src/common/futures.h" +#include "firestore/src/common/hard_assert_common.h" + +namespace firebase { +namespace firestore { + +using credentials::CredentialChangeListener; +using credentials::TokenListener; + +CppAppCheckCredentialsProvider::CppAppCheckCredentialsProvider(App& app) + : app_(app) {} + +CppAppCheckCredentialsProvider::~CppAppCheckCredentialsProvider() { + RemoveAppCheckStateListener(); +} + +void CppAppCheckCredentialsProvider::SetCredentialChangeListener( + CredentialChangeListener listener) { + if (!listener) { + SIMPLE_HARD_ASSERT(change_listener_, + "Change listener removed without being set!"); + change_listener_ = {}; + RemoveAppCheckStateListener(); + return; + } + + SIMPLE_HARD_ASSERT(!change_listener_, "Set change listener twice!"); + change_listener_ = std::move(listener); + + AddAppCheckStateListener(); +} + +void CppAppCheckCredentialsProvider::GetToken( + TokenListener listener) { + // Get token, tell the listener + Future app_check_future; + bool succeeded = app_.function_registry()->CallFunction( + ::firebase::internal::FnAppCheckGetTokenAsync, &app_, nullptr, + &app_check_future); + if (succeeded && app_check_future.status() != kFutureStatusInvalid) { + app_check_future.OnCompletion( + [listener](const Future& future_token) { + if (future_token.result()) { + listener(*future_token.result()); + } else { + listener(std::string()); + } + }); + } else { + // Getting the Future failed, so assume there is no App Check token to use. + listener(std::string()); + } +} + +void CppAppCheckCredentialsProvider::AddAppCheckStateListener() { + auto callback = reinterpret_cast(OnAppCheckStateChanged); + app_.function_registry()->CallFunction( + ::firebase::internal::FnAppCheckAddListener, &app_, callback, this); +} + +void CppAppCheckCredentialsProvider::RemoveAppCheckStateListener() { + auto callback = reinterpret_cast(OnAppCheckStateChanged); + app_.function_registry()->CallFunction( + ::firebase::internal::FnAppCheckRemoveListener, &app_, callback, this); +} + +void CppAppCheckCredentialsProvider::OnAppCheckStateChanged(std::string token, + void* context) { + auto provider = static_cast(context); + + if (provider->change_listener_) { + provider->change_listener_(token); + } +} + +} // namespace firestore +} // namespace firebase diff --git a/firestore/src/main/app_check_credentials_provider_desktop.h b/firestore/src/main/app_check_credentials_provider_desktop.h new file mode 100644 index 0000000000..bdd7c9ef4a --- /dev/null +++ b/firestore/src/main/app_check_credentials_provider_desktop.h @@ -0,0 +1,73 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIREBASE_FIRESTORE_SRC_MAIN_APP_CHECK_CREDENTIALS_PROVIDER_DESKTOP_H_ +#define FIREBASE_FIRESTORE_SRC_MAIN_APP_CHECK_CREDENTIALS_PROVIDER_DESKTOP_H_ + +#include + +#include "Firestore/core/src/credentials/credentials_fwd.h" +#include "Firestore/core/src/credentials/credentials_provider.h" +#include "Firestore/core/src/credentials/user.h" +#include "Firestore/core/src/util/statusor.h" +#include "app/src/include/firebase/app.h" +#include "app/src/include/firebase/future.h" + +#if defined(__ANDROID__) +#error "This header should not be used on Android." +#endif + +namespace firebase { +namespace firestore { + +// Glues together C++ Firebase App Check and Firestore: allows Firestore to +// listen to App Check events and to retrieve App Check tokens. Thread-safe. +// +// This is a language-specific implementation of `AppCheckCredentialsProvider` +// that works with the public C++ App Check. +class CppAppCheckCredentialsProvider + : public firestore::credentials::AppCheckCredentialsProvider { + public: + explicit CppAppCheckCredentialsProvider(App& app); + ~CppAppCheckCredentialsProvider() override; + + CppAppCheckCredentialsProvider(const CppAppCheckCredentialsProvider&) = + delete; + CppAppCheckCredentialsProvider& operator=( + const CppAppCheckCredentialsProvider&) = delete; + + // `firestore::credentials::AppCheckCredentialsProvider` interface. + void SetCredentialChangeListener( + firestore::credentials::CredentialChangeListener listener) + override; + void GetToken( + firestore::credentials::TokenListener listener) override; + + private: + void AddAppCheckStateListener(); + void RemoveAppCheckStateListener(); + + // Callback for the function registry-based pseudo-AuthStateListener + // interface. + static void OnAppCheckStateChanged(std::string, void* context); + + App& app_; +}; + +} // namespace firestore +} // namespace firebase + +#endif // FIREBASE_FIRESTORE_SRC_MAIN_APP_CHECK_CREDENTIALS_PROVIDER_DESKTOP_H_ diff --git a/firestore/src/main/create_app_check_credentials_provider.h b/firestore/src/main/create_app_check_credentials_provider.h new file mode 100644 index 0000000000..50e515dfe5 --- /dev/null +++ b/firestore/src/main/create_app_check_credentials_provider.h @@ -0,0 +1,40 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIREBASE_FIRESTORE_SRC_MAIN_CREATE_APP_CHECK_CREDENTIALS_PROVIDER_H_ +#define FIREBASE_FIRESTORE_SRC_MAIN_CREATE_APP_CHECK_CREDENTIALS_PROVIDER_H_ + +#include + +#include "Firestore/core/src/credentials/credentials_fwd.h" + +#if defined(__ANDROID__) +#error "This header should not be used on Android." +#endif + +namespace firebase { + +class App; + +namespace firestore { + +std::unique_ptr +CreateAppCheckCredentialsProvider(App& app); + +} // namespace firestore +} // namespace firebase + +#endif // FIREBASE_FIRESTORE_SRC_MAIN_CREATE_APP_CHECK_CREDENTIALS_PROVIDER_H_ diff --git a/firestore/src/main/create_app_check_credentials_provider_desktop.cc b/firestore/src/main/create_app_check_credentials_provider_desktop.cc new file mode 100644 index 0000000000..6225ffe142 --- /dev/null +++ b/firestore/src/main/create_app_check_credentials_provider_desktop.cc @@ -0,0 +1,32 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "absl/memory/memory.h" +#include "firestore/src/main/app_check_credentials_provider_desktop.h" +#include "firestore/src/main/create_credentials_provider.h" + +namespace firebase { +namespace firestore { + +using credentials::AppCheckCredentialsProvider; + +std::unique_ptr CreateAppCheckCredentialsProvider( + App& app) { + return absl::make_unique(app); +} + +} // namespace firestore +} // namespace firebase diff --git a/firestore/src/main/create_app_check_credentials_provider_ios.mm b/firestore/src/main/create_app_check_credentials_provider_ios.mm new file mode 100644 index 0000000000..13549a14ac --- /dev/null +++ b/firestore/src/main/create_app_check_credentials_provider_ios.mm @@ -0,0 +1,45 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "firestore/src/main/create_credentials_provider.h" + +#import "FIRAppInternal.h" +#import "FIRAuthInterop.h" +#import "FIRComponent.h" +#import "FIRComponentContainer.h" +#import "FIRComponentType.h" +#import "FirebaseCore.h" + +#include "Firestore/core/src/credentials/firebase_app_check_credentials_provider_apple.h" +#include "absl/memory/memory.h" +#include "app/src/include/firebase/app.h" + +namespace firebase { +namespace firestore { + +using credentials::AppCheckCredentialsProvider; +using credentials::FirebaseAppCheckCredentialsProvider; + +std::unique_ptr CreateAppCheckCredentialsProvider( + App& app) { + FIRApp* ios_app = app.GetPlatformApp(); + auto ios_app_check = FIR_COMPONENT(FIRAppCheckInterop, ios_app.container); + return absl::make_unique(ios_app, + ios_app_check); +} + +} // namespace firestore +} // namespace firebase diff --git a/firestore/src/main/firestore_main.cc b/firestore/src/main/firestore_main.cc index fc1d73806e..3834ec6a8d 100644 --- a/firestore/src/main/firestore_main.cc +++ b/firestore/src/main/firestore_main.cc @@ -42,6 +42,7 @@ #include "firestore/src/common/util.h" #include "firestore/src/include/firebase/firestore.h" #include "firestore/src/main/converter_main.h" +#include "firestore/src/main/create_app_check_credentials_provider.h" #include "firestore/src/main/create_credentials_provider.h" #include "firestore/src/main/create_firebase_metadata_provider.h" #include "firestore/src/main/document_reference_main.h" @@ -52,6 +53,7 @@ namespace firebase { namespace firestore { namespace { +using credentials::AppCheckCredentialsProvider; using credentials::AuthCredentialsProvider; using credentials::EmptyAppCheckCredentialsProvider; using model::DatabaseId; @@ -100,12 +102,16 @@ void ValidateDoubleSlash(const char* path) { } // namespace FirestoreInternal::FirestoreInternal(App* app) - : FirestoreInternal{app, CreateCredentialsProvider(*app)} {} + : FirestoreInternal{app, CreateCredentialsProvider(*app), + CreateAppCheckCredentialsProvider(*app)} {} FirestoreInternal::FirestoreInternal( - App* app, std::unique_ptr credentials) + App* app, + std::unique_ptr auth_credentials, + std::unique_ptr app_check_credentials) : app_(NOT_NULL(app)), - firestore_core_(CreateFirestore(app, std::move(credentials))), + firestore_core_(CreateFirestore( + app, std::move(auth_credentials), std::move(app_check_credentials))), transaction_executor_(absl::ShareUniquePtr(Executor::CreateConcurrent( "com.google.firebase.firestore.transaction", /*threads=*/5))) { ApplyDefaultSettings(); @@ -124,11 +130,13 @@ FirestoreInternal::~FirestoreInternal() { } std::shared_ptr FirestoreInternal::CreateFirestore( - App* app, std::unique_ptr credentials) { + App* app, + std::unique_ptr auth_credentials, + std::unique_ptr app_check_credentials) { const AppOptions& opt = app->options(); return std::make_shared( - DatabaseId{opt.project_id()}, app->name(), std::move(credentials), - std::make_shared(), CreateWorkerQueue(), + DatabaseId{opt.project_id()}, app->name(), std::move(auth_credentials), + std::move(app_check_credentials), CreateWorkerQueue(), CreateFirebaseMetadataProvider(*app), this); } diff --git a/firestore/src/main/firestore_main.h b/firestore/src/main/firestore_main.h index 68c02e2e49..49b3779547 100644 --- a/firestore/src/main/firestore_main.h +++ b/firestore/src/main/firestore_main.h @@ -150,11 +150,15 @@ class FirestoreInternal { FirestoreInternal( App* app, - std::unique_ptr credentials); + std::unique_ptr auth_credentials, + std::unique_ptr + app_check_credentials); std::shared_ptr CreateFirestore( App* app, - std::unique_ptr credentials); + std::unique_ptr auth_credentials, + std::unique_ptr + app_check_credentials); // Gets the reference-counted Future implementation of this instance, which // can be used to create a Future.