Skip to content

Token-changed listeners for iOS AppCheck #1172

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 16 commits into from
Dec 20, 2022
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
66 changes: 64 additions & 2 deletions app_check/integration_test/src/integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,22 @@ class FirebaseAppCheckTest : public FirebaseTest {
std::vector<firebase::database::DatabaseReference> cleanup_paths_;
};

// Listens for token changed notifications
class TestAppCheckListener : public firebase::app_check::AppCheckListener {
public:
TestAppCheckListener() : num_token_changes_(0) {}
~TestAppCheckListener() override {}

void OnAppCheckTokenChanged(
const firebase::app_check::AppCheckToken& token) override {
last_token_ = token;
num_token_changes_ += 1;
}

int num_token_changes_;
firebase::app_check::AppCheckToken last_token_;
};

// Initialization flow looks like this:
// - For each test:
// - Optionally initialize App Check.
Expand All @@ -133,7 +149,14 @@ void FirebaseAppCheckTest::InitializeAppCheckWithDebug() {
firebase::app_check::DebugAppCheckProviderFactory::GetInstance());
}

void FirebaseAppCheckTest::TerminateAppCheck() {}
void FirebaseAppCheckTest::TerminateAppCheck() {
::firebase::app_check::AppCheck* app_check =
::firebase::app_check::AppCheck::GetInstance(app_);
if (app_check) {
LogDebug("Shutdown App Check.");
delete app_check;
}
}

void FirebaseAppCheckTest::InitializeApp() {
LogDebug("Initialize Firebase App.");
Expand Down Expand Up @@ -178,8 +201,8 @@ void FirebaseAppCheckTest::TearDown() {
// Teardown all the products
TerminateDatabase();
TerminateAuth();
TerminateApp();
TerminateAppCheck();
TerminateApp();
FirebaseTest::TearDown();
}

Expand Down Expand Up @@ -380,6 +403,45 @@ TEST_F(FirebaseAppCheckTest, TestGetTokenLastResult) {
future2.result()->expire_time_millis);
}

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

// Create and add a token changed listener.
TestAppCheckListener token_changed_listener;
app_check->AddAppCheckListener(&token_changed_listener);

firebase::Future<::firebase::app_check::AppCheckToken> future =
app_check->GetAppCheckToken(true);
EXPECT_TRUE(WaitForCompletion(future, "GetToken"));
::firebase::app_check::AppCheckToken token = *future.result();

ASSERT_EQ(token_changed_listener.num_token_changes_, 1);
EXPECT_EQ(token_changed_listener.last_token_.token, token.token);
}

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

// Create, add, and immediately remove a token changed listener.
TestAppCheckListener token_changed_listener;
app_check->AddAppCheckListener(&token_changed_listener);
app_check->RemoveAppCheckListener(&token_changed_listener);

firebase::Future<::firebase::app_check::AppCheckToken> future =
app_check->GetAppCheckToken(true);
EXPECT_TRUE(WaitForCompletion(future, "GetToken"));

ASSERT_EQ(token_changed_listener.num_token_changes_, 0);
}

TEST_F(FirebaseAppCheckTest, TestSignIn) {
InitializeAppCheckWithDebug();
InitializeApp();
Expand Down
1 change: 1 addition & 0 deletions app_check/src/include/firebase/app_check.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ struct AppCheckToken {

/// @brief Base class used to receive messages when AppCheck token changes.
class AppCheckListener {
public:
virtual ~AppCheckListener() = 0;
/// This method gets invoked on the UI thread on changes to the token state.
/// Does not trigger on token expiry.
Expand Down
28 changes: 28 additions & 0 deletions app_check/src/ios/app_check_ios.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,32 @@
#import "FIRAppCheck.h"
#endif // __OBJC__

#ifdef __OBJC__
// Interacts with the default notification center.
@interface AppCheckNotificationCenterWrapper : NSObject

- (id)init;

- (void)stopListening;

- (void)addListener:(firebase::app_check::AppCheckListener* _Nonnull)listener;

- (void)removeListener:(firebase::app_check::AppCheckListener* _Nonnull)listener;

- (void)appCheckTokenDidChangeNotification:(NSNotification*)notification;

@end
#endif // __OBJC__

namespace firebase {
namespace app_check {
namespace internal {

// This defines the class AppCheckNotificationCenterWrapperPointer, which is a
// C++-compatible wrapper around the AppCheckNotificationCenterWrapper Obj-C
// class.
OBJ_C_PTR_WRAPPER(AppCheckNotificationCenterWrapper);

// This defines the class FIRAppCheckPointer, which is a C++-compatible
// wrapper around the FIRAppCheck Obj-C class.
OBJ_C_PTR_WRAPPER(FIRAppCheck);
Expand Down Expand Up @@ -61,10 +83,16 @@ class AppCheckInternal {
private:
#ifdef __OBJC__
FIRAppCheck* impl() const { return impl_->get(); }

AppCheckNotificationCenterWrapper* notification_center_wrapper() const {
return notification_center_wrapper_->get();
}
#endif // __OBJC__

UniquePtr<FIRAppCheckPointer> impl_;

UniquePtr<AppCheckNotificationCenterWrapperPointer> notification_center_wrapper_;

::firebase::App* app_;

FutureManager future_manager_;
Expand Down
62 changes: 60 additions & 2 deletions app_check/src/ios/app_check_ios.mm
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "app_check/src/ios/app_check_ios.h"

#import "FIRApp.h"
#import "FIRAppCheck.h"
#import "FIRAppCheckProvider.h"
#import "FIRAppCheckProviderFactory.h"
#import "FIRAppCheckToken.h"
Expand Down Expand Up @@ -91,16 +92,69 @@ - (id)initWithProviderFactory:(firebase::app_check::AppCheckProviderFactory* _No

@end

@implementation AppCheckNotificationCenterWrapper

// A collection of NSValues containing AppCheckListener*
NSMutableArray* listeners_;

- (id)init {
self = [super init];
if (self) {
listeners_ = [[NSMutableArray alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(appCheckTokenDidChangeNotification:)
name:FIRAppCheckAppCheckTokenDidChangeNotification
object:nil];
}
return self;
}

- (void)stopListening {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:FIRAppCheckAppCheckTokenDidChangeNotification
object:nil];
}

- (void)addListener:(firebase::app_check::AppCheckListener* _Nonnull)listener {
[listeners_ addObject:[NSValue valueWithPointer:listener]];
}

- (void)removeListener:(firebase::app_check::AppCheckListener* _Nonnull)listener {
[listeners_ removeObject:[NSValue valueWithPointer:listener]];
}

- (void)appCheckTokenDidChangeNotification:(NSNotification*)notification {
NSDictionary* userInfo = notification.userInfo;
NSString* app_name = (NSString*)userInfo[kFIRAppCheckAppNameNotificationKey];
NSString* token = (NSString*)userInfo[kFIRAppCheckTokenNotificationKey];
// TODO(b/263138261): Include expiration time in this app check token.
// Note: The notification contains a token string, but does not currently
// contain expiration time.
firebase::app_check::AppCheckToken cpp_token;
cpp_token.token = firebase::util::NSStringToString(token);

for (NSValue* listener_value in listeners_) {
firebase::app_check::AppCheckListener* listener =
static_cast<firebase::app_check::AppCheckListener*>(listener_value.pointerValue);
listener->OnAppCheckTokenChanged(cpp_token);
}
}

@end

namespace firebase {
namespace app_check {
namespace internal {

AppCheckInternal::AppCheckInternal(App* app) : app_(app) {
future_manager().AllocFutureApi(this, kAppCheckFnCount);
impl_ = MakeUnique<FIRAppCheckPointer>([FIRAppCheck appCheck]);
notification_center_wrapper_ = MakeUnique<AppCheckNotificationCenterWrapperPointer>(
[[AppCheckNotificationCenterWrapper alloc] init]);
}

AppCheckInternal::~AppCheckInternal() {
[notification_center_wrapper() stopListening];
future_manager().ReleaseFutureApi(this);
app_ = nullptr;
}
Expand Down Expand Up @@ -157,9 +211,13 @@ - (id)initWithProviderFactory:(firebase::app_check::AppCheckProviderFactory* _No
future()->LastResult(kAppCheckFnGetAppCheckToken));
}

void AppCheckInternal::AddAppCheckListener(AppCheckListener* listener) {}
void AppCheckInternal::AddAppCheckListener(AppCheckListener* listener) {
[notification_center_wrapper() addListener:listener];
}

void AppCheckInternal::RemoveAppCheckListener(AppCheckListener* listener) {}
void AppCheckInternal::RemoveAppCheckListener(AppCheckListener* listener) {
[notification_center_wrapper() removeListener:listener];
}

} // namespace internal
} // namespace app_check
Expand Down