diff --git a/auth/integration_test/src/integration_test.cc b/auth/integration_test/src/integration_test.cc index eef1301adc..8c8a586f51 100644 --- a/auth/integration_test/src/integration_test.cc +++ b/auth/integration_test/src/integration_test.cc @@ -1043,6 +1043,29 @@ TEST_F(FirebaseAuthTest, TestWithCustomEmailAndPassword) { EXPECT_EQ(auth_->current_user().email(), kCustomTestEmail); } +TEST_F(FirebaseAuthTest, TestUseUserAccessGroupDoesNotCrash) { + firebase::auth::AuthError error = + auth_->UseUserAccessGroup("com.google.firebase.test.accessgroup"); +#if TARGET_OS_IPHONE + // On iOS, this might return an error if keychain sharing isn't set up + // for the test app, but it shouldn't crash. We accept kAuthErrorNone or + // kAuthErrorKeychainError. + EXPECT_THAT(error, AnyOf(firebase::auth::kAuthErrorNone, + firebase::auth::kAuthErrorKeychainError)); +#else + // On other platforms, it should be a no-op and return kAuthErrorNone. + EXPECT_EQ(error, firebase::auth::kAuthErrorNone); +#endif + + error = auth_->UseUserAccessGroup(nullptr); +#if TARGET_OS_IPHONE + EXPECT_THAT(error, AnyOf(firebase::auth::kAuthErrorNone, + firebase::auth::kAuthErrorKeychainError)); +#else + EXPECT_EQ(error, firebase::auth::kAuthErrorNone); +#endif +} + TEST_F(FirebaseAuthTest, TestAuthPersistenceWithAnonymousSignin) { // Automated test is disabled on linux due to the need to unlock the keystore. SKIP_TEST_ON_LINUX; diff --git a/auth/src/android/auth_android.cc b/auth/src/android/auth_android.cc index e0a9a669cb..47d1dd870c 100644 --- a/auth/src/android/auth_android.cc +++ b/auth/src/android/auth_android.cc @@ -670,6 +670,12 @@ void Auth::UseEmulator(std::string host, uint32_t port) { SetEmulatorJni(auth_data_, host.c_str(), port); } +AuthError Auth::UseUserAccessGroup(const char* access_group) { + (void)access_group; // Unused on Android. + // This is an iOS-only feature, so it's a no-op on Android. + return kAuthErrorNone; +} + // Not implemented for Android. void EnableTokenAutoRefresh(AuthData* auth_data) {} void DisableTokenAutoRefresh(AuthData* auth_data) {} diff --git a/auth/src/desktop/auth_desktop.cc b/auth/src/desktop/auth_desktop.cc index dc9aca2950..e66dfa1617 100644 --- a/auth/src/desktop/auth_desktop.cc +++ b/auth/src/desktop/auth_desktop.cc @@ -575,6 +575,12 @@ void Auth::UseEmulator(std::string host, uint32_t port) { auth_impl->assigned_emulator_url.append(std::to_string(port)); } +AuthError Auth::UseUserAccessGroup(const char* access_group) { + (void)access_group; // Unused on desktop. + // This is an iOS-only feature, so it's a no-op on desktop. + return kAuthErrorNone; +} + void InitializeTokenRefresher(AuthData* auth_data) { auto auth_impl = static_cast(auth_data->auth_impl); auth_impl->token_refresh_thread.Initialize(auth_data); diff --git a/auth/src/include/firebase/auth.h b/auth/src/include/firebase/auth.h index f6809c4a57..c994d18652 100644 --- a/auth/src/include/firebase/auth.h +++ b/auth/src/include/firebase/auth.h @@ -502,6 +502,23 @@ class Auth { /// Gets the App this auth object is connected to. App& app(); + /// @brief Modifies this Auth instance to use the specified keychain access + /// group. + /// + /// For more details on how to configure keychain access groups and capabilities + /// on iOS, please refer to the Firebase iOS SDK documentation and Apple's + /// documentation on keychain services. + /// + /// @note This method is only functional on iOS. On other platforms, it's a + /// no-op and will return kAuthErrorNone. + /// + /// @param[in] access_group The keychain access group to use. Set to @c nullptr + /// to use the default app bundle ID access group. + /// + /// @return kAuthErrorNone on success, or an AuthError code if an error + /// occurred (iOS only). + AuthError UseUserAccessGroup(const char* access_group); + /// Returns the Auth object for an App. Creates the Auth if required. /// /// To get the Auth object for the default app, use, diff --git a/auth/src/ios/auth_ios.mm b/auth/src/ios/auth_ios.mm index a0292ba3b8..0118eefcc5 100644 --- a/auth/src/ios/auth_ios.mm +++ b/auth/src/ios/auth_ios.mm @@ -590,6 +590,19 @@ void SignInCallback(FIRUser *_Nullable user, NSError *_Nullable error, SetEmulatorJni(auth_data_, host.c_str(), port); } +AuthError Auth::UseUserAccessGroup(const char* access_group) { + if (!auth_data_) { + return kAuthErrorFailure; // Or a more specific "not initialized" error if available + } + NSString* ns_access_group = access_group ? @(access_group) : nil; + NSError* error = nil; + BOOL success = [AuthImpl(auth_data_) useUserAccessGroup:ns_access_group error:&error]; + if (!success) { + return AuthErrorFromNSError(error); + } + return kAuthErrorNone; +} + // Remap iOS SDK errors reported by the UIDelegate. While these errors seem like // user interaction errors, they are actually caused by bad provider ids. NSError *RemapBadProviderIDErrors(NSError *_Nonnull error) {