settings: Add `can_delete_own_message_group` realm setting.

Added `can_delete_message_group` realm setting to replace
`delete_own_message_policy` property.
This commit is contained in:
Vector73 2024-09-11 22:43:37 +05:30 committed by Tim Abbott
parent 26da3300ec
commit 28c7a04734
24 changed files with 264 additions and 140 deletions

View File

@ -20,6 +20,14 @@ format used by the Zulip server that they are interacting with.
## Changes in Zulip 10.0 ## Changes in Zulip 10.0
**Feature level 291**
* `PATCH /realm`, [`GET /events`](/api/get-events),
[`POST /register`](/api/register-queue):
Added `can_delete_own_message_group` realm setting which is a
[group-setting value](/api/group-setting-values) describing the set of users
with permission to delete the messages that they have sent in the organization.
**Feature level 290** **Feature level 290**
* [`POST /user_groups/{user_group_id}/deactivate`](/api/deactivate-user-group): * [`POST /user_groups/{user_group_id}/deactivate`](/api/deactivate-user-group):

View File

@ -34,7 +34,8 @@ DESKTOP_WARNING_VERSION = "5.9.3"
# new level means in api_docs/changelog.md, as well as "**Changes**" # new level means in api_docs/changelog.md, as well as "**Changes**"
# entries in the endpoint's documentation in `zulip.yaml`. # entries in the endpoint's documentation in `zulip.yaml`.
API_FEATURE_LEVEL = 290 # Last bumped for UserGroup.deactivated
API_FEATURE_LEVEL = 291 # Last bumped for can_delete_own_message_group
# Bump the minor PROVISION_VERSION to indicate that folks should provision # 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 # only when going from an old version of the code to a newer version. Bump

View File

@ -185,9 +185,7 @@ export function build_page() {
can_create_user_groups: settings_data.user_can_create_user_groups(), can_create_user_groups: settings_data.user_can_create_user_groups(),
policy_values: settings_config.common_policy_values, policy_values: settings_config.common_policy_values,
realm_can_delete_any_message_group: realm.realm_can_delete_any_message_group, realm_can_delete_any_message_group: realm.realm_can_delete_any_message_group,
realm_delete_own_message_policy: realm.realm_delete_own_message_policy, realm_can_delete_own_message_group: realm.realm_can_delete_own_message_group,
DELETE_OWN_MESSAGE_POLICY_ADMINS_ONLY:
settings_config.common_message_policy_values.by_admins_only.code,
...settings_org.get_organization_settings_options(), ...settings_org.get_organization_settings_options(),
demote_inactive_streams_values: settings_config.demote_inactive_streams_values, demote_inactive_streams_values: settings_config.demote_inactive_streams_values,
web_mark_read_on_scroll_policy_values: web_mark_read_on_scroll_policy_values:

View File

@ -210,6 +210,7 @@ export function dispatch_normal_event(event) {
avatar_changes_disabled: settings_account.update_avatar_change_display, avatar_changes_disabled: settings_account.update_avatar_change_display,
bot_creation_policy: settings_bots.update_bot_permissions_ui, bot_creation_policy: settings_bots.update_bot_permissions_ui,
can_delete_any_message_group: noop, can_delete_any_message_group: noop,
can_delete_own_message_group: noop,
create_multiuse_invite_group: noop, create_multiuse_invite_group: noop,
invite_to_stream_policy: noop, invite_to_stream_policy: noop,
default_code_block_language: noop, default_code_block_language: noop,

View File

@ -482,6 +482,7 @@ const dropdown_widget_map = new Map<string, DropdownWidget | null>([
["realm_can_create_private_channel_group", null], ["realm_can_create_private_channel_group", null],
["realm_can_create_web_public_channel_group", null], ["realm_can_create_web_public_channel_group", null],
["realm_can_delete_any_message_group", null], ["realm_can_delete_any_message_group", null],
["realm_can_delete_own_message_group", null],
["realm_direct_message_initiator_group", null], ["realm_direct_message_initiator_group", null],
["realm_direct_message_permission_group", null], ["realm_direct_message_permission_group", null],
]); ]);
@ -802,6 +803,7 @@ export function check_realm_settings_property_changed(elem: HTMLElement): boolea
case "realm_can_create_private_channel_group": case "realm_can_create_private_channel_group":
case "realm_can_create_web_public_channel_group": case "realm_can_create_web_public_channel_group":
case "realm_can_delete_any_message_group": case "realm_can_delete_any_message_group":
case "realm_can_delete_own_message_group":
case "realm_direct_message_initiator_group": case "realm_direct_message_initiator_group":
case "realm_direct_message_permission_group": case "realm_direct_message_permission_group":
proposed_val = get_dropdown_list_widget_setting_value($elem); proposed_val = get_dropdown_list_widget_setting_value($elem);
@ -1000,6 +1002,7 @@ export function populate_data_for_realm_settings_request(
"can_create_public_channel_group", "can_create_public_channel_group",
"can_create_web_public_channel_group", "can_create_web_public_channel_group",
"can_delete_any_message_group", "can_delete_any_message_group",
"can_delete_own_message_group",
"direct_message_initiator_group", "direct_message_initiator_group",
"direct_message_permission_group", "direct_message_permission_group",
]); ]);

View File

@ -212,7 +212,13 @@ export function user_can_delete_any_message(): boolean {
} }
export function user_can_delete_own_message(): boolean { export function user_can_delete_own_message(): boolean {
return user_has_permission(realm.realm_delete_own_message_policy); if (page_params.is_spectator) {
return false;
}
return user_groups.is_user_in_group(
realm.realm_can_delete_own_message_group,
current_user.user_id,
);
} }
export function should_mask_unread_count(sub_muted: boolean): boolean { export function should_mask_unread_count(sub_muted: boolean): boolean {

View File

@ -266,41 +266,19 @@ function message_delete_limit_setting_enabled() {
// should be enabled. The setting is disabled when every user // should be enabled. The setting is disabled when every user
// who is allowed to delete their own messages is also allowed // who is allowed to delete their own messages is also allowed
// to delete any message in the organization. // to delete any message in the organization.
const realm_delete_own_message_policy = Number.parseInt( const realm_can_delete_own_message_group_id =
$("#id_realm_delete_own_message_policy").val(), settings_components.get_dropdown_list_widget_setting_value(
10, $("#id_realm_can_delete_own_message_group"),
); );
const realm_can_delete_any_message_group_id = const realm_can_delete_any_message_group_id =
settings_components.get_dropdown_list_widget_setting_value( settings_components.get_dropdown_list_widget_setting_value(
$("#id_realm_can_delete_any_message_group"), $("#id_realm_can_delete_any_message_group"),
); );
const realm_can_delete_any_message_group_name = user_groups.get_user_group_from_id( const can_delete_any_message_subgroups = user_groups.get_recursive_subgroups(
realm_can_delete_any_message_group_id, user_groups.get_user_group_from_id(realm_can_delete_any_message_group_id),
).name; );
const common_message_policy_values = settings_config.common_message_policy_values; can_delete_any_message_subgroups.add(realm_can_delete_any_message_group_id);
return !can_delete_any_message_subgroups.has(realm_can_delete_own_message_group_id);
if (realm_delete_own_message_policy === common_message_policy_values.by_admins_only.code) {
return false;
}
if (realm_can_delete_any_message_group_name === "role:administrators") {
return true;
}
if (realm_delete_own_message_policy === common_message_policy_values.by_moderators_only.code) {
return false;
}
if (realm_can_delete_any_message_group_name === "role:moderators") {
return true;
}
if (realm_delete_own_message_policy === common_message_policy_values.by_full_members.code) {
return false;
}
if (realm_can_delete_any_message_group_name === "role:fullmembers") {
return true;
}
if (realm_delete_own_message_policy === common_message_policy_values.by_members.code) {
return false;
}
return true;
} }
function check_disable_message_delete_limit_setting_dropdown() { function check_disable_message_delete_limit_setting_dropdown() {
@ -318,11 +296,6 @@ function check_disable_message_delete_limit_setting_dropdown() {
} }
} }
function set_delete_own_message_policy_dropdown(setting_value) {
$("#id_realm_delete_own_message_policy").val(setting_value);
check_disable_message_delete_limit_setting_dropdown();
}
function set_msg_delete_limit_dropdown() { function set_msg_delete_limit_dropdown() {
settings_components.set_time_limit_setting("realm_message_content_delete_limit_seconds"); settings_components.set_time_limit_setting("realm_message_content_delete_limit_seconds");
} }
@ -488,8 +461,8 @@ function update_dependent_subsettings(property_name) {
case "realm_can_delete_any_message_group": case "realm_can_delete_any_message_group":
check_disable_message_delete_limit_setting_dropdown(); check_disable_message_delete_limit_setting_dropdown();
break; break;
case "realm_delete_own_message_policy": case "realm_can_delete_own_message_group":
set_delete_own_message_policy_dropdown(realm.realm_delete_own_message_policy); check_disable_message_delete_limit_setting_dropdown();
break; break;
case "realm_org_join_restrictions": case "realm_org_join_restrictions":
set_org_join_restrictions_dropdown(); set_org_join_restrictions_dropdown();
@ -542,6 +515,7 @@ export function discard_realm_property_element_changes(elem) {
case "realm_can_create_private_channel_group": case "realm_can_create_private_channel_group":
case "realm_can_create_web_public_channel_group": case "realm_can_create_web_public_channel_group":
case "realm_can_delete_any_message_group": case "realm_can_delete_any_message_group":
case "realm_can_delete_own_message_group":
settings_components.set_dropdown_list_widget_setting_value( settings_components.set_dropdown_list_widget_setting_value(
property_name, property_name,
property_value, property_value,
@ -861,7 +835,10 @@ export function set_up_dropdown_widget_for_realm_group_settings() {
if (setting_name === "direct_message_permission_group") { if (setting_name === "direct_message_permission_group") {
dropdown_list_item_click_callback = dropdown_list_item_click_callback =
check_disable_direct_message_initiator_group_dropdown; check_disable_direct_message_initiator_group_dropdown;
} else if (setting_name === "can_delete_any_message_group") { } else if (
setting_name === "can_delete_any_message_group" ||
setting_name === "can_delete_own_message_group"
) {
dropdown_list_item_click_callback = check_disable_message_delete_limit_setting_dropdown; dropdown_list_item_click_callback = check_disable_message_delete_limit_setting_dropdown;
} }
set_up_dropdown_widget( set_up_dropdown_widget(
@ -1044,7 +1021,6 @@ export function build_page() {
set_msg_move_limit_setting("realm_move_messages_within_stream_limit_seconds"); set_msg_move_limit_setting("realm_move_messages_within_stream_limit_seconds");
set_msg_move_limit_setting("realm_move_messages_between_streams_limit_seconds"); set_msg_move_limit_setting("realm_move_messages_between_streams_limit_seconds");
set_msg_delete_limit_dropdown(); set_msg_delete_limit_dropdown();
set_delete_own_message_policy_dropdown(realm.realm_delete_own_message_policy);
set_message_retention_setting_dropdown(); set_message_retention_setting_dropdown();
set_org_join_restrictions_dropdown(); set_org_join_restrictions_dropdown();
set_message_content_in_email_notifications_visibility(); set_message_content_in_email_notifications_visibility();
@ -1153,11 +1129,6 @@ export function build_page() {
); );
}); });
$("#id_realm_delete_own_message_policy").on("change", (e) => {
const setting_value = Number.parseInt($(e.target).val(), 10);
set_delete_own_message_policy_dropdown(setting_value);
});
$("#id_realm_org_join_restrictions").on("click", (e) => { $("#id_realm_org_join_restrictions").on("click", (e) => {
// This prevents the disappearance of modal when there are // This prevents the disappearance of modal when there are
// no allowed domains otherwise it gets closed due to // no allowed domains otherwise it gets closed due to
@ -1225,6 +1196,8 @@ export function build_page() {
}); });
} }
check_disable_message_delete_limit_setting_dropdown();
realm_icon.build_realm_icon_widget(upload_realm_logo_or_icon, null, true); realm_icon.build_realm_icon_widget(upload_realm_logo_or_icon, null, true);
if (realm.zulip_plan_is_not_limited) { if (realm.zulip_plan_is_not_limited) {
realm_logo.build_realm_logo_widget(upload_realm_logo_or_icon, false); realm_logo.build_realm_logo_widget(upload_realm_logo_or_icon, false);

View File

@ -269,6 +269,7 @@ const realm_schema = z.object({
realm_can_create_private_channel_group: z.number(), realm_can_create_private_channel_group: z.number(),
realm_can_create_web_public_channel_group: z.number(), realm_can_create_web_public_channel_group: z.number(),
realm_can_delete_any_message_group: z.number(), realm_can_delete_any_message_group: z.number(),
realm_can_delete_own_message_group: z.number(),
realm_create_multiuse_invite_group: z.number(), realm_create_multiuse_invite_group: z.number(),
realm_create_private_stream_policy: z.number(), realm_create_private_stream_policy: z.number(),
realm_date_created: z.number(), realm_date_created: z.number(),
@ -283,7 +284,6 @@ const realm_schema = z.object({
}), }),
), ),
realm_default_language: z.string(), realm_default_language: z.string(),
realm_delete_own_message_policy: z.number(),
realm_description: z.string(), realm_description: z.string(),
realm_digest_emails_enabled: NOT_TYPED_YET, realm_digest_emails_enabled: NOT_TYPED_YET,
realm_digest_weekday: NOT_TYPED_YET, realm_digest_weekday: NOT_TYPED_YET,

View File

@ -241,14 +241,10 @@
label=(t 'Who can delete any message') label=(t 'Who can delete any message')
value_type="number" }} value_type="number" }}
<div class="input-group"> {{> ../dropdown_widget_with_label
<label for="realm_delete_own_message_policy" class="settings-field-label"> widget_name="realm_can_delete_own_message_group"
{{t "Who can delete their own messages" }} label=(t 'Who can delete their own messages')
</label> value_type="number" }}
<select name="realm_delete_own_message_policy" id="id_realm_delete_own_message_policy" class="prop-element bootstrap-focus-style settings_select" data-setting-widget-type="number">
{{> dropdown_options_widget option_values=common_message_policy_values}}
</select>
</div>
<div class="input-group time-limit-setting"> <div class="input-group time-limit-setting">
<label for="realm_message_content_delete_limit_seconds" class="settings-field-label"> <label for="realm_message_content_delete_limit_seconds" class="settings-field-label">

View File

@ -5,7 +5,7 @@ const {strict: assert} = require("assert");
const {mock_esm, zrequire} = require("./lib/namespace"); const {mock_esm, zrequire} = require("./lib/namespace");
const {run_test} = require("./lib/test"); const {run_test} = require("./lib/test");
const $ = require("./lib/zjquery"); const $ = require("./lib/zjquery");
const {page_params, realm} = require("./lib/zpage_params"); const {page_params, realm, current_user} = require("./lib/zpage_params");
const {Filter} = zrequire("filter"); const {Filter} = zrequire("filter");
const {MessageList} = zrequire("message_list"); const {MessageList} = zrequire("message_list");
@ -150,6 +150,8 @@ test("my_message_all_actions", () => {
// Set page parameters. // Set page parameters.
set_page_params_no_edit_restrictions(); set_page_params_no_edit_restrictions();
realm.realm_can_delete_any_message_group = everyone.id; realm.realm_can_delete_any_message_group = everyone.id;
realm.realm_can_delete_own_message_group = everyone.id;
current_user.user_id = me.user_id;
// Get message with maximum permissions available // Get message with maximum permissions available
// Initialize message list // Initialize message list
const list = init_message_list(); const list = init_message_list();

View File

@ -237,9 +237,8 @@ test_realm_group_settings(
settings_data.user_can_delete_any_message, settings_data.user_can_delete_any_message,
); );
test_message_policy( test_realm_group_settings(
"user_can_delete_own_message", "realm_can_delete_own_message_group",
"realm_delete_own_message_policy",
settings_data.user_can_delete_own_message, settings_data.user_can_delete_own_message,
); );

View File

@ -1051,6 +1051,7 @@ group_setting_update_data_type = DictType(
("can_create_private_channel_group", group_setting_type), ("can_create_private_channel_group", group_setting_type),
("can_create_web_public_channel_group", group_setting_type), ("can_create_web_public_channel_group", group_setting_type),
("can_delete_any_message_group", group_setting_type), ("can_delete_any_message_group", group_setting_type),
("can_delete_own_message_group", group_setting_type),
("direct_message_initiator_group", group_setting_type), ("direct_message_initiator_group", group_setting_type),
("direct_message_permission_group", group_setting_type), ("direct_message_permission_group", group_setting_type),
], ],

View File

@ -34,7 +34,6 @@ def create_internal_realm() -> None:
# is changed later before the transaction is committed. # is changed later before the transaction is committed.
for permission_configuration in Realm.REALM_PERMISSION_GROUP_SETTINGS.values(): for permission_configuration in Realm.REALM_PERMISSION_GROUP_SETTINGS.values():
setattr(realm, permission_configuration.id_field_name, -1) setattr(realm, permission_configuration.id_field_name, -1)
realm.delete_own_message_policy = 1
realm.save() realm.save()
RealmAuditLog.objects.create( RealmAuditLog.objects.create(

View File

@ -0,0 +1,23 @@
# Generated by Django 5.0.7 on 2024-08-28 05:15
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("zerver", "0578_namedusergroup_deactivated"),
]
operations = [
migrations.AddField(
model_name="realm",
name="can_delete_own_message_group",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.RESTRICT,
related_name="+",
to="zerver.usergroup",
),
),
]

View File

@ -0,0 +1,46 @@
# 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
from django.db.models import OuterRef
def set_default_value_for_can_delete_own_message_group(
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
) -> None:
Realm = apps.get_model("zerver", "Realm")
NamedUserGroup = apps.get_model("zerver", "NamedUserGroup")
delete_own_message_policy_to_user_group = {
1: "role:members",
2: "role:administrators",
3: "role:fullmembers",
4: "role:moderators",
5: "role:everyone",
}
for id, user_group in delete_own_message_policy_to_user_group.items():
Realm.objects.filter(
can_delete_own_message_group=None, delete_own_message_policy=id
).update(
can_delete_own_message_group=NamedUserGroup.objects.filter(
name=user_group, realm=OuterRef("id"), is_system_group=True
).values("pk")
)
class Migration(migrations.Migration):
atomic = False
dependencies = [
("zerver", "0579_realm_can_delete_own_message_group"),
]
operations = [
migrations.RunPython(
set_default_value_for_can_delete_own_message_group,
elidable=True,
reverse_code=migrations.RunPython.noop,
)
]

View File

@ -0,0 +1,22 @@
# Generated by Django 5.0.7 on 2024-08-28 05:31
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("zerver", "0580_set_default_value_for_can_delete_own_message_group"),
]
operations = [
migrations.AlterField(
model_name="realm",
name="can_delete_own_message_group",
field=models.ForeignKey(
on_delete=django.db.models.deletion.RESTRICT,
related_name="+",
to="zerver.usergroup",
),
),
]

View File

@ -313,6 +313,11 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
"UserGroup", on_delete=models.RESTRICT, related_name="+" "UserGroup", on_delete=models.RESTRICT, related_name="+"
) )
# Who in the organization is allowed to delete their own message.
can_delete_own_message_group = models.ForeignKey(
"UserGroup", on_delete=models.RESTRICT, related_name="+"
)
# Who in the organization is allowed to delete messages they themselves sent. # Who in the organization is allowed to delete messages they themselves sent.
delete_own_message_policy = models.PositiveSmallIntegerField( delete_own_message_policy = models.PositiveSmallIntegerField(
default=CommonMessagePolicyEnum.EVERYONE default=CommonMessagePolicyEnum.EVERYONE
@ -737,6 +742,15 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
default_group_name=SystemGroups.ADMINISTRATORS, default_group_name=SystemGroups.ADMINISTRATORS,
id_field_name="can_delete_any_message_group_id", id_field_name="can_delete_any_message_group_id",
), ),
can_delete_own_message_group=GroupPermissionSetting(
require_system_group=False,
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_delete_own_message_group_id",
),
direct_message_initiator_group=GroupPermissionSetting( direct_message_initiator_group=GroupPermissionSetting(
require_system_group=False, require_system_group=False,
allow_internet_group=False, allow_internet_group=False,
@ -777,6 +791,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
"can_create_public_channel_group", "can_create_public_channel_group",
"can_create_web_public_channel_group", "can_create_web_public_channel_group",
"can_delete_any_message_group", "can_delete_any_message_group",
"can_delete_own_message_group",
"direct_message_initiator_group", "direct_message_initiator_group",
"direct_message_permission_group", "direct_message_permission_group",
] ]
@ -1148,6 +1163,8 @@ def get_realm_with_settings(realm_id: int) -> Realm:
"can_create_web_public_channel_group__named_user_group", "can_create_web_public_channel_group__named_user_group",
"can_delete_any_message_group", "can_delete_any_message_group",
"can_delete_any_message_group__named_user_group", "can_delete_any_message_group__named_user_group",
"can_delete_own_message_group",
"can_delete_own_message_group__named_user_group",
"direct_message_initiator_group", "direct_message_initiator_group",
"direct_message_initiator_group__named_user_group", "direct_message_initiator_group__named_user_group",
"direct_message_permission_group", "direct_message_permission_group",

View File

@ -777,8 +777,8 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings):
"can_create_public_channel_group", "can_create_public_channel_group",
"can_create_web_public_channel_group", "can_create_web_public_channel_group",
"can_delete_any_message_group", "can_delete_any_message_group",
"can_delete_own_message_group",
"create_multiuse_invite_group", "create_multiuse_invite_group",
"delete_own_message_policy",
"direct_message_initiator_group", "direct_message_initiator_group",
"direct_message_permission_group", "direct_message_permission_group",
"edit_topic_policy", "edit_topic_policy",
@ -871,7 +871,7 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings):
return self.has_permission("can_delete_any_message_group") return self.has_permission("can_delete_any_message_group")
def can_delete_own_message(self) -> bool: def can_delete_own_message(self) -> bool:
return self.has_permission("delete_own_message_policy") return self.has_permission("can_delete_own_message_group")
def can_access_public_streams(self) -> bool: def can_access_public_streams(self) -> bool:
return not (self.is_guest or self.realm.is_zephyr_mirror_realm) return not (self.is_guest or self.realm.is_zephyr_mirror_realm)

View File

@ -4326,6 +4326,21 @@ paths:
**Changes**: New in Zulip 10.0 (feature level 281). Previously, this **Changes**: New in Zulip 10.0 (feature level 281). Previously, this
permission was limited to administrators only and was uneditable. permission was limited to administrators only and was uneditable.
- $ref: "#/components/schemas/GroupSettingValue" - $ref: "#/components/schemas/GroupSettingValue"
can_delete_own_message_group:
allOf:
- description: |
A [group-setting value](/api/group-setting-values) defining the set of
users who have permission to delete messages that they have sent in the
organization.
**Changes**: New in Zulip 10.0 (feature level 291). Previously, this
permission was controlled by the enum `delete_own_message_policy`. Values
were 1=Members, 2=Admins, 3=Full members, 4=Moderators, 5=Everyone.
Before Zulip 5.0 (feature level 101), the `allow_message_deleting` boolean
setting controlled this permission; `true` corresponded to `Everyone`, and
`false` to `Admins`.
- $ref: "#/components/schemas/GroupSettingValue"
default_code_block_language: default_code_block_language:
type: string type: string
description: | description: |
@ -15666,6 +15681,23 @@ paths:
**Changes**: New in Zulip 10.0 (feature level 281). Previously, this **Changes**: New in Zulip 10.0 (feature level 281). Previously, this
permission was limited to administrators only and was uneditable. permission was limited to administrators only and was uneditable.
- $ref: "#/components/schemas/GroupSettingValue" - $ref: "#/components/schemas/GroupSettingValue"
realm_can_delete_own_message_group:
allOf:
- description: |
Present if `realm` is present in `fetch_event_types`.
A [group-setting value](/api/group-setting-values) defining the set of
users who have permission to delete messages that they have sent in the
organization.
**Changes**: New in Zulip 10.0 (feature level 291). Previously, this
permission was controlled by the enum `delete_own_message_policy`. Values
were 1=Members, 2=Admins, 3=Full members, 4=Moderators, 5=Everyone.
Before Zulip 5.0 (feature level 101), the `allow_message_deleting` boolean
setting controlled this permission; `true` corresponded to `Everyone`, and
`false` to `Admins`.
- $ref: "#/components/schemas/GroupSettingValue"
realm_delete_own_message_policy: realm_delete_own_message_policy:
type: integer type: integer
description: | description: |

View File

@ -134,6 +134,7 @@ class HomeTest(ZulipTestCase):
"realm_can_create_public_channel_group", "realm_can_create_public_channel_group",
"realm_can_create_web_public_channel_group", "realm_can_create_web_public_channel_group",
"realm_can_delete_any_message_group", "realm_can_delete_any_message_group",
"realm_can_delete_own_message_group",
"realm_create_multiuse_invite_group", "realm_create_multiuse_invite_group",
"realm_create_private_stream_policy", "realm_create_private_stream_policy",
"realm_create_public_stream_policy", "realm_create_public_stream_policy",

View File

@ -7,14 +7,11 @@ from django.db import IntegrityError
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
from zerver.actions.message_delete import do_delete_messages from zerver.actions.message_delete import do_delete_messages
from zerver.actions.realm_settings import ( from zerver.actions.realm_settings import do_change_realm_permission_group_setting
do_change_realm_permission_group_setting,
do_set_realm_property,
)
from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_classes import ZulipTestCase
from zerver.models import Message, NamedUserGroup, UserProfile from zerver.models import Message, NamedUserGroup, UserProfile
from zerver.models.groups import SystemGroups from zerver.models.groups import SystemGroups
from zerver.models.realms import CommonMessagePolicyEnum, get_realm from zerver.models.realms import get_realm
from zerver.models.streams import get_stream from zerver.models.streams import get_stream
if TYPE_CHECKING: if TYPE_CHECKING:
@ -34,7 +31,7 @@ class DeleteMessageTest(ZulipTestCase):
def test_delete_message_by_user(self) -> None: def test_delete_message_by_user(self) -> None:
def set_message_deleting_params( def set_message_deleting_params(
can_delete_any_message_group: NamedUserGroup, can_delete_any_message_group: NamedUserGroup,
delete_own_message_policy: int, can_delete_own_message_group: NamedUserGroup,
message_content_delete_limit_seconds: int | str, message_content_delete_limit_seconds: int | str,
) -> None: ) -> None:
self.login("iago") self.login("iago")
@ -44,7 +41,9 @@ class DeleteMessageTest(ZulipTestCase):
"can_delete_any_message_group": orjson.dumps( "can_delete_any_message_group": orjson.dumps(
{"new": can_delete_any_message_group.id} {"new": can_delete_any_message_group.id}
).decode(), ).decode(),
"delete_own_message_policy": orjson.dumps(delete_own_message_policy).decode(), "can_delete_own_message_group": orjson.dumps(
{"new": can_delete_own_message_group.id}
).decode(),
"message_content_delete_limit_seconds": orjson.dumps( "message_content_delete_limit_seconds": orjson.dumps(
message_content_delete_limit_seconds message_content_delete_limit_seconds
).decode(), ).decode(),
@ -83,10 +82,13 @@ class DeleteMessageTest(ZulipTestCase):
moderators_system_group = NamedUserGroup.objects.get( moderators_system_group = NamedUserGroup.objects.get(
name=SystemGroups.MODERATORS, realm=realm, is_system_group=True name=SystemGroups.MODERATORS, realm=realm, is_system_group=True
) )
everyone_system_group = NamedUserGroup.objects.get(
name=SystemGroups.EVERYONE, realm=realm, is_system_group=True
)
# Test if message deleting is not allowed(default). # Test if message deleting is not allowed(default).
set_message_deleting_params( set_message_deleting_params(
administrators_system_group, CommonMessagePolicyEnum.ADMINS_ONLY, "unlimited" administrators_system_group, administrators_system_group, "unlimited"
) )
hamlet = self.example_user("hamlet") hamlet = self.example_user("hamlet")
self.login_user(hamlet) self.login_user(hamlet)
@ -103,9 +105,7 @@ class DeleteMessageTest(ZulipTestCase):
# Test if message deleting is allowed. # Test if message deleting is allowed.
# Test if time limit is None(no limit). # Test if time limit is None(no limit).
set_message_deleting_params( set_message_deleting_params(administrators_system_group, everyone_system_group, "unlimited")
administrators_system_group, CommonMessagePolicyEnum.EVERYONE, "unlimited"
)
msg_id = self.send_stream_message(hamlet, "Denmark") msg_id = self.send_stream_message(hamlet, "Denmark")
message = Message.objects.get(id=msg_id) message = Message.objects.get(id=msg_id)
message.date_sent -= timedelta(seconds=600) message.date_sent -= timedelta(seconds=600)
@ -118,9 +118,7 @@ class DeleteMessageTest(ZulipTestCase):
self.assert_json_success(result) self.assert_json_success(result)
# Test if time limit is non-zero. # Test if time limit is non-zero.
set_message_deleting_params( set_message_deleting_params(administrators_system_group, everyone_system_group, 240)
administrators_system_group, CommonMessagePolicyEnum.EVERYONE, 240
)
msg_id_1 = self.send_stream_message(hamlet, "Denmark") msg_id_1 = self.send_stream_message(hamlet, "Denmark")
message = Message.objects.get(id=msg_id_1) message = Message.objects.get(id=msg_id_1)
message.date_sent -= timedelta(seconds=120) message.date_sent -= timedelta(seconds=120)
@ -151,9 +149,7 @@ class DeleteMessageTest(ZulipTestCase):
self.assert_json_error(result, "Invalid message(s)") self.assert_json_error(result, "Invalid message(s)")
# Test if message deletion is allowed when every member can delete any message. # Test if message deletion is allowed when every member can delete any message.
set_message_deleting_params( set_message_deleting_params(members_system_group, administrators_system_group, "unlimited")
members_system_group, CommonMessagePolicyEnum.ADMINS_ONLY, "unlimited"
)
msg_id_1 = self.send_stream_message(hamlet, "Denmark") msg_id_1 = self.send_stream_message(hamlet, "Denmark")
msg_id_2 = self.send_stream_message(hamlet, "Denmark") msg_id_2 = self.send_stream_message(hamlet, "Denmark")
msg_id_3 = self.send_stream_message(hamlet, "Denmark") msg_id_3 = self.send_stream_message(hamlet, "Denmark")
@ -169,7 +165,7 @@ class DeleteMessageTest(ZulipTestCase):
# Test if there is no time limit to delete messages for users who can delete # Test if there is no time limit to delete messages for users who can delete
# any message. # any message.
set_message_deleting_params(moderators_system_group, CommonMessagePolicyEnum.EVERYONE, 240) set_message_deleting_params(moderators_system_group, everyone_system_group, 240)
msg_id_1 = self.send_stream_message(hamlet, "Denmark") msg_id_1 = self.send_stream_message(hamlet, "Denmark")
message = Message.objects.get(id=msg_id_1) message = Message.objects.get(id=msg_id_1)
message.date_sent -= timedelta(seconds=120) message.date_sent -= timedelta(seconds=120)
@ -214,7 +210,7 @@ class DeleteMessageTest(ZulipTestCase):
def set_message_deleting_params( def set_message_deleting_params(
can_delete_any_message_group: NamedUserGroup, can_delete_any_message_group: NamedUserGroup,
delete_own_message_policy: int, can_delete_own_message_group: NamedUserGroup,
message_content_delete_limit_seconds: int | str, message_content_delete_limit_seconds: int | str,
) -> None: ) -> None:
result = self.api_patch( result = self.api_patch(
@ -224,7 +220,9 @@ class DeleteMessageTest(ZulipTestCase):
"can_delete_any_message_group": orjson.dumps( "can_delete_any_message_group": orjson.dumps(
{"new": can_delete_any_message_group.id} {"new": can_delete_any_message_group.id}
).decode(), ).decode(),
"delete_own_message_policy": orjson.dumps(delete_own_message_policy).decode(), "can_delete_own_message_group": orjson.dumps(
{"new": can_delete_own_message_group.id}
).decode(),
"message_content_delete_limit_seconds": orjson.dumps( "message_content_delete_limit_seconds": orjson.dumps(
message_content_delete_limit_seconds message_content_delete_limit_seconds
).decode(), ).decode(),
@ -256,9 +254,12 @@ class DeleteMessageTest(ZulipTestCase):
moderators_system_group = NamedUserGroup.objects.get( moderators_system_group = NamedUserGroup.objects.get(
name=SystemGroups.MODERATORS, realm=realm, is_system_group=True name=SystemGroups.MODERATORS, realm=realm, is_system_group=True
) )
everyone_system_group = NamedUserGroup.objects.get(
name=SystemGroups.EVERYONE, realm=realm, is_system_group=True
)
set_message_deleting_params( set_message_deleting_params(
moderators_system_group, CommonMessagePolicyEnum.ADMINS_ONLY, "unlimited" moderators_system_group, administrators_system_group, "unlimited"
) )
hamlet = self.example_user("hamlet") hamlet = self.example_user("hamlet")
@ -280,9 +281,7 @@ class DeleteMessageTest(ZulipTestCase):
self.assert_json_success(result) self.assert_json_success(result)
msg_id = self.send_stream_message(test_bot, "Denmark") msg_id = self.send_stream_message(test_bot, "Denmark")
set_message_deleting_params( set_message_deleting_params(administrators_system_group, everyone_system_group, "unlimited")
administrators_system_group, CommonMessagePolicyEnum.EVERYONE, "unlimited"
)
result = test_delete_message_by_other_user(msg_id) result = test_delete_message_by_other_user(msg_id)
self.assert_json_error(result, "You don't have permission to delete this message") self.assert_json_error(result, "You don't have permission to delete this message")
@ -291,9 +290,7 @@ class DeleteMessageTest(ZulipTestCase):
self.assert_json_success(result) self.assert_json_success(result)
msg_id = self.send_stream_message(test_bot, "Denmark") msg_id = self.send_stream_message(test_bot, "Denmark")
set_message_deleting_params( set_message_deleting_params(administrators_system_group, everyone_system_group, 600)
administrators_system_group, CommonMessagePolicyEnum.EVERYONE, 600
)
message = Message.objects.get(id=msg_id) message = Message.objects.get(id=msg_id)
message.date_sent = timezone_now() - timedelta(seconds=700) message.date_sent = timezone_now() - timedelta(seconds=700)
@ -310,16 +307,12 @@ class DeleteMessageTest(ZulipTestCase):
# Check that the bot can also delete the messages sent by them # Check that the bot can also delete the messages sent by them
# depending on the realm permissions for message deletion. # depending on the realm permissions for message deletion.
set_message_deleting_params( set_message_deleting_params(administrators_system_group, administrators_system_group, 600)
administrators_system_group, CommonMessagePolicyEnum.ADMINS_ONLY, 600
)
msg_id = self.send_stream_message(test_bot, "Denmark") msg_id = self.send_stream_message(test_bot, "Denmark")
result = self.api_delete(test_bot, f"/api/v1/messages/{msg_id}") result = self.api_delete(test_bot, f"/api/v1/messages/{msg_id}")
self.assert_json_error(result, "You don't have permission to delete this message") self.assert_json_error(result, "You don't have permission to delete this message")
set_message_deleting_params( set_message_deleting_params(administrators_system_group, everyone_system_group, 600)
administrators_system_group, CommonMessagePolicyEnum.EVERYONE, 600
)
message = Message.objects.get(id=msg_id) message = Message.objects.get(id=msg_id)
message.date_sent = timezone_now() - timedelta(seconds=700) message.date_sent = timezone_now() - timedelta(seconds=700)
message.save() message.save()
@ -364,6 +357,9 @@ class DeleteMessageTest(ZulipTestCase):
moderators_system_group = NamedUserGroup.objects.get( moderators_system_group = NamedUserGroup.objects.get(
name=SystemGroups.MODERATORS, realm=realm, is_system_group=True name=SystemGroups.MODERATORS, realm=realm, is_system_group=True
) )
everyone_system_group = NamedUserGroup.objects.get(
name=SystemGroups.EVERYONE, realm=realm, is_system_group=True
)
do_change_realm_permission_group_setting( do_change_realm_permission_group_setting(
realm, realm,
@ -371,10 +367,10 @@ class DeleteMessageTest(ZulipTestCase):
administrators_system_group, administrators_system_group,
acting_user=None, acting_user=None,
) )
do_set_realm_property( do_change_realm_permission_group_setting(
realm, realm,
"delete_own_message_policy", "can_delete_own_message_group",
CommonMessagePolicyEnum.EVERYONE, everyone_system_group,
acting_user=None, acting_user=None,
) )
@ -392,10 +388,10 @@ class DeleteMessageTest(ZulipTestCase):
moderators_system_group, moderators_system_group,
acting_user=None, acting_user=None,
) )
do_set_realm_property( do_change_realm_permission_group_setting(
realm, realm,
"delete_own_message_policy", "can_delete_own_message_group",
CommonMessagePolicyEnum.ADMINS_ONLY, administrators_system_group,
acting_user=None, acting_user=None,
) )
@ -410,7 +406,7 @@ class DeleteMessageTest(ZulipTestCase):
"hamlet", "cordelia", "You don't have permission to delete this message" "hamlet", "cordelia", "You don't have permission to delete this message"
) )
def test_delete_message_according_to_delete_own_message_policy(self) -> None: def test_delete_message_according_to_can_delete_own_message_group(self) -> None:
def check_delete_message_by_sender(sender_name: str, error_msg: str | None = None) -> None: def check_delete_message_by_sender(sender_name: str, error_msg: str | None = None) -> None:
sender = self.example_user(sender_name) sender = self.example_user(sender_name)
msg_id = self.send_stream_message(sender, "Verona") msg_id = self.send_stream_message(sender, "Verona")
@ -423,19 +419,32 @@ class DeleteMessageTest(ZulipTestCase):
realm = get_realm("zulip") realm = get_realm("zulip")
do_set_realm_property( administrators_system_group = NamedUserGroup.objects.get(
name=SystemGroups.ADMINISTRATORS, realm=realm, is_system_group=True
)
moderators_system_group = NamedUserGroup.objects.get(
name=SystemGroups.MODERATORS, realm=realm, is_system_group=True
)
everyone_system_group = NamedUserGroup.objects.get(
name=SystemGroups.EVERYONE, realm=realm, is_system_group=True
)
members_system_group = NamedUserGroup.objects.get(
name=SystemGroups.MEMBERS, realm=realm, is_system_group=True
)
do_change_realm_permission_group_setting(
realm, realm,
"delete_own_message_policy", "can_delete_own_message_group",
CommonMessagePolicyEnum.ADMINS_ONLY, administrators_system_group,
acting_user=None, acting_user=None,
) )
check_delete_message_by_sender("shiva", "You don't have permission to delete this message") check_delete_message_by_sender("shiva", "You don't have permission to delete this message")
check_delete_message_by_sender("iago") check_delete_message_by_sender("iago")
do_set_realm_property( do_change_realm_permission_group_setting(
realm, realm,
"delete_own_message_policy", "can_delete_own_message_group",
CommonMessagePolicyEnum.MODERATORS_ONLY, moderators_system_group,
acting_user=None, acting_user=None,
) )
check_delete_message_by_sender( check_delete_message_by_sender(
@ -443,10 +452,10 @@ class DeleteMessageTest(ZulipTestCase):
) )
check_delete_message_by_sender("shiva") check_delete_message_by_sender("shiva")
do_set_realm_property( do_change_realm_permission_group_setting(
realm, realm,
"delete_own_message_policy", "can_delete_own_message_group",
CommonMessagePolicyEnum.MEMBERS_ONLY, members_system_group,
acting_user=None, acting_user=None,
) )
check_delete_message_by_sender( check_delete_message_by_sender(
@ -454,25 +463,8 @@ class DeleteMessageTest(ZulipTestCase):
) )
check_delete_message_by_sender("cordelia") check_delete_message_by_sender("cordelia")
do_set_realm_property( do_change_realm_permission_group_setting(
realm, realm, "can_delete_own_message_group", everyone_system_group, acting_user=None
"delete_own_message_policy",
CommonMessagePolicyEnum.FULL_MEMBERS_ONLY,
acting_user=None,
)
do_set_realm_property(realm, "waiting_period_threshold", 10, acting_user=None)
cordelia = self.example_user("cordelia")
cordelia.date_joined = timezone_now() - timedelta(days=9)
cordelia.save()
check_delete_message_by_sender(
"cordelia", "You don't have permission to delete this message"
)
cordelia.date_joined = timezone_now() - timedelta(days=11)
cordelia.save()
check_delete_message_by_sender("cordelia")
do_set_realm_property(
realm, "delete_own_message_policy", CommonMessagePolicyEnum.EVERYONE, acting_user=None
) )
check_delete_message_by_sender("cordelia") check_delete_message_by_sender("cordelia")
check_delete_message_by_sender("polonius") check_delete_message_by_sender("polonius")

View File

@ -14,7 +14,7 @@ from zerver.actions.message_edit import (
maybe_send_resolve_topic_notifications, maybe_send_resolve_topic_notifications,
) )
from zerver.actions.reactions import do_add_reaction from zerver.actions.reactions import do_add_reaction
from zerver.actions.realm_settings import do_set_realm_property from zerver.actions.realm_settings import do_change_realm_permission_group_setting
from zerver.actions.user_topics import do_set_user_topic_visibility_policy from zerver.actions.user_topics import do_set_user_topic_visibility_policy
from zerver.lib.message import truncate_topic from zerver.lib.message import truncate_topic
from zerver.lib.test_classes import ZulipTestCase, get_topic_messages from zerver.lib.test_classes import ZulipTestCase, get_topic_messages
@ -27,7 +27,7 @@ from zerver.lib.user_topics import (
from zerver.lib.utils import assert_is_not_none from zerver.lib.utils import assert_is_not_none
from zerver.models import Message, UserMessage, UserProfile, UserTopic from zerver.models import Message, UserMessage, UserProfile, UserTopic
from zerver.models.constants import MAX_TOPIC_NAME_LENGTH from zerver.models.constants import MAX_TOPIC_NAME_LENGTH
from zerver.models.realms import CommonMessagePolicyEnum from zerver.models.groups import NamedUserGroup, SystemGroups
from zerver.models.streams import Stream from zerver.models.streams import Stream
@ -888,10 +888,13 @@ class MessageMoveTopicTest(ZulipTestCase):
# Delete the message in target topic to make it empty. # Delete the message in target topic to make it empty.
self.login("hamlet") self.login("hamlet")
do_set_realm_property( members_system_group = NamedUserGroup.objects.get(
name=SystemGroups.MEMBERS, realm=hamlet.realm, is_system_group=True
)
do_change_realm_permission_group_setting(
hamlet.realm, hamlet.realm,
"delete_own_message_policy", "can_delete_own_message_group",
CommonMessagePolicyEnum.MEMBERS_ONLY, members_system_group,
acting_user=None, acting_user=None,
) )
self.client_delete(f"/json/messages/{target_message_id}") self.client_delete(f"/json/messages/{target_message_id}")

View File

@ -154,7 +154,7 @@ def validate_can_delete_message(user_profile: UserProfile, message: Message) ->
# Users can only delete messages sent by them or by their bots. # Users can only delete messages sent by them or by their bots.
raise JsonableError(_("You don't have permission to delete this message")) raise JsonableError(_("You don't have permission to delete this message"))
if not user_profile.can_delete_own_message(): if not user_profile.can_delete_own_message():
# Only user with roles as allowed by delete_own_message_policy can delete message. # Only user with roles as allowed by can_delete_own_message_group can delete message.
raise JsonableError(_("You don't have permission to delete this message")) raise JsonableError(_("You don't have permission to delete this message"))
deadline_seconds: int | None = user_profile.realm.message_content_delete_limit_seconds deadline_seconds: int | None = user_profile.realm.message_content_delete_limit_seconds

View File

@ -114,6 +114,7 @@ def update_realm(
inline_url_embed_preview: Json[bool] | None = None, inline_url_embed_preview: Json[bool] | None = None,
add_custom_emoji_policy: Json[CommonPolicyEnum] | None = None, add_custom_emoji_policy: Json[CommonPolicyEnum] | None = None,
can_delete_any_message_group: Json[GroupSettingChangeRequest] | None = None, can_delete_any_message_group: Json[GroupSettingChangeRequest] | None = None,
can_delete_own_message_group: Json[GroupSettingChangeRequest] | None = None,
delete_own_message_policy: Json[CommonMessagePolicyEnum] | None = None, delete_own_message_policy: Json[CommonMessagePolicyEnum] | None = None,
message_content_delete_limit_seconds_raw: Annotated[ message_content_delete_limit_seconds_raw: Annotated[
Json[int | str] | None, Json[int | str] | None,