diff --git a/api_docs/changelog.md b/api_docs/changelog.md index b4b649da4e..0f6ff870b6 100644 --- a/api_docs/changelog.md +++ b/api_docs/changelog.md @@ -20,6 +20,17 @@ format used by the Zulip server that they are interacting with. ## Changes in Zulip 8.0 +**Feature level 225** + +* `PATCH /realm`, [`POST /register`](/api/register-queue), + [`GET /events`](/api/get-events): Added `can_access_all_users_group_id` + realm setting, which is the ID of the user group whose members can + access all the users in the oragnization. + +* [`POST /register`](/api/register-queue): Added `allowed_system_groups` + field to configuration data object of permission settings passed in + `server_supported_permission_settings`. + **Feature level 224** * [`GET /events`](/api/get-events), [`GET /messages`](/api/get-messages), diff --git a/version.py b/version.py index c04610ef21..da8a08bcce 100644 --- a/version.py +++ b/version.py @@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.9.3" # Changes should be accompanied by documentation explaining what the # new level means in api_docs/changelog.md, as well as "**Changes**" # entries in the endpoint's documentation in `zulip.yaml`. -API_FEATURE_LEVEL = 224 +API_FEATURE_LEVEL = 225 # 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/zerver/lib/event_schema.py b/zerver/lib/event_schema.py index 2abd77a73c..c0f652d721 100644 --- a/zerver/lib/event_schema.py +++ b/zerver/lib/event_schema.py @@ -991,7 +991,8 @@ night_logo_data = DictType( ) group_setting_update_data_type = DictType( - required_keys=[], optional_keys=[("create_multiuse_invite_group", int)] + required_keys=[], + optional_keys=[("create_multiuse_invite_group", int), ("can_access_all_users_group", int)], ) update_dict_data = UnionType( diff --git a/zerver/lib/types.py b/zerver/lib/types.py index baea1af42e..b5f59004e7 100644 --- a/zerver/lib/types.py +++ b/zerver/lib/types.py @@ -1,5 +1,5 @@ import datetime -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Any, Callable, Dict, List, Optional, Tuple, TypedDict, TypeVar, Union from django_stubs_ext import StrPromise @@ -291,6 +291,7 @@ class GroupPermissionSetting: default_group_name: str id_field_name: str default_for_system_groups: Optional[str] = None + allowed_system_groups: List[str] = field(default_factory=list) @dataclass diff --git a/zerver/lib/user_groups.py b/zerver/lib/user_groups.py index 5a2033b472..072aaec09a 100644 --- a/zerver/lib/user_groups.py +++ b/zerver/lib/user_groups.py @@ -215,6 +215,16 @@ def access_user_group_for_setting( ) ) + if ( + permission_configuration.allowed_system_groups + and user_group.name not in permission_configuration.allowed_system_groups + ): + raise JsonableError( + _("'{setting_name}' setting cannot be set to '{group_name}' group.").format( + setting_name=setting_name, group_name=user_group.name + ) + ) + return user_group diff --git a/zerver/migrations/0487_realm_can_access_all_users_group.py b/zerver/migrations/0487_realm_can_access_all_users_group.py new file mode 100644 index 0000000000..f6d1efbacf --- /dev/null +++ b/zerver/migrations/0487_realm_can_access_all_users_group.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1.7 on 2023-03-23 14:40 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("zerver", "0486_clear_old_data_for_unused_usermessage_flags"), + ] + + operations = [ + migrations.AddField( + model_name="realm", + name="can_access_all_users_group", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.RESTRICT, + related_name="+", + to="zerver.usergroup", + ), + ), + ] diff --git a/zerver/migrations/0488_set_default_value_for_can_access_all_users_group.py b/zerver/migrations/0488_set_default_value_for_can_access_all_users_group.py new file mode 100644 index 0000000000..6e624814bb --- /dev/null +++ b/zerver/migrations/0488_set_default_value_for_can_access_all_users_group.py @@ -0,0 +1,35 @@ +# Generated by Django 4.2.5 on 2023-09-21 14:00 + +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_default_value_for_can_access_all_users_group( + apps: StateApps, schema_editor: BaseDatabaseSchemaEditor +) -> None: + Realm = apps.get_model("zerver", "Realm") + UserGroup = apps.get_model("zerver", "UserGroup") + + EVERYONE_GROUP_NAME = "role:everyone" + + Realm.objects.filter(can_access_all_users_group=None).update( + can_access_all_users_group=UserGroup.objects.filter( + name=EVERYONE_GROUP_NAME, realm=OuterRef("id"), is_system_group=True + ).values("pk") + ) + + +class Migration(migrations.Migration): + dependencies = [ + ("zerver", "0487_realm_can_access_all_users_group"), + ] + + operations = [ + migrations.RunPython( + set_default_value_for_can_access_all_users_group, + elidable=True, + reverse_code=migrations.RunPython.noop, + ), + ] diff --git a/zerver/migrations/0489_alter_realm_can_access_all_users_group.py b/zerver/migrations/0489_alter_realm_can_access_all_users_group.py new file mode 100644 index 0000000000..980146d749 --- /dev/null +++ b/zerver/migrations/0489_alter_realm_can_access_all_users_group.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.5 on 2023-09-21 14:07 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("zerver", "0488_set_default_value_for_can_access_all_users_group"), + ] + + operations = [ + migrations.AlterField( + model_name="realm", + name="can_access_all_users_group", + field=models.ForeignKey( + on_delete=django.db.models.deletion.RESTRICT, + related_name="+", + to="zerver.usergroup", + ), + ), + ] diff --git a/zerver/models.py b/zerver/models.py index 05885499cb..173654b59b 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -445,6 +445,14 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub "UserGroup", on_delete=models.RESTRICT, related_name="+" ) + # on_delete field here is set to RESTRICT because we don't want to allow + # deleting a user group in case it is referenced by this setting. + # We are not using PROTECT since we want to allow deletion of user groups + # when realm itself is deleted. + can_access_all_users_group = 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=POLICY_MEMBERS_ONLY) @@ -814,6 +822,16 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub default_group_name=SystemGroups.ADMINISTRATORS, id_field_name="create_multiuse_invite_group_id", ), + can_access_all_users_group=GroupPermissionSetting( + require_system_group=True, + allow_internet_group=False, + allow_owners_group=False, + allow_nobody_group=False, + allow_everyone_group=True, + default_group_name=SystemGroups.EVERYONE, + id_field_name="can_access_all_users_group_id", + allowed_system_groups=[SystemGroups.EVERYONE], + ), ) DIGEST_WEEKDAY_VALUES = [0, 1, 2, 3, 4, 5, 6] diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index f481721aad..120a2299bf 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -4543,6 +4543,16 @@ paths: guest users to prominently highlight their status. **Changes**: New in Zulip 8.0 (feature level 216). + can_access_all_users_group: + type: integer + description: | + The ID of the [user group](/api/get-user-groups) whose members + are allowed to access all users in the organization. + + This setting can currently only be set to `"role:everyone"` + system group. + + **Changes**: New in Zulip 8.0 (feature level 225). additionalProperties: false example: { @@ -14356,6 +14366,16 @@ paths: guest users to prominently highlight their status. **Changes**: New in Zulip 8.0 (feature level 216). + realm_can_access_all_users_group: + type: integer + description: | + The ID of the [user group](/api/get-user-groups) whose members + are allowed to access all users in the organization. + + This setting can currently only be set to `"role:members"` + and `"role:everyone"` system groups. + + **Changes**: New in Zulip 8.0 (feature level 225). zulip_plan_is_not_limited: type: boolean description: | @@ -19422,6 +19442,18 @@ components: Name of the default group for the setting for system groups. This is non-null only for group-level settings. + allowed_system_groups: + type: array + description: | + An array of names of system groups to which the setting can + be set to. + + If the list is empty, the setting can be set to system groups + based on the other boolean fields. + + **Changes**: New in Zulip 8.0 (feature level 225). + items: + type: string User: allOf: - $ref: "#/components/schemas/UserBase" diff --git a/zerver/tests/test_home.py b/zerver/tests/test_home.py index 2df8468382..d401e8cec6 100644 --- a/zerver/tests/test_home.py +++ b/zerver/tests/test_home.py @@ -115,6 +115,7 @@ class HomeTest(ZulipTestCase): "realm_bot_creation_policy", "realm_bot_domain", "realm_bots", + "realm_can_access_all_users_group", "realm_create_multiuse_invite_group", "realm_create_private_stream_policy", "realm_create_public_stream_policy", diff --git a/zerver/tests/test_realm.py b/zerver/tests/test_realm.py index f86030a9bb..5c8a321fda 100644 --- a/zerver/tests/test_realm.py +++ b/zerver/tests/test_realm.py @@ -1286,6 +1286,11 @@ class RealmAPITest(ZulipTestCase): user_group.name == SystemGroups.OWNERS and not setting_permission_configuration.allow_owners_group ) + or ( + setting_permission_configuration.allowed_system_groups + and user_group.name + not in setting_permission_configuration.allowed_system_groups + ) ): value = orjson.dumps(user_group.id).decode() diff --git a/zerver/views/realm.py b/zerver/views/realm.py index cad1c8969e..8707c9f78e 100644 --- a/zerver/views/realm.py +++ b/zerver/views/realm.py @@ -175,6 +175,9 @@ def update_realm( default=None, ), enable_guest_user_indicator: Optional[bool] = REQ(json_validator=check_bool, default=None), + can_access_all_users_group_id: Optional[int] = REQ( + "can_access_all_users_group", json_validator=check_int, default=None + ), ) -> HttpResponse: realm = user_profile.realm