Skip to content

Add App Check hooks for desktop rest calls #1198

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 14, 2023
Merged
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
3 changes: 2 additions & 1 deletion admob/src/common/admob_common.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ FIREBASE_APP_REGISTER_CALLBACKS(
if (app == ::firebase::App::GetInstance()) {
firebase::admob::Terminate();
}
});
},
false);

namespace firebase {
namespace admob {
Expand Down
3 changes: 2 additions & 1 deletion analytics/src/analytics_common.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ FIREBASE_APP_REGISTER_CALLBACKS(
if (app == ::firebase::App::GetInstance()) {
firebase::analytics::Terminate();
}
});
},
false);

namespace firebase {
namespace analytics {
Expand Down
1 change: 1 addition & 0 deletions app/src/function_registry.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ enum FunctionId {
FnAuthGetCurrentUserUid,
FnAuthAddAuthStateListener,
FnAuthRemoveAuthStateListener,
FnAppCheckGetTokenAsync,
};

// Class for providing a generic way for firebase libraries to expose their
Expand Down
16 changes: 10 additions & 6 deletions app/src/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,18 @@ class AppCallback {

// Initialize a module instance.
//
// Right now all module auto-initialization is disabled by default. Module
// initialization can be enabled on a case by case basis using
// Module auto-initialization is determined by the enabled flag.
// Most products should default to false, except for App Check which
// needs to be aware of App creation, and may not require user calls.
// Module initialization can be enabled on a case by case basis using
// SetEnabledByName() before creating an App object, for example:
// SetEnabledByName("analytics", true);
AppCallback(const char* module_name, Created created, Destroyed destroyed)
AppCallback(const char* module_name, Created created, Destroyed destroyed,
bool enabled_by_default)
: module_name_(module_name),
created_(created),
destroyed_(destroyed),
enabled_(false) {
enabled_(enabled_by_default) {
AddCallback(this);
}

Expand Down Expand Up @@ -186,14 +189,15 @@ class AppCallback {
// }
// });
#define FIREBASE_APP_REGISTER_CALLBACKS(module_name, created_code, \
destroyed_code) \
destroyed_code, enabled_by_default) \
namespace firebase { \
static InitResult module_name##Created(::firebase::App* app) { \
created_code; \
} \
static void module_name##Destroyed(::firebase::App* app) { destroyed_code; } \
static ::firebase::AppCallback module_name##_app_callback( \
#module_name, module_name##Created, module_name##Destroyed); \
#module_name, module_name##Created, module_name##Destroyed, \
enabled_by_default); \
/* This is a global symbol that is referenced from all compilation units */ \
/* that include this module. */ \
void* FIREBASE_APP_REGISTER_CALLBACKS_INITIALIZER_NAME(module_name) \
Expand Down
2 changes: 1 addition & 1 deletion app_check/integration_test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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_auth firebase_app)
set(firebase_libs firebase_app_check firebase_database firebase_storage firebase_auth firebase_app)
set(gtest_libs gtest gmock)
target_link_libraries(${integration_test_target_name} ${firebase_libs}
${gtest_libs} ${ADDITIONAL_LIBS})
80 changes: 79 additions & 1 deletion app_check/integration_test/src/integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "firebase/auth.h"
#include "firebase/database.h"
#include "firebase/internal/platform.h"
#include "firebase/storage.h"
#include "firebase/util.h"
#include "firebase_test_framework.h" // NOLINT

Expand Down Expand Up @@ -106,6 +107,14 @@ class FirebaseAppCheckTest : public FirebaseTest {
// Initialize everything needed for Database tests.
void InitializeAppAuthDatabase();

// Initialize Firebase Storage.
void InitializeStorage();
// Shut down Firebase Storage.
void TerminateStorage();

// Initialize everything needed for Storage tests.
void InitializeAppAuthStorage();

firebase::database::DatabaseReference CreateWorkingPath(
bool suppress_cleanup = false);

Expand All @@ -114,8 +123,9 @@ class FirebaseAppCheckTest : public FirebaseTest {

bool initialized_;
firebase::database::Database* database_;

std::vector<firebase::database::DatabaseReference> cleanup_paths_;

firebase::storage::Storage* storage_;
};

// Listens for token changed notifications
Expand Down Expand Up @@ -157,6 +167,8 @@ void FirebaseAppCheckTest::TerminateAppCheck() {
delete app_check;
}
}

firebase::app_check::AppCheck::SetAppCheckProviderFactory(nullptr);
}

void FirebaseAppCheckTest::InitializeApp() {
Expand Down Expand Up @@ -189,6 +201,7 @@ FirebaseAppCheckTest::FirebaseAppCheckTest()
app_(nullptr),
auth_(nullptr),
database_(nullptr),
storage_(nullptr),
cleanup_paths_() {
FindFirebaseConfig(FIREBASE_CONFIG_STRING);
}
Expand All @@ -201,6 +214,7 @@ FirebaseAppCheckTest::~FirebaseAppCheckTest() {
void FirebaseAppCheckTest::TearDown() {
// Teardown all the products
TerminateDatabase();
TerminateStorage();
TerminateAuth();
TerminateAppCheck();
TerminateApp();
Expand Down Expand Up @@ -298,6 +312,43 @@ void FirebaseAppCheckTest::InitializeAppAuthDatabase() {
InitializeDatabase();
}

void FirebaseAppCheckTest::InitializeStorage() {
LogDebug("Initializing Firebase Storage.");

::firebase::ModuleInitializer initializer;
initializer.Initialize(
app_, &storage_, [](::firebase::App* app, void* target) {
LogDebug("Attempting to initialize Firebase Storage.");
::firebase::InitResult result;
*reinterpret_cast<firebase::storage::Storage**>(target) =
firebase::storage::Storage::GetInstance(app, &result);
return result;
});

WaitForCompletion(initializer.InitializeLastResult(), "InitializeStorage");

ASSERT_EQ(initializer.InitializeLastResult().error(), 0)
<< initializer.InitializeLastResult().error_message();

LogDebug("Successfully initialized Firebase Storage.");
}

void FirebaseAppCheckTest::TerminateStorage() {
if (storage_) {
LogDebug("Shutdown the Storage library.");
delete storage_;
storage_ = nullptr;
}

ProcessEvents(100);
}

void FirebaseAppCheckTest::InitializeAppAuthStorage() {
InitializeApp();
InitializeAuth();
InitializeStorage();
}

void FirebaseAppCheckTest::SignIn() {
if (auth_->current_user() != nullptr) {
// Already signed in.
Expand Down Expand Up @@ -674,4 +725,31 @@ TEST_F(FirebaseAppCheckTest, DISABLED_TestRunTransaction) {
}
}

TEST_F(FirebaseAppCheckTest, TestStorageReadFile) {
InitializeAppCheckWithDebug();
InitializeAppAuthStorage();
firebase::storage::StorageReference ref = storage_->GetReference("test.txt");
EXPECT_TRUE(ref.is_valid());
const size_t kBufferSize = 128;
char buffer[kBufferSize];
memset(buffer, 0, sizeof(buffer));
firebase::Future<size_t> future = ref.GetBytes(buffer, kBufferSize);
WaitForCompletion(future, "GetBytes", firebase::storage::kErrorNone);
LogDebug(" buffer: %s", buffer);
}

TEST_F(FirebaseAppCheckTest, TestStorageReadFileUnauthenticated) {
// Don't set up AppCheck
InitializeAppAuthStorage();
firebase::storage::StorageReference ref = storage_->GetReference("test.txt");
EXPECT_TRUE(ref.is_valid());
const size_t kBufferSize = 128;
char buffer[kBufferSize];
memset(buffer, 0, sizeof(buffer));
firebase::Future<size_t> future = ref.GetBytes(buffer, kBufferSize);
WaitForCompletion(future, "GetBytes",
firebase::storage::kErrorUnauthenticated);
LogDebug(" buffer: %s", buffer);
}

} // namespace firebase_testapp_automated
41 changes: 36 additions & 5 deletions app_check/src/common/app_check.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "app/src/include/firebase/version.h"
#include "app/src/log.h"
#include "app/src/util.h"
#include "app_check/src/common/common.h"

// Include the header that matches the platform being used.
#if FIREBASE_PLATFORM_ANDROID
Expand All @@ -38,11 +39,22 @@
// FIREBASE_PLATFORM_TVOS

// Register the module initializer.
FIREBASE_APP_REGISTER_CALLBACKS(app_check,
{ return ::firebase::kInitResultSuccess; },
{
// Nothing to tear down.
});
FIREBASE_APP_REGISTER_CALLBACKS(
app_check,
{
// Create the AppCheck object for the given app.
::firebase::app_check::AppCheck::GetInstance(app);
return ::firebase::kInitResultSuccess;
},
{
::firebase::app_check::AppCheck* app_check =
::firebase::app_check::internal::GetExistingAppCheckInstance(app);
if (app_check) {
delete app_check;
}
},
// App Check wants to be turned on by default
true);

namespace firebase {
namespace app_check {
Expand All @@ -57,7 +69,26 @@ AppCheckListener::~AppCheckListener() {}
AppCheckProvider::~AppCheckProvider() {}
AppCheckProviderFactory::~AppCheckProviderFactory() {}

namespace internal {

AppCheck* GetExistingAppCheckInstance(::firebase::App* app) {
if (!app) return nullptr;

MutexLock lock(g_app_check_lock);
if (g_app_check_map) {
auto it = g_app_check_map->find(app);
if (it != g_app_check_map->end()) {
return it->second;
}
}
return nullptr;
}

} // namespace internal

AppCheck* AppCheck::GetInstance(::firebase::App* app) {
if (!app) return nullptr;

MutexLock lock(g_app_check_lock);
if (!g_app_check_map) {
g_app_check_map = new std::map<::firebase::App*, AppCheck*>();
Expand Down
6 changes: 6 additions & 0 deletions app_check/src/common/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,17 @@ namespace firebase {
namespace app_check {
namespace internal {

// Used by App Check functions that return a future
enum AppCheckFn {
kAppCheckFnGetAppCheckToken = 0,
kAppCheckFnCount,
};

// Helper function to get an existing AppCheck instance or nullptr,
// if an instance does not already exist.
::firebase::app_check::AppCheck* GetExistingAppCheckInstance(
::firebase::App* app);

} // namespace internal
} // namespace app_check
} // namespace firebase
Expand Down
43 changes: 43 additions & 0 deletions app_check/src/desktop/app_check_desktop.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
#include <ctime>
#include <string>

#include "app/src/function_registry.h"
#include "app/src/log.h"
#include "app_check/src/common/common.h"

namespace firebase {
Expand All @@ -28,10 +30,12 @@ static AppCheckProviderFactory* g_provider_factory = nullptr;
AppCheckInternal::AppCheckInternal(App* app)
: app_(app), cached_token_(), cached_provider_() {
future_manager().AllocFutureApi(this, kAppCheckFnCount);
InitRegistryCalls();
}

AppCheckInternal::~AppCheckInternal() {
future_manager().ReleaseFutureApi(this);
CleanupRegistryCalls();
app_ = nullptr;
// Clear the cached token by setting the expiration
cached_token_.expire_time_millis = 0;
Expand Down Expand Up @@ -126,6 +130,45 @@ void AppCheckInternal::RemoveAppCheckListener(AppCheckListener* listener) {
}
}

static int g_app_check_registry_count = 0;

void AppCheckInternal::InitRegistryCalls() {
if (g_app_check_registry_count == 0) {
app_->function_registry()->RegisterFunction(
::firebase::internal::FnAppCheckGetTokenAsync,
AppCheckInternal::GetAppCheckTokenAsyncForRegistry);
}
g_app_check_registry_count++;
}

void AppCheckInternal::CleanupRegistryCalls() {
g_app_check_registry_count--;
if (g_app_check_registry_count == 0) {
app_->function_registry()->UnregisterFunction(
::firebase::internal::FnAppCheckGetTokenAsync);
}
}

// Static functions used by the internal registry tool
bool AppCheckInternal::GetAppCheckTokenAsyncForRegistry(App* app,
void* /*unused*/,
void* out) {
Future<AppCheckToken>* out_future = static_cast<Future<AppCheckToken>*>(out);
if (!app || !out_future) {
return false;
}

AppCheck* app_check = AppCheck::GetInstance(app);
if (app_check) {
// TODO(amaurice): This should call some internal function instead of the
// public one, since this will change the *LastResult value behind the
// scenes.
*out_future = app_check->GetAppCheckToken(false);
return true;
}
return false;
}

} // namespace internal
} // namespace app_check
} // namespace firebase
12 changes: 12 additions & 0 deletions app_check/src/desktop/app_check_desktop.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ class AppCheckInternal {
// Get the Provider associated with the stored App used to create this.
AppCheckProvider* GetProvider();

// Adds internal App Check functions to the function registry, which other
// products can then call to get App Check information without needing a
// direct dependency.
void InitRegistryCalls();

// Removes those functions from the registry.
void CleanupRegistryCalls();

// Gets a Future<AppCheckToken> for the given App, stored in the out_future.
static bool GetAppCheckTokenAsyncForRegistry(App* app, void* /*unused*/,
void* out_future);

::firebase::App* app_;

FutureManager future_manager_;
Expand Down
11 changes: 6 additions & 5 deletions auth/src/auth.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,12 @@
#endif // FIREBASE_PLATFORM_WINDOWS

// Register the module initializer.
FIREBASE_APP_REGISTER_CALLBACKS(auth,
{ return ::firebase::kInitResultSuccess; },
{
// Nothing to tear down.
});
FIREBASE_APP_REGISTER_CALLBACKS(
auth, { return ::firebase::kInitResultSuccess; },
{
// Nothing to tear down.
},
false);

namespace firebase {
namespace auth {
Expand Down
Loading