diff --git a/auth/integration_test/src/integration_test.cc b/auth/integration_test/src/integration_test.cc index eef1301adc..96a83a3c0b 100644 --- a/auth/integration_test/src/integration_test.cc +++ b/auth/integration_test/src/integration_test.cc @@ -1513,4 +1513,47 @@ TEST_F(FirebaseAuthTest, TestLinkFederatedProviderBadProviderIdFails) { #endif // defined(ENABLE_OAUTH_TESTS) +TEST_F(FirebaseAuthTest, TestUseUserAccessGroup) { + // This test simply calls the UseUserAccessGroup method to ensure it doesn't + // crash. It doesn't verify the underlying keychain behavior. + // On non-iOS platforms, this is a no-op and should return kAuthErrorNone. + // On iOS, it will call the underlying OS method. + + LogDebug("Calling UseUserAccessGroup with a test group ID."); + firebase::auth::AuthError error = + auth_->UseUserAccessGroup("com.firebase.test.accessgroup"); + +#if TARGET_OS_IPHONE + // On iOS, the actual error code depends on keychain access and provisioning. + // For this test, we just ensure it doesn't crash. + // A specific error like kAuthErrorMissingIOSBundleID might occur if the + // environment isn't set up for keychain sharing, or kAuthErrorNone on + // success. For a simple "does not crash" test, we don't strictly check + // the error code, but kAuthErrorNone is the ideal outcome if keychain is + // usable. + LogDebug("UseUserAccessGroup returned %d on iOS.", error); +#else + // On other platforms, it should be a no-op. + EXPECT_EQ(error, firebase::auth::kAuthErrorNone); +#endif + + LogDebug("Calling UseUserAccessGroup with nullptr to clear the group."); + error = auth_->UseUserAccessGroup(nullptr); + +#if TARGET_OS_IPHONE + LogDebug("UseUserAccessGroup(nullptr) returned %d on iOS.", error); +#else + EXPECT_EQ(error, firebase::auth::kAuthErrorNone); +#endif + + LogDebug("Calling UseUserAccessGroup with empty string to clear the group."); + error = auth_->UseUserAccessGroup(""); + +#if TARGET_OS_IPHONE + LogDebug("UseUserAccessGroup(\"\") returned %d on iOS.", error); +#else + EXPECT_EQ(error, firebase::auth::kAuthErrorNone); +#endif +} + } // namespace firebase_testapp_automated diff --git a/auth/src/android/auth_android.cc b/auth/src/android/auth_android.cc index e0a9a669cb..0757e92acb 100644 --- a/auth/src/android/auth_android.cc +++ b/auth/src/android/auth_android.cc @@ -676,5 +676,14 @@ void DisableTokenAutoRefresh(AuthData* auth_data) {} void InitializeTokenRefresher(AuthData* auth_data) {} void DestroyTokenRefresher(AuthData* auth_data) {} +// Stub implementation for UseUserAccessGroupInternal on Android. +AuthError UseUserAccessGroupInternal(AuthData* auth_data, + const char* group_id) { + // This function is a no-op on Android. + (void)auth_data; + (void)group_id; + return kAuthErrorNone; +} + } // namespace auth } // namespace firebase diff --git a/auth/src/auth.cc b/auth/src/auth.cc index b1417f63f1..9981aaacde 100644 --- a/auth/src/auth.cc +++ b/auth/src/auth.cc @@ -373,5 +373,12 @@ AUTH_RESULT_FN(Auth, SignInWithEmailAndPassword, AuthResult) AUTH_RESULT_FN(Auth, CreateUserWithEmailAndPassword, AuthResult) +AuthError Auth::UseUserAccessGroup(const char* group_id) { + if (!auth_data_) { + return kAuthErrorUninitialized; + } + return UseUserAccessGroupInternal(auth_data_, group_id); +} + } // namespace auth } // namespace firebase diff --git a/auth/src/desktop/auth_desktop.cc b/auth/src/desktop/auth_desktop.cc index dc9aca2950..6ed47c60f1 100644 --- a/auth/src/desktop/auth_desktop.cc +++ b/auth/src/desktop/auth_desktop.cc @@ -768,5 +768,14 @@ void IdTokenRefreshThread::DisableAuthRefresh() { ref_count_--; } +// Stub implementation for UseUserAccessGroupInternal on desktop. +AuthError UseUserAccessGroupInternal(AuthData* auth_data, + const char* group_id) { + // This function is a no-op on desktop platforms. + (void)auth_data; + (void)group_id; + return kAuthErrorNone; +} + } // namespace auth } // namespace firebase diff --git a/auth/src/include/firebase/auth.h b/auth/src/include/firebase/auth.h index f6809c4a57..e72a64ceb8 100644 --- a/auth/src/include/firebase/auth.h +++ b/auth/src/include/firebase/auth.h @@ -517,6 +517,23 @@ class Auth { /// not available on the current device. static Auth* GetAuth(App* app, InitResult* init_result_out = nullptr); + /// @brief Sets the user access group to use for data sharing for the app. + /// + /// This method is only applicable to iOS. On other platforms, it's a no-op + /// and will return `kAuthErrorNone`. + /// + /// Sets the user access group to use for data sharing for the app. This + /// should be used to share authentication data between apps that have the + /// same access group. + /// + /// @param[in] group_id The user access group to use. For example, + /// `com.example.accessgroup`. Set to `nullptr` or an empty string to clear + /// a previously set value and use the default keychain group. + /// + /// @return Returns `kAuthErrorNone` on success, or an `AuthError` code if an + /// error occurred. + AuthError UseUserAccessGroup(const char* group_id); + private: /// @cond FIREBASE_APP_INTERNAL friend class ::firebase::App; diff --git a/auth/src/ios/auth_ios.mm b/auth/src/ios/auth_ios.mm index a0292ba3b8..2ffbb9cb83 100644 --- a/auth/src/ios/auth_ios.mm +++ b/auth/src/ios/auth_ios.mm @@ -608,5 +608,17 @@ void DisableTokenAutoRefresh(AuthData *auth_data) {} void InitializeTokenRefresher(AuthData *auth_data) {} void DestroyTokenRefresher(AuthData *auth_data) {} +AuthError UseUserAccessGroupInternal(AuthData* auth_data, + const char* group_id) { + NSString* group_id_nsstring = nil; + if (group_id != nullptr && strlen(group_id) > 0) { + group_id_nsstring = [NSString stringWithUTF8String:group_id]; + } + + NSError* ns_error = nil; + [AuthImpl(auth_data) useUserAccessGroup:group_id_nsstring error:&ns_error]; + return AuthErrorFromNSError(ns_error); +} + } // namespace auth } // namespace firebase