diff --git a/api_docs/changelog.md b/api_docs/changelog.md index 34aff18150..0d2e98a67b 100644 --- a/api_docs/changelog.md +++ b/api_docs/changelog.md @@ -20,6 +20,13 @@ format used by the Zulip server that they are interacting with. ## Changes in Zulip 10.0 +**Feature level 299** + +* `PATCH /realm`, [`POST /register`](/api/register-queue), + [`GET /events`](/api/get-events): Added `can_create_groups` + realm setting, which is a [group-setting value](/api/group-setting-values) + describing the set of users with permission to create user groups. + **Feature level 298** * [`POST /user_groups/{user_group_id}/deactivate`](/api/deactivate-user-group): diff --git a/version.py b/version.py index 32a6075a26..f130158459 100644 --- a/version.py +++ b/version.py @@ -34,7 +34,7 @@ DESKTOP_WARNING_VERSION = "5.9.3" # new level means in api_docs/changelog.md, as well as "**Changes**" # entries in the endpoint's documentation in `zulip.yaml`. -API_FEATURE_LEVEL = 298 # Last bumped for CANNOT_DEACTIVATE_GROUP_IN_USE error. +API_FEATURE_LEVEL = 299 # Last bumped for org level group create/manage permissions. # Bump the minor PROVISION_VERSION to indicate that folks should provision # only when going from an old version of the code to a newer version. Bump diff --git a/web/src/settings_org.js b/web/src/settings_org.js index 45d8e6d9f9..53faca79c8 100644 --- a/web/src/settings_org.js +++ b/web/src/settings_org.js @@ -843,6 +843,12 @@ export function set_up_dropdown_widget_for_realm_group_settings() { ) { dropdown_list_item_click_callback = check_disable_message_delete_limit_setting_dropdown; } + + if (setting_name === "can_create_groups") { + // Temporarily skip this setting until further commits + // where this setting will be ready to use. + continue; + } set_up_dropdown_widget( "realm_" + setting_name, get_setting_options, diff --git a/zerver/lib/event_schema.py b/zerver/lib/event_schema.py index f9f6a06e61..3b95855880 100644 --- a/zerver/lib/event_schema.py +++ b/zerver/lib/event_schema.py @@ -1074,6 +1074,7 @@ group_setting_update_data_type = DictType( optional_keys=[ ("create_multiuse_invite_group", int), ("can_access_all_users_group", int), + ("can_create_groups", group_setting_type), ("can_create_public_channel_group", group_setting_type), ("can_create_private_channel_group", group_setting_type), ("can_create_web_public_channel_group", group_setting_type), diff --git a/zerver/migrations/0588_realm_add_can_create_groups.py b/zerver/migrations/0588_realm_add_can_create_groups.py new file mode 100644 index 0000000000..e769c864bc --- /dev/null +++ b/zerver/migrations/0588_realm_add_can_create_groups.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.8 on 2024-09-04 10:30 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("zerver", "0587_savedsnippet"), + ] + + operations = [ + migrations.AddField( + model_name="realm", + name="can_create_groups", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.RESTRICT, + related_name="+", + to="zerver.usergroup", + ), + ), + ] diff --git a/zerver/migrations/0589_set_can_create_groups.py b/zerver/migrations/0589_set_can_create_groups.py new file mode 100644 index 0000000000..207219edc8 --- /dev/null +++ b/zerver/migrations/0589_set_can_create_groups.py @@ -0,0 +1,53 @@ +# Generated by Django 5.0.8 on 2024-09-05 06:11 + +from django.db import migrations +from django.db.backends.base.schema import BaseDatabaseSchemaEditor +from django.db.migrations.state import StateApps +from django.db.models import OuterRef + + +def set_can_create_groups_for_existing_realms( + apps: StateApps, schema_editor: BaseDatabaseSchemaEditor +) -> None: + Realm = apps.get_model("zerver", "Realm") + NamedUserGroup = apps.get_model("zerver", "NamedUserGroup") + + MEMBERS_ONLY = 1 + ADMINS_ONLY = 2 + FULL_MEMBERS_ONLY = 3 + MODERATORS_ONLY = 4 + + Realm.objects.filter(can_create_groups=None, user_group_edit_policy=MEMBERS_ONLY).update( + can_create_groups=NamedUserGroup.objects.filter( + name="role:members", realm=OuterRef("id"), is_system_group=True + ).values("pk") + ) + Realm.objects.filter(can_create_groups=None, user_group_edit_policy=ADMINS_ONLY).update( + can_create_groups=NamedUserGroup.objects.filter( + name="role:administrators", realm=OuterRef("id"), is_system_group=True + ).values("pk") + ) + Realm.objects.filter(can_create_groups=None, user_group_edit_policy=FULL_MEMBERS_ONLY).update( + can_create_groups=NamedUserGroup.objects.filter( + name="role:fullmembers", realm=OuterRef("id"), is_system_group=True + ).values("pk") + ) + Realm.objects.filter(can_create_groups=None, user_group_edit_policy=MODERATORS_ONLY).update( + can_create_groups=NamedUserGroup.objects.filter( + name="role:moderators", realm=OuterRef("id"), is_system_group=True + ).values("pk") + ) + + +class Migration(migrations.Migration): + dependencies = [ + ("zerver", "0588_realm_add_can_create_groups"), + ] + + operations = [ + migrations.RunPython( + set_can_create_groups_for_existing_realms, + elidable=True, + reverse_code=migrations.RunPython.noop, + ) + ] diff --git a/zerver/migrations/0590_alter_realm_can_create_groups.py b/zerver/migrations/0590_alter_realm_can_create_groups.py new file mode 100644 index 0000000000..9f96f8c113 --- /dev/null +++ b/zerver/migrations/0590_alter_realm_can_create_groups.py @@ -0,0 +1,21 @@ +# Generated by Django 5.0.8 on 2024-09-05 08:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("zerver", "0589_set_can_create_groups"), + ] + + operations = [ + migrations.AlterField( + model_name="realm", + name="can_create_groups", + field=models.ForeignKey( + on_delete=models.deletion.RESTRICT, + related_name="+", + to="zerver.usergroup", + ), + ), + ] diff --git a/zerver/models/realms.py b/zerver/models/realms.py index 91950000ba..ae8183d2ee 100644 --- a/zerver/models/realms.py +++ b/zerver/models/realms.py @@ -341,6 +341,9 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub "UserGroup", on_delete=models.RESTRICT, related_name="+" ) + # UserGroup which is allowed to create groups. + can_create_groups = models.ForeignKey("UserGroup", on_delete=models.RESTRICT, related_name="+") + # Who in the organization is allowed to invite other users to streams. invite_to_stream_policy = models.PositiveSmallIntegerField( default=CommonPolicyEnum.MEMBERS_ONLY @@ -768,6 +771,15 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub SystemGroups.NOBODY, ], ), + can_create_groups=GroupPermissionSetting( + require_system_group=False, + allow_internet_group=False, + allow_owners_group=True, + allow_nobody_group=False, + allow_everyone_group=False, + default_group_name=SystemGroups.MEMBERS, + id_field_name="can_create_groups_id", + ), ) REALM_PERMISSION_GROUP_SETTINGS_WITH_NEW_API_FORMAT = [ @@ -778,6 +790,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub "can_delete_own_message_group", "direct_message_initiator_group", "direct_message_permission_group", + "can_create_groups", ] DIGEST_WEEKDAY_VALUES = [0, 1, 2, 3, 4, 5, 6] @@ -1153,6 +1166,8 @@ def get_realm_with_settings(realm_id: int) -> Realm: "direct_message_initiator_group__named_user_group", "direct_message_permission_group", "direct_message_permission_group__named_user_group", + "can_create_groups", + "can_create_groups__named_user_group", "new_stream_announcements_stream", "signup_announcements_stream", "zulip_update_announcements_stream", diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index f50a0223e4..bf0d953260 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -4296,6 +4296,20 @@ paths: `"role:internet"` and `"role:owners"`. **Changes**: New in Zulip 8.0 (feature level 209). + can_create_groups: + allOf: + - $ref: "#/components/schemas/GroupSettingValue" + - description: | + A [group-setting value](/api/group-setting-values) defining + the set of users who have permission to create user + groups in this organization. + + This setting cannot be set to `"role:internet"`, `"role:everyone"`, + and `"role:nobody"` system groups. + + **Changes**: New in Zulip 10.0 (feature level 299). Previously + `user_group_edit_policy` field used to control the permission + to create user groups. can_create_public_channel_group: allOf: - $ref: "#/components/schemas/GroupSettingValue" @@ -16050,6 +16064,20 @@ paths: The [policy](/api/roles-and-permissions#permission-levels) for which users can create bot users in this organization. + realm_can_create_groups: + allOf: + - $ref: "#/components/schemas/GroupSettingValue" + - description: | + A [group-setting value](/api/group-setting-values) defining + the set of users who have permission to create user + groups in this organization. + + This setting cannot be set to `"role:internet"`, `"role:everyone"`, + and `"role:nobody"` system groups. + + **Changes**: New in Zulip 10.0 (feature level 299). Previously + `realm_user_group_edit_policy` field used to control the + permission to create user groups. realm_can_create_public_channel_group: allOf: - $ref: "#/components/schemas/GroupSettingValue" diff --git a/zerver/tests/test_home.py b/zerver/tests/test_home.py index 2cf8041a5f..e4fe51ec5c 100644 --- a/zerver/tests/test_home.py +++ b/zerver/tests/test_home.py @@ -130,6 +130,7 @@ class HomeTest(ZulipTestCase): "realm_bot_domain", "realm_bots", "realm_can_access_all_users_group", + "realm_can_create_groups", "realm_can_create_private_channel_group", "realm_can_create_public_channel_group", "realm_can_create_web_public_channel_group", diff --git a/zerver/views/realm.py b/zerver/views/realm.py index 831b91b9cb..2c4074ac06 100644 --- a/zerver/views/realm.py +++ b/zerver/views/realm.py @@ -145,6 +145,7 @@ def update_realm( can_create_web_public_channel_group: Json[GroupSettingChangeRequest] | None = None, direct_message_initiator_group: Json[GroupSettingChangeRequest] | None = None, direct_message_permission_group: Json[GroupSettingChangeRequest] | None = None, + can_create_groups: Json[GroupSettingChangeRequest] | None = None, invite_to_stream_policy: Json[CommonPolicyEnum] | None = None, move_messages_between_streams_policy: Json[MoveMessagesBetweenStreamsPolicyEnum] | None = None, user_group_edit_policy: Json[CommonPolicyEnum] | None = None,