diff --git a/docs/auth/start.md b/docs/auth/start.md index bac3d5eeaa52..9c6f08b91e42 100644 --- a/docs/auth/start.md +++ b/docs/auth/start.md @@ -223,6 +223,35 @@ final auth = FirebaseAuth.instanceFor(app: Firebase.app(), persistence: Persiste await auth.setPersistence(Persistence.LOCAL); ``` +## Share authentication state between Apple apps + +On Apple platforms, you can share authentication state between apps in the same +developer account by storing it in a shared Keychain access group. First, enable +the Keychain Sharing capability for each app and add the same access group. +Then configure Firebase Auth with the fully qualified access group: + +```dart +await FirebaseAuth.instance.setSettings( + userAccessGroup: 'TEAMID.com.example.group1', +); +``` + +Changing from the default Keychain to a shared access group signs out an +existing user unless the user is migrated. To preserve the current session, +including an anonymous session, opt into migration when setting the access +group: + +```dart +await FirebaseAuth.instance.setSettings( + userAccessGroup: 'TEAMID.com.example.group1', + migrateCurrentUser: true, +); +``` + +Migration can replace a user already stored in the destination access group. +For more information about configuring access groups, see +[Enabling cross-app authentication with shared Apple Keychain](https://firebase.google.com/docs/auth/ios/single-sign-on). + ## Next Steps Explore the guides on signing in and signing up users with the supported diff --git a/packages/firebase_auth/firebase_auth/android/src/main/java/io/flutter/plugins/firebase/auth/GeneratedAndroidFirebaseAuth.java b/packages/firebase_auth/firebase_auth/android/src/main/java/io/flutter/plugins/firebase/auth/GeneratedAndroidFirebaseAuth.java index 5aaa168b70e8..f6187b613aa0 100644 --- a/packages/firebase_auth/firebase_auth/android/src/main/java/io/flutter/plugins/firebase/auth/GeneratedAndroidFirebaseAuth.java +++ b/packages/firebase_auth/firebase_auth/android/src/main/java/io/flutter/plugins/firebase/auth/GeneratedAndroidFirebaseAuth.java @@ -2128,6 +2128,19 @@ public void setUserAccessGroup(@Nullable String setterArg) { this.userAccessGroup = setterArg; } + private @NonNull Boolean migrateCurrentUser; + + public @NonNull Boolean getMigrateCurrentUser() { + return migrateCurrentUser; + } + + public void setMigrateCurrentUser(@NonNull Boolean setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"migrateCurrentUser\" is null."); + } + this.migrateCurrentUser = setterArg; + } + private @Nullable String phoneNumber; public @Nullable String getPhoneNumber() { @@ -2173,6 +2186,7 @@ public boolean equals(Object o) { return pigeonDeepEquals( appVerificationDisabledForTesting, that.appVerificationDisabledForTesting) && pigeonDeepEquals(userAccessGroup, that.userAccessGroup) + && pigeonDeepEquals(migrateCurrentUser, that.migrateCurrentUser) && pigeonDeepEquals(phoneNumber, that.phoneNumber) && pigeonDeepEquals(smsCode, that.smsCode) && pigeonDeepEquals(forceRecaptchaFlow, that.forceRecaptchaFlow); @@ -2185,6 +2199,7 @@ public int hashCode() { getClass(), appVerificationDisabledForTesting, userAccessGroup, + migrateCurrentUser, phoneNumber, smsCode, forceRecaptchaFlow @@ -2210,6 +2225,14 @@ public static final class Builder { return this; } + private @Nullable Boolean migrateCurrentUser; + + @CanIgnoreReturnValue + public @NonNull Builder setMigrateCurrentUser(@NonNull Boolean setterArg) { + this.migrateCurrentUser = setterArg; + return this; + } + private @Nullable String phoneNumber; @CanIgnoreReturnValue @@ -2238,6 +2261,7 @@ public static final class Builder { InternalFirebaseAuthSettings pigeonReturn = new InternalFirebaseAuthSettings(); pigeonReturn.setAppVerificationDisabledForTesting(appVerificationDisabledForTesting); pigeonReturn.setUserAccessGroup(userAccessGroup); + pigeonReturn.setMigrateCurrentUser(migrateCurrentUser); pigeonReturn.setPhoneNumber(phoneNumber); pigeonReturn.setSmsCode(smsCode); pigeonReturn.setForceRecaptchaFlow(forceRecaptchaFlow); @@ -2247,9 +2271,10 @@ public static final class Builder { @NonNull public ArrayList toList() { - ArrayList toListResult = new ArrayList<>(5); + ArrayList toListResult = new ArrayList<>(6); toListResult.add(appVerificationDisabledForTesting); toListResult.add(userAccessGroup); + toListResult.add(migrateCurrentUser); toListResult.add(phoneNumber); toListResult.add(smsCode); toListResult.add(forceRecaptchaFlow); @@ -2264,11 +2289,13 @@ public ArrayList toList() { (Boolean) appVerificationDisabledForTesting); Object userAccessGroup = pigeonVar_list.get(1); pigeonResult.setUserAccessGroup((String) userAccessGroup); - Object phoneNumber = pigeonVar_list.get(2); + Object migrateCurrentUser = pigeonVar_list.get(2); + pigeonResult.setMigrateCurrentUser((Boolean) migrateCurrentUser); + Object phoneNumber = pigeonVar_list.get(3); pigeonResult.setPhoneNumber((String) phoneNumber); - Object smsCode = pigeonVar_list.get(3); + Object smsCode = pigeonVar_list.get(4); pigeonResult.setSmsCode((String) smsCode); - Object forceRecaptchaFlow = pigeonVar_list.get(4); + Object forceRecaptchaFlow = pigeonVar_list.get(5); pigeonResult.setForceRecaptchaFlow((Boolean) forceRecaptchaFlow); return pigeonResult; } diff --git a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/FLTFirebaseAuthPlugin.m b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/FLTFirebaseAuthPlugin.m index 29140b911e9b..648667a170e9 100644 --- a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/FLTFirebaseAuthPlugin.m +++ b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/FLTFirebaseAuthPlugin.m @@ -1533,6 +1533,7 @@ - (void)setSettingsApp:(nonnull AuthPigeonFirebaseApp *)app settings:(nonnull InternalFirebaseAuthSettings *)settings completion:(nonnull void (^)(FlutterError *_Nullable))completion { FIRAuth *auth = [self getFIRAuthFromAppNameFromPigeon:app]; + FIRUser *userToMigrate = settings.migrateCurrentUser ? auth.currentUser : nil; if (settings.userAccessGroup != nil) { BOOL useUserAccessGroupSuccessful; @@ -1554,6 +1555,15 @@ - (void)setSettingsApp:(nonnull AuthPigeonFirebaseApp *)app @"on MacOS."); #endif + if (userToMigrate != nil) { + [auth updateCurrentUser:userToMigrate + completion:^(NSError *_Nullable error) { + completion(error == nil ? nil + : [FLTFirebaseAuthPlugin convertToFlutterError:error]); + }]; + return; + } + completion(nil); } diff --git a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/firebase_auth_messages.g.m b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/firebase_auth_messages.g.m index 82ae8cfcccc7..62bcec163d25 100644 --- a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/firebase_auth_messages.g.m +++ b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/firebase_auth_messages.g.m @@ -969,12 +969,14 @@ - (NSUInteger)hash { @implementation InternalFirebaseAuthSettings + (instancetype)makeWithAppVerificationDisabledForTesting:(BOOL)appVerificationDisabledForTesting userAccessGroup:(nullable NSString *)userAccessGroup + migrateCurrentUser:(BOOL)migrateCurrentUser phoneNumber:(nullable NSString *)phoneNumber smsCode:(nullable NSString *)smsCode forceRecaptchaFlow:(nullable NSNumber *)forceRecaptchaFlow { InternalFirebaseAuthSettings *pigeonResult = [[InternalFirebaseAuthSettings alloc] init]; pigeonResult.appVerificationDisabledForTesting = appVerificationDisabledForTesting; pigeonResult.userAccessGroup = userAccessGroup; + pigeonResult.migrateCurrentUser = migrateCurrentUser; pigeonResult.phoneNumber = phoneNumber; pigeonResult.smsCode = smsCode; pigeonResult.forceRecaptchaFlow = forceRecaptchaFlow; @@ -984,9 +986,10 @@ + (InternalFirebaseAuthSettings *)fromList:(NSArray *)list { InternalFirebaseAuthSettings *pigeonResult = [[InternalFirebaseAuthSettings alloc] init]; pigeonResult.appVerificationDisabledForTesting = [GetNullableObjectAtIndex(list, 0) boolValue]; pigeonResult.userAccessGroup = GetNullableObjectAtIndex(list, 1); - pigeonResult.phoneNumber = GetNullableObjectAtIndex(list, 2); - pigeonResult.smsCode = GetNullableObjectAtIndex(list, 3); - pigeonResult.forceRecaptchaFlow = GetNullableObjectAtIndex(list, 4); + pigeonResult.migrateCurrentUser = [GetNullableObjectAtIndex(list, 2) boolValue]; + pigeonResult.phoneNumber = GetNullableObjectAtIndex(list, 3); + pigeonResult.smsCode = GetNullableObjectAtIndex(list, 4); + pigeonResult.forceRecaptchaFlow = GetNullableObjectAtIndex(list, 5); return pigeonResult; } + (nullable InternalFirebaseAuthSettings *)nullableFromList:(NSArray *)list { @@ -996,6 +999,7 @@ + (nullable InternalFirebaseAuthSettings *)nullableFromList:(NSArray *)list return @[ @(self.appVerificationDisabledForTesting), self.userAccessGroup ?: [NSNull null], + @(self.migrateCurrentUser), self.phoneNumber ?: [NSNull null], self.smsCode ?: [NSNull null], self.forceRecaptchaFlow ?: [NSNull null], @@ -1011,6 +1015,7 @@ - (BOOL)isEqual:(id)object { InternalFirebaseAuthSettings *other = (InternalFirebaseAuthSettings *)object; return self.appVerificationDisabledForTesting == other.appVerificationDisabledForTesting && FLTPigeonDeepEquals(self.userAccessGroup, other.userAccessGroup) && + self.migrateCurrentUser == other.migrateCurrentUser && FLTPigeonDeepEquals(self.phoneNumber, other.phoneNumber) && FLTPigeonDeepEquals(self.smsCode, other.smsCode) && FLTPigeonDeepEquals(self.forceRecaptchaFlow, other.forceRecaptchaFlow); @@ -1020,6 +1025,7 @@ - (NSUInteger)hash { NSUInteger result = [self class].hash; result = result * 31 + @(self.appVerificationDisabledForTesting).hash; result = result * 31 + FLTPigeonDeepHash(self.userAccessGroup); + result = result * 31 + @(self.migrateCurrentUser).hash; result = result * 31 + FLTPigeonDeepHash(self.phoneNumber); result = result * 31 + FLTPigeonDeepHash(self.smsCode); result = result * 31 + FLTPigeonDeepHash(self.forceRecaptchaFlow); diff --git a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/include/Public/firebase_auth_messages.g.h b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/include/Public/firebase_auth_messages.g.h index f83da7e34a3b..001c7bc663d2 100644 --- a/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/include/Public/firebase_auth_messages.g.h +++ b/packages/firebase_auth/firebase_auth/ios/firebase_auth/Sources/firebase_auth/include/Public/firebase_auth_messages.g.h @@ -230,11 +230,13 @@ typedef NS_ENUM(NSUInteger, ActionCodeInfoOperation) { - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithAppVerificationDisabledForTesting:(BOOL)appVerificationDisabledForTesting userAccessGroup:(nullable NSString *)userAccessGroup + migrateCurrentUser:(BOOL)migrateCurrentUser phoneNumber:(nullable NSString *)phoneNumber smsCode:(nullable NSString *)smsCode forceRecaptchaFlow:(nullable NSNumber *)forceRecaptchaFlow; @property(nonatomic, assign) BOOL appVerificationDisabledForTesting; @property(nonatomic, copy, nullable) NSString *userAccessGroup; +@property(nonatomic, assign) BOOL migrateCurrentUser; @property(nonatomic, copy, nullable) NSString *phoneNumber; @property(nonatomic, copy, nullable) NSString *smsCode; @property(nonatomic, strong, nullable) NSNumber *forceRecaptchaFlow; diff --git a/packages/firebase_auth/firebase_auth/lib/src/firebase_auth.dart b/packages/firebase_auth/firebase_auth/lib/src/firebase_auth.dart index 3c07d4b4707a..cb4c7a7d956d 100644 --- a/packages/firebase_auth/firebase_auth/lib/src/firebase_auth.dart +++ b/packages/firebase_auth/firebase_auth/lib/src/firebase_auth.dart @@ -397,9 +397,15 @@ class FirebaseAuth extends FirebasePlugin implements FirebaseService { /// Key Sharing capabilities must be enabled for your app via XCode (Project /// settings > Capabilities). To learn more, visit the /// [Apple documentation](https://developer.apple.com/documentation/security/keychain_services/keychain_items/sharing_access_to_keychain_items_among_a_collection_of_apps). + /// + /// [migrateCurrentUser] This setting only applies to iOS and macOS platforms. + /// When `true`, the currently signed-in user is migrated to + /// [userAccessGroup]. This may replace a user already stored in that access + /// group. Future setSettings({ bool appVerificationDisabledForTesting = false, String? userAccessGroup, + bool migrateCurrentUser = false, String? phoneNumber, String? smsCode, bool? forceRecaptchaFlow, @@ -407,6 +413,7 @@ class FirebaseAuth extends FirebasePlugin implements FirebaseService { return _delegate.setSettings( appVerificationDisabledForTesting: appVerificationDisabledForTesting, userAccessGroup: userAccessGroup, + migrateCurrentUser: migrateCurrentUser, phoneNumber: phoneNumber, smsCode: smsCode, forceRecaptchaFlow: forceRecaptchaFlow, diff --git a/packages/firebase_auth/firebase_auth/test/firebase_auth_test.dart b/packages/firebase_auth/firebase_auth/test/firebase_auth_test.dart index 5e570b0b5b82..865fc1b112d7 100644 --- a/packages/firebase_auth/firebase_auth/test/firebase_auth_test.dart +++ b/packages/firebase_auth/firebase_auth/test/firebase_auth_test.dart @@ -506,6 +506,7 @@ void main() { smsCode: any, forceRecaptchaFlow: any, userAccessGroup: any, + migrateCurrentUser: true, )).thenAnswer((i) async {}); String phoneNumber = '123456'; @@ -513,6 +514,7 @@ void main() { bool forceRecaptchaFlow = true; bool appVerificationDisabledForTesting = true; String userAccessGroup = 'group-id'; + bool migrateCurrentUser = true; await auth.setSettings( appVerificationDisabledForTesting: appVerificationDisabledForTesting, @@ -520,6 +522,7 @@ void main() { smsCode: smsCode, forceRecaptchaFlow: forceRecaptchaFlow, userAccessGroup: userAccessGroup, + migrateCurrentUser: migrateCurrentUser, ); verify( @@ -530,6 +533,7 @@ void main() { smsCode: smsCode, forceRecaptchaFlow: forceRecaptchaFlow, userAccessGroup: userAccessGroup, + migrateCurrentUser: migrateCurrentUser, ), ); }); @@ -1122,6 +1126,7 @@ class MockFirebaseAuth extends Mock Future setSettings({ bool? appVerificationDisabledForTesting, String? userAccessGroup, + bool migrateCurrentUser = false, String? phoneNumber, String? smsCode, bool? forceRecaptchaFlow, @@ -1130,6 +1135,7 @@ class MockFirebaseAuth extends Mock Invocation.method(#setSettings, [ appVerificationDisabledForTesting, userAccessGroup, + migrateCurrentUser, phoneNumber, smsCode, forceRecaptchaFlow, diff --git a/packages/firebase_auth/firebase_auth/windows/messages.g.cpp b/packages/firebase_auth/firebase_auth/windows/messages.g.cpp index ea2200e617b2..810c7b5b49e2 100644 --- a/packages/firebase_auth/firebase_auth/windows/messages.g.cpp +++ b/packages/firebase_auth/firebase_auth/windows/messages.g.cpp @@ -1821,19 +1821,22 @@ size_t PigeonInternalDeepHash(const InternalActionCodeSettings& v) { // InternalFirebaseAuthSettings InternalFirebaseAuthSettings::InternalFirebaseAuthSettings( - bool app_verification_disabled_for_testing) + bool app_verification_disabled_for_testing, bool migrate_current_user) : app_verification_disabled_for_testing_( - app_verification_disabled_for_testing) {} + app_verification_disabled_for_testing), + migrate_current_user_(migrate_current_user) {} InternalFirebaseAuthSettings::InternalFirebaseAuthSettings( bool app_verification_disabled_for_testing, - const std::string* user_access_group, const std::string* phone_number, - const std::string* sms_code, const bool* force_recaptcha_flow) + const std::string* user_access_group, bool migrate_current_user, + const std::string* phone_number, const std::string* sms_code, + const bool* force_recaptcha_flow) : app_verification_disabled_for_testing_( app_verification_disabled_for_testing), user_access_group_(user_access_group ? std::optional(*user_access_group) : std::nullopt), + migrate_current_user_(migrate_current_user), phone_number_(phone_number ? std::optional(*phone_number) : std::nullopt), sms_code_(sms_code ? std::optional(*sms_code) @@ -1867,6 +1870,14 @@ void InternalFirebaseAuthSettings::set_user_access_group( user_access_group_ = value_arg; } +bool InternalFirebaseAuthSettings::migrate_current_user() const { + return migrate_current_user_; +} + +void InternalFirebaseAuthSettings::set_migrate_current_user(bool value_arg) { + migrate_current_user_ = value_arg; +} + const std::string* InternalFirebaseAuthSettings::phone_number() const { return phone_number_ ? &(*phone_number_) : nullptr; } @@ -1911,10 +1922,11 @@ void InternalFirebaseAuthSettings::set_force_recaptcha_flow(bool value_arg) { EncodableList InternalFirebaseAuthSettings::ToEncodableList() const { EncodableList list; - list.reserve(5); + list.reserve(6); list.push_back(EncodableValue(app_verification_disabled_for_testing_)); list.push_back(user_access_group_ ? EncodableValue(*user_access_group_) : EncodableValue()); + list.push_back(EncodableValue(migrate_current_user_)); list.push_back(phone_number_ ? EncodableValue(*phone_number_) : EncodableValue()); list.push_back(sms_code_ ? EncodableValue(*sms_code_) : EncodableValue()); @@ -1925,21 +1937,22 @@ EncodableList InternalFirebaseAuthSettings::ToEncodableList() const { InternalFirebaseAuthSettings InternalFirebaseAuthSettings::FromEncodableList( const EncodableList& list) { - InternalFirebaseAuthSettings decoded(std::get(list[0])); + InternalFirebaseAuthSettings decoded(std::get(list[0]), + std::get(list[2])); auto& encodable_user_access_group = list[1]; if (!encodable_user_access_group.IsNull()) { decoded.set_user_access_group( std::get(encodable_user_access_group)); } - auto& encodable_phone_number = list[2]; + auto& encodable_phone_number = list[3]; if (!encodable_phone_number.IsNull()) { decoded.set_phone_number(std::get(encodable_phone_number)); } - auto& encodable_sms_code = list[3]; + auto& encodable_sms_code = list[4]; if (!encodable_sms_code.IsNull()) { decoded.set_sms_code(std::get(encodable_sms_code)); } - auto& encodable_force_recaptcha_flow = list[4]; + auto& encodable_force_recaptcha_flow = list[5]; if (!encodable_force_recaptcha_flow.IsNull()) { decoded.set_force_recaptcha_flow( std::get(encodable_force_recaptcha_flow)); @@ -1954,6 +1967,8 @@ bool InternalFirebaseAuthSettings::operator==( other.app_verification_disabled_for_testing_) && PigeonInternalDeepEquals(user_access_group_, other.user_access_group_) && + PigeonInternalDeepEquals(migrate_current_user_, + other.migrate_current_user_) && PigeonInternalDeepEquals(phone_number_, other.phone_number_) && PigeonInternalDeepEquals(sms_code_, other.sms_code_) && PigeonInternalDeepEquals(force_recaptcha_flow_, @@ -1970,6 +1985,7 @@ size_t InternalFirebaseAuthSettings::Hash() const { result = result * 31 + PigeonInternalDeepHash(app_verification_disabled_for_testing_); result = result * 31 + PigeonInternalDeepHash(user_access_group_); + result = result * 31 + PigeonInternalDeepHash(migrate_current_user_); result = result * 31 + PigeonInternalDeepHash(phone_number_); result = result * 31 + PigeonInternalDeepHash(sms_code_); result = result * 31 + PigeonInternalDeepHash(force_recaptcha_flow_); diff --git a/packages/firebase_auth/firebase_auth/windows/messages.g.h b/packages/firebase_auth/firebase_auth/windows/messages.g.h index e1e1af028fbc..e09037f7e908 100644 --- a/packages/firebase_auth/firebase_auth/windows/messages.g.h +++ b/packages/firebase_auth/firebase_auth/windows/messages.g.h @@ -812,13 +812,14 @@ class InternalFirebaseAuthSettings { public: // Constructs an object setting all non-nullable fields. explicit InternalFirebaseAuthSettings( - bool app_verification_disabled_for_testing); + bool app_verification_disabled_for_testing, bool migrate_current_user); // Constructs an object setting all fields. explicit InternalFirebaseAuthSettings( bool app_verification_disabled_for_testing, - const std::string* user_access_group, const std::string* phone_number, - const std::string* sms_code, const bool* force_recaptcha_flow); + const std::string* user_access_group, bool migrate_current_user, + const std::string* phone_number, const std::string* sms_code, + const bool* force_recaptcha_flow); bool app_verification_disabled_for_testing() const; void set_app_verification_disabled_for_testing(bool value_arg); @@ -827,6 +828,9 @@ class InternalFirebaseAuthSettings { void set_user_access_group(const std::string_view* value_arg); void set_user_access_group(std::string_view value_arg); + bool migrate_current_user() const; + void set_migrate_current_user(bool value_arg); + const std::string* phone_number() const; void set_phone_number(const std::string_view* value_arg); void set_phone_number(std::string_view value_arg); @@ -863,6 +867,7 @@ class InternalFirebaseAuthSettings { friend class PigeonInternalCodecSerializer; bool app_verification_disabled_for_testing_; std::optional user_access_group_; + bool migrate_current_user_; std::optional phone_number_; std::optional sms_code_; std::optional force_recaptcha_flow_; diff --git a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/method_channel/method_channel_firebase_auth.dart b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/method_channel/method_channel_firebase_auth.dart index c4ee96da1389..c447c6e22d4b 100644 --- a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/method_channel/method_channel_firebase_auth.dart +++ b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/method_channel/method_channel_firebase_auth.dart @@ -595,10 +595,17 @@ class MethodChannelFirebaseAuth extends FirebaseAuthPlatform { Future setSettings({ bool appVerificationDisabledForTesting = false, String? userAccessGroup, + bool migrateCurrentUser = false, String? phoneNumber, String? smsCode, bool? forceRecaptchaFlow, }) async { + if (migrateCurrentUser && userAccessGroup == null) { + throw ArgumentError( + 'The [userAccessGroup] must be set when [migrateCurrentUser] is true.', + ); + } + if (phoneNumber != null && smsCode == null || phoneNumber == null && smsCode != null) { throw ArgumentError( @@ -613,6 +620,7 @@ class MethodChannelFirebaseAuth extends FirebaseAuthPlatform { appVerificationDisabledForTesting: appVerificationDisabledForTesting, userAccessGroup: userAccessGroup, + migrateCurrentUser: migrateCurrentUser, phoneNumber: phoneNumber, smsCode: smsCode, forceRecaptchaFlow: forceRecaptchaFlow, diff --git a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/pigeon/messages.pigeon.dart b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/pigeon/messages.pigeon.dart index 60844c34575c..90d58851910e 100644 --- a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/pigeon/messages.pigeon.dart +++ b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/pigeon/messages.pigeon.dart @@ -919,6 +919,7 @@ class InternalFirebaseAuthSettings { InternalFirebaseAuthSettings({ required this.appVerificationDisabledForTesting, this.userAccessGroup, + required this.migrateCurrentUser, this.phoneNumber, this.smsCode, this.forceRecaptchaFlow, @@ -928,6 +929,8 @@ class InternalFirebaseAuthSettings { String? userAccessGroup; + bool migrateCurrentUser; + String? phoneNumber; String? smsCode; @@ -938,6 +941,7 @@ class InternalFirebaseAuthSettings { return [ appVerificationDisabledForTesting, userAccessGroup, + migrateCurrentUser, phoneNumber, smsCode, forceRecaptchaFlow, @@ -953,9 +957,10 @@ class InternalFirebaseAuthSettings { return InternalFirebaseAuthSettings( appVerificationDisabledForTesting: result[0]! as bool, userAccessGroup: result[1] as String?, - phoneNumber: result[2] as String?, - smsCode: result[3] as String?, - forceRecaptchaFlow: result[4] as bool?, + migrateCurrentUser: result[2]! as bool, + phoneNumber: result[3] as String?, + smsCode: result[4] as String?, + forceRecaptchaFlow: result[5] as bool?, ); } @@ -972,6 +977,7 @@ class InternalFirebaseAuthSettings { return _deepEquals(appVerificationDisabledForTesting, other.appVerificationDisabledForTesting) && _deepEquals(userAccessGroup, other.userAccessGroup) && + _deepEquals(migrateCurrentUser, other.migrateCurrentUser) && _deepEquals(phoneNumber, other.phoneNumber) && _deepEquals(smsCode, other.smsCode) && _deepEquals(forceRecaptchaFlow, other.forceRecaptchaFlow); diff --git a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/platform_interface/platform_interface_firebase_auth.dart b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/platform_interface/platform_interface_firebase_auth.dart index 60377db69238..491b516e66b7 100644 --- a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/platform_interface/platform_interface_firebase_auth.dart +++ b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/platform_interface/platform_interface_firebase_auth.dart @@ -396,9 +396,15 @@ abstract class FirebaseAuthPlatform extends PlatformInterface { /// Key Sharing capabilities must be enabled for your app via XCode (Project /// settings > Capabilities). To learn more, visit the /// [Apple documentation](https://developer.apple.com/documentation/security/keychain_services/keychain_items/sharing_access_to_keychain_items_among_a_collection_of_apps). + /// + /// [migrateCurrentUser] This setting only applies to iOS and macOS platforms. + /// When `true`, the currently signed-in user is migrated to + /// [userAccessGroup]. This may replace a user already stored in that access + /// group. Future setSettings({ bool appVerificationDisabledForTesting = false, String? userAccessGroup, + bool migrateCurrentUser = false, String? phoneNumber, String? smsCode, bool? forceRecaptchaFlow, diff --git a/packages/firebase_auth/firebase_auth_platform_interface/pigeons/messages.dart b/packages/firebase_auth/firebase_auth_platform_interface/pigeons/messages.dart index 943fa1693b87..d109e35a458c 100644 --- a/packages/firebase_auth/firebase_auth_platform_interface/pigeons/messages.dart +++ b/packages/firebase_auth/firebase_auth_platform_interface/pigeons/messages.dart @@ -242,6 +242,7 @@ class InternalFirebaseAuthSettings { const InternalFirebaseAuthSettings({ required this.appVerificationDisabledForTesting, required this.userAccessGroup, + required this.migrateCurrentUser, required this.phoneNumber, required this.smsCode, required this.forceRecaptchaFlow, @@ -249,6 +250,7 @@ class InternalFirebaseAuthSettings { final bool appVerificationDisabledForTesting; final String? userAccessGroup; + final bool migrateCurrentUser; final String? phoneNumber; final String? smsCode; final bool? forceRecaptchaFlow; diff --git a/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart b/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart index 7234322bd51e..860e6c5cce0d 100644 --- a/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart +++ b/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart @@ -393,6 +393,7 @@ class FirebaseAuthWeb extends FirebaseAuthPlatform { Future setSettings({ bool? appVerificationDisabledForTesting, String? userAccessGroup, + bool migrateCurrentUser = false, String? phoneNumber, String? smsCode, bool? forceRecaptchaFlow, diff --git a/packages/firebase_data_connect/firebase_data_connect/test/src/network/websocket_transport_test.mocks.dart b/packages/firebase_data_connect/firebase_data_connect/test/src/network/websocket_transport_test.mocks.dart index f73d415a9c22..bdfabe96506d 100644 --- a/packages/firebase_data_connect/firebase_data_connect/test/src/network/websocket_transport_test.mocks.dart +++ b/packages/firebase_data_connect/firebase_data_connect/test/src/network/websocket_transport_test.mocks.dart @@ -379,6 +379,7 @@ class MockFirebaseAuth extends _i1.Mock implements _i4.FirebaseAuth { _i5.Future setSettings({ bool? appVerificationDisabledForTesting = false, String? userAccessGroup, + bool migrateCurrentUser = false, String? phoneNumber, String? smsCode, bool? forceRecaptchaFlow, @@ -391,6 +392,7 @@ class MockFirebaseAuth extends _i1.Mock implements _i4.FirebaseAuth { #appVerificationDisabledForTesting: appVerificationDisabledForTesting, #userAccessGroup: userAccessGroup, + #migrateCurrentUser: migrateCurrentUser, #phoneNumber: phoneNumber, #smsCode: smsCode, #forceRecaptchaFlow: forceRecaptchaFlow, diff --git a/tests/integration_test/firebase_auth/firebase_auth_instance_e2e_test.dart b/tests/integration_test/firebase_auth/firebase_auth_instance_e2e_test.dart index 17c1448a273b..2c88535a173d 100644 --- a/tests/integration_test/firebase_auth/firebase_auth_instance_e2e_test.dart +++ b/tests/integration_test/firebase_auth/firebase_auth_instance_e2e_test.dart @@ -1030,6 +1030,25 @@ void main() { ); group('setSettings()', () { + test( + 'migrates the current user when changing access groups', + () async { + final auth = FirebaseAuth.instance; + + await auth.signOut(); + final credential = await auth.signInAnonymously(); + final uid = credential.user!.uid; + + await auth.setSettings( + userAccessGroup: 'YYX2P3XVJ7.io.flutter.plugins.firebase.tests', + migrateCurrentUser: true, + ); + + expect(auth.currentUser?.uid, uid); + }, + skip: kIsWeb || defaultTargetPlatform != TargetPlatform.iOS, + ); + test( 'throws argument error if phoneNumber & smsCode have not been set simultaneously', () async { diff --git a/tests/ios/Runner/Runner.entitlements b/tests/ios/Runner/Runner.entitlements index d7ab8334dfa2..e21cc550c0d9 100644 --- a/tests/ios/Runner/Runner.entitlements +++ b/tests/ios/Runner/Runner.entitlements @@ -6,5 +6,9 @@ applinks:flutterfiretests.page.link + keychain-access-groups + + $(AppIdentifierPrefix)io.flutter.plugins.firebase.tests +