diff --git a/zerver/actions/user_groups.py b/zerver/actions/user_groups.py index 687aef3550..6fc9ca9c68 100644 --- a/zerver/actions/user_groups.py +++ b/zerver/actions/user_groups.py @@ -7,7 +7,11 @@ from django.utils.timezone import now as timezone_now from django.utils.translation import gettext as _ from zerver.lib.exceptions import JsonableError -from zerver.lib.user_groups import access_user_group_by_id +from zerver.lib.user_groups import ( + access_user_group_by_id, + get_role_based_system_groups_dict, + set_defaults_for_group_settings, +) from zerver.models import ( GroupGroupMembership, Realm, @@ -35,9 +39,14 @@ def create_user_group_in_database( description: str = "", is_system_group: bool = False, ) -> UserGroup: - user_group = UserGroup.objects.create( + user_group = UserGroup( name=name, realm=realm, description=description, is_system_group=is_system_group ) + system_groups_name_dict = get_role_based_system_groups_dict(realm) + user_group = set_defaults_for_group_settings(user_group, system_groups_name_dict) + + user_group.save() + UserGroupMembership.objects.bulk_create( UserGroupMembership(user_profile=member, user_group=user_group) for member in members ) diff --git a/zerver/lib/import_realm.py b/zerver/lib/import_realm.py index 51d5dbe323..98d046bddf 100644 --- a/zerver/lib/import_realm.py +++ b/zerver/lib/import_realm.py @@ -969,6 +969,10 @@ def do_import_realm(import_dir: Path, subdomain: str, processes: int = 1) -> Rea if "zerver_usergroup" in data: update_model_ids(UserGroup, data, "usergroup") re_map_foreign_keys(data, "zerver_usergroup", "realm", related_table="realm") + for setting_name in UserGroup.GROUP_PERMISSION_SETTINGS: + re_map_foreign_keys( + data, "zerver_usergroup", setting_name, related_table="usergroup" + ) bulk_import_model(data, UserGroup) # We expect Zulip server exports to contain these system groups, diff --git a/zerver/lib/types.py b/zerver/lib/types.py index fc89a665cf..301676d7d1 100644 --- a/zerver/lib/types.py +++ b/zerver/lib/types.py @@ -281,3 +281,4 @@ class GroupPermissionSetting: allow_owners_group: bool allow_nobody_group: bool default_group_name: str + default_for_system_groups: Optional[str] = None diff --git a/zerver/lib/user_groups.py b/zerver/lib/user_groups.py index 7132abcbc1..b70931c344 100644 --- a/zerver/lib/user_groups.py +++ b/zerver/lib/user_groups.py @@ -226,6 +226,31 @@ def get_recursive_subgroups_for_groups(user_group_ids: List[int]) -> List[int]: return list(recursive_subgroups.values_list("id", flat=True)) +def get_role_based_system_groups_dict(realm: Realm) -> Dict[str, UserGroup]: + system_groups = UserGroup.objects.filter(realm=realm, is_system_group=True) + system_groups_name_dict = {} + for group in system_groups: + system_groups_name_dict[group.name] = group + + return system_groups_name_dict + + +def set_defaults_for_group_settings( + user_group: UserGroup, + system_groups_name_dict: Dict[str, UserGroup], +) -> UserGroup: + for setting_name, permission_config in UserGroup.GROUP_PERMISSION_SETTINGS.items(): + if user_group.is_system_group and permission_config.default_for_system_groups is not None: + default_group_name = permission_config.default_for_system_groups + else: + default_group_name = permission_config.default_group_name + + default_group = system_groups_name_dict[default_group_name] + setattr(user_group, setting_name, default_group) + + return user_group + + @transaction.atomic(savepoint=False) def create_system_user_groups_for_realm(realm: Realm) -> Dict[int, UserGroup]: """Any changes to this function likely require a migration to adjust @@ -233,6 +258,12 @@ def create_system_user_groups_for_realm(realm: Realm) -> Dict[int, UserGroup]: which is a copy of this function from when we introduced system groups. """ role_system_groups_dict: Dict[int, UserGroup] = {} + + # This value will be used to set the temporary initial value for different + # settings since we can only set them to the correct values after the groups + # are created. + initial_group_setting_value = -1 + for role in UserGroup.SYSTEM_USER_GROUP_ROLE_MAP: user_group_params = UserGroup.SYSTEM_USER_GROUP_ROLE_MAP[role] user_group = UserGroup( @@ -240,6 +271,7 @@ def create_system_user_groups_for_realm(realm: Realm) -> Dict[int, UserGroup]: description=user_group_params["description"], realm=realm, is_system_group=True, + can_mention_group_id=initial_group_setting_value, ) role_system_groups_dict[role] = user_group @@ -248,18 +280,21 @@ def create_system_user_groups_for_realm(realm: Realm) -> Dict[int, UserGroup]: description="Members of this organization, not including new accounts and guests", realm=realm, is_system_group=True, + can_mention_group_id=initial_group_setting_value, ) everyone_on_internet_system_group = UserGroup( name=UserGroup.EVERYONE_ON_INTERNET_GROUP_NAME, description="Everyone on the Internet", realm=realm, is_system_group=True, + can_mention_group_id=initial_group_setting_value, ) nobody_system_group = UserGroup( name=UserGroup.NOBODY_GROUP_NAME, description="Nobody", realm=realm, is_system_group=True, + can_mention_group_id=initial_group_setting_value, ) # Order of this list here is important to create correct GroupGroupMembership objects system_user_groups_list = [ @@ -275,6 +310,13 @@ def create_system_user_groups_for_realm(realm: Realm) -> Dict[int, UserGroup]: UserGroup.objects.bulk_create(system_user_groups_list) + groups_with_updated_settings = [] + system_groups_name_dict = get_role_based_system_groups_dict(realm) + for group in system_user_groups_list: + user_group = set_defaults_for_group_settings(group, system_groups_name_dict) + groups_with_updated_settings.append(group) + UserGroup.objects.bulk_update(groups_with_updated_settings, ["can_mention_group"]) + subgroup_objects = [] # "Nobody" system group is not a subgroup of any user group, since it is already empty. subgroup, remaining_groups = system_user_groups_list[1], system_user_groups_list[2:] diff --git a/zerver/migrations/0454_usergroup_can_mention_group.py b/zerver/migrations/0454_usergroup_can_mention_group.py new file mode 100644 index 0000000000..263a13b443 --- /dev/null +++ b/zerver/migrations/0454_usergroup_can_mention_group.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.1 on 2023-06-12 10:47 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("zerver", "0453_followed_topic_notifications"), + ] + + operations = [ + migrations.AddField( + model_name="usergroup", + name="can_mention_group", + field=models.ForeignKey( + null=True, on_delete=django.db.models.deletion.RESTRICT, to="zerver.usergroup" + ), + ), + ] diff --git a/zerver/migrations/0455_set_default_for_can_mention_group.py b/zerver/migrations/0455_set_default_for_can_mention_group.py new file mode 100644 index 0000000000..86f5ea9f60 --- /dev/null +++ b/zerver/migrations/0455_set_default_for_can_mention_group.py @@ -0,0 +1,44 @@ +# Generated by Django 4.2.1 on 2023-06-12 10:47 + +from django.db import migrations +from django.db.backends.base.schema import BaseDatabaseSchemaEditor +from django.db.migrations.state import StateApps + + +def set_default_value_for_can_mention_group( + apps: StateApps, schema_editor: BaseDatabaseSchemaEditor +) -> None: + Realm = apps.get_model("zerver", "Realm") + UserGroup = apps.get_model("zerver", "UserGroup") + + groups_to_update = [] + for realm in Realm.objects.all(): + # The default value of `can_mention_group` is everyone group for + # all groups except role-based system groups. For role-based system + # groups, we set the value of `can_mention_group` to nobody group. + nobody_group = UserGroup.objects.get(name="@role:nobody", realm=realm, is_system_group=True) + everyone_group = UserGroup.objects.get( + name="@role:everyone", realm=realm, is_system_group=True + ) + for group in UserGroup.objects.filter(realm=realm): + if group.is_system_group: + group.can_mention_group = nobody_group + else: + group.can_mention_group = everyone_group + groups_to_update.append(group) + + UserGroup.objects.bulk_update(groups_to_update, ["can_mention_group"]) + + +class Migration(migrations.Migration): + dependencies = [ + ("zerver", "0454_usergroup_can_mention_group"), + ] + + operations = [ + migrations.RunPython( + set_default_value_for_can_mention_group, + elidable=True, + reverse_code=migrations.RunPython.noop, + ) + ] diff --git a/zerver/migrations/0456_alter_usergroup_can_mention_group.py b/zerver/migrations/0456_alter_usergroup_can_mention_group.py new file mode 100644 index 0000000000..0d07bdc81d --- /dev/null +++ b/zerver/migrations/0456_alter_usergroup_can_mention_group.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.1 on 2023-06-12 10:58 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("zerver", "0455_set_default_for_can_mention_group"), + ] + + operations = [ + migrations.AlterField( + model_name="usergroup", + name="can_mention_group", + field=models.ForeignKey( + on_delete=django.db.models.deletion.RESTRICT, to="zerver.usergroup" + ), + ), + ] diff --git a/zerver/models.py b/zerver/models.py index 64388397b3..e20564c70d 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -2256,6 +2256,8 @@ class UserGroup(models.Model): # type: ignore[django-manager-missing] # django- description = models.TextField(default="") is_system_group = models.BooleanField(default=False) + can_mention_group = models.ForeignKey("self", on_delete=models.RESTRICT) + # Names for system groups. FULL_MEMBERS_GROUP_NAME = "@role:fullmembers" EVERYONE_ON_INTERNET_GROUP_NAME = "@role:internet" @@ -2292,6 +2294,17 @@ class UserGroup(models.Model): # type: ignore[django-manager-missing] # django- }, } + GROUP_PERMISSION_SETTINGS = { + "can_mention_group": GroupPermissionSetting( + require_system_group=False, + allow_internet_group=False, + allow_owners_group=False, + allow_nobody_group=True, + default_group_name=EVERYONE_GROUP_NAME, + default_for_system_groups=NOBODY_GROUP_NAME, + ), + } + class Meta: unique_together = (("realm", "name"),) diff --git a/zerver/openapi/curl_param_value_generators.py b/zerver/openapi/curl_param_value_generators.py index e52f36f0c9..b4b892dd77 100644 --- a/zerver/openapi/curl_param_value_generators.py +++ b/zerver/openapi/curl_param_value_generators.py @@ -263,7 +263,9 @@ def create_user_group_data() -> Dict[str, object]: ["/user_groups/{user_group_id}:patch", "/user_groups/{user_group_id}:delete"] ) def get_temp_user_group_id() -> Dict[str, object]: - user_group, _ = UserGroup.objects.get_or_create(name="temp", realm=get_realm("zulip")) + user_group, _ = UserGroup.objects.get_or_create( + name="temp", realm=get_realm("zulip"), can_mention_group_id=11 + ) return { "user_group_id": user_group.id, } diff --git a/zerver/tests/test_import_export.py b/zerver/tests/test_import_export.py index ddaf830652..23cabfed55 100644 --- a/zerver/tests/test_import_export.py +++ b/zerver/tests/test_import_export.py @@ -1231,6 +1231,11 @@ class RealmImportExportTest(ExportFile): direct_subgroup_names = {group.name for group in direct_subgroups} return direct_subgroup_names + @getter + def get_user_group_can_mention_group_setting(r: Realm) -> Set[str]: + user_group = UserGroup.objects.get(realm=r, name="hamletcharacters") + return user_group.can_mention_group.name + # test botstoragedata and botconfigdata @getter def get_botstoragedata(r: Realm) -> Dict[str, object]: diff --git a/zerver/tests/test_user_groups.py b/zerver/tests/test_user_groups.py index 56ae5ec7f0..0a6d13ea88 100644 --- a/zerver/tests/test_user_groups.py +++ b/zerver/tests/test_user_groups.py @@ -240,6 +240,13 @@ class UserGroupAPITestCase(UserGroupTestCase): self.assert_json_success(result) self.assert_length(UserGroup.objects.filter(realm=hamlet.realm), 10) + # Check default value of can_mention_group setting. + everyone_system_group = UserGroup.objects.get( + name="@role:everyone", realm=hamlet.realm, is_system_group=True + ) + support_group = UserGroup.objects.get(name="support", realm=hamlet.realm) + self.assertEqual(support_group.can_mention_group, everyone_system_group) + # Test invalid member error params = { "name": "backend",