From 03220ba456dc401e1e6ad2f1f47b803f13a6d886 Mon Sep 17 00:00:00 2001 From: Ujjawal Modi Date: Mon, 21 Aug 2023 15:36:41 +0530 Subject: [PATCH] user_groups: Add can_manage_group field in database. This commit adds a new group level setting can_manage_group for configuring who can manage a group. This commit only adds the field in database and make changes to automatically create single user groups corresponsing to acting user which will be the default value for this setting. Fixes part of #25928. --- zerver/lib/import_realm.py | 3 +- zerver/lib/user_groups.py | 9 ++- .../0570_namedusergroup_can_manage_group.py | 23 ++++++++ .../0571_set_default_for_can_manage_group.py | 55 +++++++++++++++++++ .../0572_alter_usergroup_can_manage_group.py | 22 ++++++++ zerver/models/groups.py | 11 ++++ zerver/openapi/curl_param_value_generators.py | 1 + zerver/tests/test_user_groups.py | 6 +- 8 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 zerver/migrations/0570_namedusergroup_can_manage_group.py create mode 100644 zerver/migrations/0571_set_default_for_can_manage_group.py create mode 100644 zerver/migrations/0572_alter_usergroup_can_manage_group.py diff --git a/zerver/lib/import_realm.py b/zerver/lib/import_realm.py index 50ad6dd5f6..62d257e111 100644 --- a/zerver/lib/import_realm.py +++ b/zerver/lib/import_realm.py @@ -754,6 +754,7 @@ def bulk_import_named_user_groups(data: TableData) -> None: group["name"], group["description"], group["is_system_group"], + group["can_manage_group_id"], group["can_mention_group_id"], ) for group in data["zerver_namedusergroup"] @@ -761,7 +762,7 @@ def bulk_import_named_user_groups(data: TableData) -> None: query = SQL( """ - INSERT INTO zerver_namedusergroup (usergroup_ptr_id, realm_id, name, description, is_system_group, can_mention_group_id) + INSERT INTO zerver_namedusergroup (usergroup_ptr_id, realm_id, name, description, is_system_group, can_manage_group_id, can_mention_group_id) VALUES %s """ ) diff --git a/zerver/lib/user_groups.py b/zerver/lib/user_groups.py index 9440427681..fb4373d72e 100644 --- a/zerver/lib/user_groups.py +++ b/zerver/lib/user_groups.py @@ -598,19 +598,20 @@ def bulk_create_system_user_groups(groups: list[dict[str, str]], realm: Realm) - user_group_ids = [id for (id,) in cursor.fetchall()] rows = [ - SQL("({},{},{},{},{},{})").format( + SQL("({},{},{},{},{},{},{})").format( Literal(user_group_ids[idx]), Literal(realm.id), Literal(group["name"]), Literal(group["description"]), Literal(True), Literal(initial_group_setting_value), + Literal(initial_group_setting_value), ) for idx, group in enumerate(groups) ] query = SQL( """ - INSERT INTO zerver_namedusergroup (usergroup_ptr_id, realm_id, name, description, is_system_group, can_mention_group_id) + INSERT INTO zerver_namedusergroup (usergroup_ptr_id, realm_id, name, description, is_system_group, can_manage_group_id, can_mention_group_id) VALUES {rows} """ ).format(rows=SQL(", ").join(rows)) @@ -691,7 +692,9 @@ def create_system_user_groups_for_realm(realm: Realm) -> dict[int, NamedUserGrou for group in system_user_groups_list: user_group = set_defaults_for_group_settings(group, {}, system_groups_name_dict) groups_with_updated_settings.append(user_group) - NamedUserGroup.objects.bulk_update(groups_with_updated_settings, ["can_mention_group"]) + NamedUserGroup.objects.bulk_update( + groups_with_updated_settings, ["can_manage_group", "can_mention_group"] + ) subgroup_objects: list[GroupGroupMembership] = [] # "Nobody" system group is not a subgroup of any user group, since it is already empty. diff --git a/zerver/migrations/0570_namedusergroup_can_manage_group.py b/zerver/migrations/0570_namedusergroup_can_manage_group.py new file mode 100644 index 0000000000..90a55ceb1b --- /dev/null +++ b/zerver/migrations/0570_namedusergroup_can_manage_group.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.2 on 2023-07-15 16:28 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("zerver", "0569_remove_userprofile_tutorial_status"), + ] + + operations = [ + migrations.AddField( + model_name="namedusergroup", + name="can_manage_group", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.RESTRICT, + related_name="+", + to="zerver.usergroup", + ), + ), + ] diff --git a/zerver/migrations/0571_set_default_for_can_manage_group.py b/zerver/migrations/0571_set_default_for_can_manage_group.py new file mode 100644 index 0000000000..3a34399466 --- /dev/null +++ b/zerver/migrations/0571_set_default_for_can_manage_group.py @@ -0,0 +1,55 @@ +# Generated by Django 4.2.2 on 2023-07-15 17:08 + +from django.db import migrations, transaction +from django.db.backends.base.schema import BaseDatabaseSchemaEditor +from django.db.migrations.state import StateApps +from django.db.models import Max, Min, OuterRef + + +def set_default_value_for_can_manage_group( + apps: StateApps, schema_editor: BaseDatabaseSchemaEditor +) -> None: + NamedUserGroup = apps.get_model("zerver", "NamedUserGroup") + + BATCH_SIZE = 1000 + max_id = NamedUserGroup.objects.filter(can_manage_group=None).aggregate(Max("id"))["id__max"] + + if max_id is None: + # Do nothing if there are no user groups on the server. + return + + lower_bound = NamedUserGroup.objects.filter(can_manage_group=None).aggregate(Min("id"))[ + "id__min" + ] + while lower_bound <= max_id: + upper_bound = lower_bound + BATCH_SIZE - 1 + print(f"Processing batch {lower_bound} to {upper_bound} for NamedUserGroup") + + with transaction.atomic(): + NamedUserGroup.objects.filter( + id__range=(lower_bound, upper_bound), can_manage_group=None + ).update( + can_manage_group=NamedUserGroup.objects.filter( + name="role:nobody", + realm_for_sharding=OuterRef("realm_for_sharding"), + is_system_group=True, + ).values("pk") + ) + + lower_bound += BATCH_SIZE + + +class Migration(migrations.Migration): + atomic = False + + dependencies = [ + ("zerver", "0570_namedusergroup_can_manage_group"), + ] + + operations = [ + migrations.RunPython( + set_default_value_for_can_manage_group, + elidable=True, + reverse_code=migrations.RunPython.noop, + ) + ] diff --git a/zerver/migrations/0572_alter_usergroup_can_manage_group.py b/zerver/migrations/0572_alter_usergroup_can_manage_group.py new file mode 100644 index 0000000000..8a18eef833 --- /dev/null +++ b/zerver/migrations/0572_alter_usergroup_can_manage_group.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.2 on 2023-07-16 12:57 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("zerver", "0571_set_default_for_can_manage_group"), + ] + + operations = [ + migrations.AlterField( + model_name="namedusergroup", + name="can_manage_group", + field=models.ForeignKey( + on_delete=django.db.models.deletion.RESTRICT, + related_name="+", + to="zerver.usergroup", + ), + ), + ] diff --git a/zerver/models/groups.py b/zerver/models/groups.py index f300ea2ad0..3e95be594a 100644 --- a/zerver/models/groups.py +++ b/zerver/models/groups.py @@ -54,6 +54,7 @@ class NamedUserGroup(UserGroup): # type: ignore[django-manager-missing] # djang description = models.TextField(default="", db_column="description") is_system_group = models.BooleanField(default=False, db_column="is_system_group") + can_manage_group = models.ForeignKey(UserGroup, on_delete=models.RESTRICT, related_name="+") can_mention_group = models.ForeignKey( UserGroup, on_delete=models.RESTRICT, db_column="can_mention_group_id" ) @@ -87,6 +88,16 @@ class NamedUserGroup(UserGroup): # type: ignore[django-manager-missing] # djang } GROUP_PERMISSION_SETTINGS = { + "can_manage_group": GroupPermissionSetting( + require_system_group=False, + allow_internet_group=False, + allow_owners_group=True, + allow_nobody_group=True, + allow_everyone_group=False, + default_group_name=SystemGroups.NOBODY, + default_for_system_groups=SystemGroups.NOBODY, + id_field_name="can_manage_group_id", + ), "can_mention_group": GroupPermissionSetting( require_system_group=False, allow_internet_group=False, diff --git a/zerver/openapi/curl_param_value_generators.py b/zerver/openapi/curl_param_value_generators.py index 7a7def93b2..7ea97e2133 100644 --- a/zerver/openapi/curl_param_value_generators.py +++ b/zerver/openapi/curl_param_value_generators.py @@ -266,6 +266,7 @@ def get_temp_user_group_id() -> dict[str, object]: user_group, _ = NamedUserGroup.objects.get_or_create( name="temp", realm=get_realm("zulip"), + can_manage_group_id=11, can_mention_group_id=11, realm_for_sharding=get_realm("zulip"), ) diff --git a/zerver/tests/test_user_groups.py b/zerver/tests/test_user_groups.py index 35620b1b83..2bb1357547 100644 --- a/zerver/tests/test_user_groups.py +++ b/zerver/tests/test_user_groups.py @@ -343,11 +343,15 @@ class UserGroupAPITestCase(UserGroupTestCase): self.assert_json_success(result) self.assert_length(NamedUserGroup.objects.filter(realm=hamlet.realm), 10) - # Check default value of can_mention_group setting. + # Check default value of settings. everyone_system_group = NamedUserGroup.objects.get( name="role:everyone", realm=hamlet.realm, is_system_group=True ) + nobody_system_group = NamedUserGroup.objects.get( + name=SystemGroups.NOBODY, realm=hamlet.realm, is_system_group=True + ) support_group = NamedUserGroup.objects.get(name="support", realm=hamlet.realm) + self.assertEqual(support_group.can_manage_group, nobody_system_group.usergroup_ptr) self.assertEqual(support_group.can_mention_group, everyone_system_group.usergroup_ptr) # Test invalid member error