stream_settings: Add can_delete_own_message_group setting.

Adds per-channel `can_delete_own_message_group` setting for
defining who can delete their own message in the channel.
This commit is contained in:
Vector73
2025-07-16 14:45:53 +00:00
committed by Tim Abbott
parent c4e641365b
commit 97a43fa6b6
23 changed files with 357 additions and 9 deletions

View File

@@ -0,0 +1,10 @@
* [`GET /users/me/subscriptions`](/api/get-subscriptions),
[`GET /streams`](/api/get-streams), [`GET /events`](/api/get-events),
[`POST /register`](/api/register-queue): Added `can_delete_own_message_group`
field which is a [group-setting value](/api/group-setting-values) describing the
set of users with permissions to delete the messages they have sent in the channel.
* [`POST /users/me/subscriptions`](/api/subscribe),
[`PATCH /streams/{stream_id}`](/api/update-stream): Added
`can_delete_own_message_group` parameter to support setting and
changing the user group whose members can delete the messages they have sent
in the channel.

View File

@@ -72,6 +72,7 @@ export const stream_group_setting_name_schema = z.enum([
"can_add_subscribers_group",
"can_administer_channel_group",
"can_delete_any_message_group",
"can_delete_own_message_group",
"can_move_messages_out_of_channel_group",
"can_move_messages_within_channel_group",
"can_remove_subscribers_group",

View File

@@ -270,9 +270,24 @@ export function get_deletability(message: Message): boolean {
return false;
}
if (!settings_data.user_can_delete_own_message()) {
if (message.type !== "stream") {
return false;
}
const stream = stream_data.get_sub_by_id(message.stream_id);
assert(stream !== undefined);
const can_delete_own_message_in_channel =
settings_data.user_has_permission_for_group_setting(
stream.can_delete_own_message_group,
"can_delete_own_message_group",
"stream",
);
if (!can_delete_own_message_in_channel) {
return false;
}
}
if (realm.realm_message_content_delete_limit_seconds === null) {
// This means no time limit for message deletion.
return true;

View File

@@ -1605,6 +1605,7 @@ export const group_setting_widget_map = new Map<string, GroupSettingPillContaine
["can_manage_group", null],
["can_mention_group", null],
["can_delete_any_message_group", null],
["can_delete_own_message_group", null],
["can_move_messages_out_of_channel_group", null],
["can_move_messages_within_channel_group", null],
["can_remove_members_group", null],

View File

@@ -766,6 +766,9 @@ export const all_group_setting_labels = {
can_delete_any_message_group: $t({
defaultMessage: "Who can delete any message in this channel",
}),
can_delete_own_message_group: $t({
defaultMessage: "Who can delete their own messages in this channel",
}),
can_move_messages_out_of_channel_group: $t({
defaultMessage: "Who can move messages out of this channel",
}),
@@ -872,6 +875,7 @@ export const stream_group_permission_settings: StreamGroupSettingName[] = [
"can_send_message_group",
"can_administer_channel_group",
"can_delete_any_message_group",
"can_delete_own_message_group",
"can_move_messages_out_of_channel_group",
"can_move_messages_within_channel_group",
"can_subscribe_group",

View File

@@ -77,6 +77,7 @@ const group_setting_widget_map = new Map<string, GroupSettingPillContainer | nul
["can_add_subscribers_group", null],
["can_administer_channel_group", null],
["can_delete_any_message_group", null],
["can_delete_own_message_group", null],
["can_move_messages_out_of_channel_group", null],
["can_move_messages_within_channel_group", null],
["can_remove_subscribers_group", null],

View File

@@ -14,6 +14,7 @@ export const stream_permission_group_settings_schema = z.enum([
"can_add_subscribers_group",
"can_administer_channel_group",
"can_delete_any_message_group",
"can_delete_own_message_group",
"can_move_messages_out_of_channel_group",
"can_move_messages_within_channel_group",
"can_remove_subscribers_group",
@@ -36,6 +37,7 @@ export const stream_schema = z.object({
can_add_subscribers_group: group_setting_value_schema,
can_administer_channel_group: group_setting_value_schema,
can_delete_any_message_group: group_setting_value_schema,
can_delete_own_message_group: group_setting_value_schema,
can_move_messages_out_of_channel_group: group_setting_value_schema,
can_move_messages_within_channel_group: group_setting_value_schema,
can_remove_subscribers_group: group_setting_value_schema,

View File

@@ -160,6 +160,11 @@
setting_name="can_delete_any_message_group"
label=group_setting_labels.can_delete_any_message_group
prefix=prefix }}
{{> ../settings/group_setting_value_pill_input
setting_name="can_delete_own_message_group"
label=group_setting_labels.can_delete_own_message_group
prefix=prefix }}
</div>
<div id="channel-administrative-permissions" class="settings-subsection-parent">

View File

@@ -57,6 +57,7 @@ exports.test_streams = {
topics_policy: "inherit",
can_administer_channel_group: 2,
can_delete_any_message_group: 2,
can_delete_own_message_group: 2,
can_move_messages_out_of_channel_group: 2,
can_move_messages_within_channel_group: 2,
can_send_message_group: 2,
@@ -81,6 +82,7 @@ exports.test_streams = {
topics_policy: "inherit",
can_administer_channel_group: 2,
can_delete_any_message_group: 2,
can_delete_own_message_group: 2,
can_move_messages_out_of_channel_group: 2,
can_move_messages_within_channel_group: 2,
can_send_message_group: 2,

View File

@@ -2260,13 +2260,12 @@ run_test("get_deletability", ({override}) => {
name: "denmark",
stream_id: 3,
can_delete_any_message_group: nobody_group.id,
can_delete_own_message_group: nobody_group.id,
can_delete_own_message_group: moderators_group.id,
};
stream_data.add_sub(social);
stream_data.add_sub(denmark);
const message = {
sent_by_me: false,
locally_echoed: true,
type: "stream",
stream_id: social.stream_id,
@@ -2290,4 +2289,24 @@ run_test("get_deletability", ({override}) => {
message.sender_id = moderator_user_id;
initialize_and_override_current_user(moderator_user_id, override);
assert.equal(message_edit.get_deletability(message), false);
// Test per-channel delete permissions for deleting own messages.
message.stream_id = social.stream_id;
message.sender_id = moderator_user_id;
initialize_and_override_current_user(moderator_user_id, override);
assert.equal(message_edit.get_deletability(message), true);
message.sender_id = me.user_id;
initialize_and_override_current_user(me.user_id, override);
assert.equal(message_edit.get_deletability(message), false);
message.stream_id = denmark.stream_id;
assert.equal(message_edit.get_deletability(message), false);
initialize_and_override_current_user(moderator_user_id, override);
assert.equal(message_edit.get_deletability(message), false);
message.sender_id = moderator_user_id;
assert.equal(message_edit.get_deletability(message), false);
});

View File

@@ -460,6 +460,7 @@ def send_subscription_add_events(
can_add_subscribers_group=stream_dict["can_add_subscribers_group"],
can_administer_channel_group=stream_dict["can_administer_channel_group"],
can_delete_any_message_group=stream_dict["can_delete_any_message_group"],
can_delete_own_message_group=stream_dict["can_delete_own_message_group"],
can_move_messages_out_of_channel_group=stream_dict[
"can_move_messages_out_of_channel_group"
],

View File

@@ -855,6 +855,7 @@ class BasicStreamFields(BaseModel):
is_archived: bool
can_administer_channel_group: int | UserGroupMembersDict
can_delete_any_message_group: int | UserGroupMembersDict
can_delete_own_message_group: int | UserGroupMembersDict
can_move_messages_out_of_channel_group: int | UserGroupMembersDict
can_move_messages_within_channel_group: int | UserGroupMembersDict
can_remove_subscribers_group: int | UserGroupMembersDict
@@ -921,6 +922,7 @@ class SingleSubscription(BaseModel):
is_archived: bool
can_administer_channel_group: int | UserGroupMembersDict
can_delete_any_message_group: int | UserGroupMembersDict
can_delete_own_message_group: int | UserGroupMembersDict
can_move_messages_out_of_channel_group: int | UserGroupMembersDict
can_move_messages_within_channel_group: int | UserGroupMembersDict
can_remove_subscribers_group: int | UserGroupMembersDict

View File

@@ -91,6 +91,7 @@ class StreamDict(TypedDict, total=False):
can_add_subscribers_group: UserGroup | None
can_administer_channel_group: UserGroup | None
can_delete_any_message_group: UserGroup | None
can_delete_own_message_group: UserGroup | None
can_move_messages_out_of_channel_group: UserGroup | None
can_move_messages_within_channel_group: UserGroup | None
can_send_message_group: UserGroup | None
@@ -350,6 +351,7 @@ def create_stream_if_needed(
can_add_subscribers_group: UserGroup | None = None,
can_administer_channel_group: UserGroup | None = None,
can_delete_any_message_group: UserGroup | None = None,
can_delete_own_message_group: UserGroup | None = None,
can_move_messages_out_of_channel_group: UserGroup | None = None,
can_move_messages_within_channel_group: UserGroup | None = None,
can_send_message_group: UserGroup | None = None,
@@ -479,6 +481,7 @@ def create_streams_if_needed(
can_add_subscribers_group=stream_dict.get("can_add_subscribers_group", None),
can_administer_channel_group=stream_dict.get("can_administer_channel_group", None),
can_delete_any_message_group=stream_dict.get("can_delete_any_message_group", None),
can_delete_own_message_group=stream_dict.get("can_delete_own_message_group", None),
can_move_messages_out_of_channel_group=stream_dict.get(
"can_move_messages_out_of_channel_group", None
),
@@ -1172,6 +1175,15 @@ def can_delete_any_message_in_channel(user_profile: UserProfile, stream: Stream)
)
def can_delete_own_message_in_channel(user_profile: UserProfile, stream: Stream) -> bool:
return user_has_permission_for_group_setting(
stream.can_delete_own_message_group_id,
user_profile,
Stream.stream_permission_group_settings["can_delete_own_message_group"],
direct_member_only=False,
)
def can_move_messages_out_of_channel(user_profile: UserProfile, stream: Stream) -> bool:
if user_profile.is_realm_admin:
return True
@@ -1654,6 +1666,9 @@ def stream_to_dict(
can_delete_any_message_group = get_group_setting_value_for_register_api(
stream.can_delete_any_message_group_id, anonymous_group_membership
)
can_delete_own_message_group = get_group_setting_value_for_register_api(
stream.can_delete_own_message_group_id, anonymous_group_membership
)
can_move_messages_out_of_channel_group = get_group_setting_value_for_register_api(
stream.can_move_messages_out_of_channel_group_id, anonymous_group_membership
)
@@ -1682,6 +1697,7 @@ def stream_to_dict(
can_add_subscribers_group=can_add_subscribers_group,
can_administer_channel_group=can_administer_channel_group,
can_delete_any_message_group=can_delete_any_message_group,
can_delete_own_message_group=can_delete_own_message_group,
can_move_messages_out_of_channel_group=can_move_messages_out_of_channel_group,
can_move_messages_within_channel_group=can_move_messages_within_channel_group,
can_send_message_group=can_send_message_group,

View File

@@ -74,6 +74,9 @@ def get_web_public_subs(
can_delete_any_message_group = get_group_setting_value_for_register_api(
stream.can_delete_any_message_group_id, anonymous_group_membership
)
can_delete_own_message_group = get_group_setting_value_for_register_api(
stream.can_delete_own_message_group_id, anonymous_group_membership
)
can_move_messages_out_of_channel_group = get_group_setting_value_for_register_api(
stream.can_move_messages_out_of_channel_group_id, anonymous_group_membership
)
@@ -132,6 +135,7 @@ def get_web_public_subs(
can_add_subscribers_group=can_add_subscribers_group,
can_administer_channel_group=can_administer_channel_group,
can_delete_any_message_group=can_delete_any_message_group,
can_delete_own_message_group=can_delete_own_message_group,
can_move_messages_out_of_channel_group=can_move_messages_out_of_channel_group,
can_move_messages_within_channel_group=can_move_messages_within_channel_group,
can_send_message_group=can_send_message_group,
@@ -209,6 +213,9 @@ def build_stream_api_dict(
can_delete_any_message_group = get_group_setting_value_for_register_api(
raw_stream_dict["can_delete_any_message_group_id"], anonymous_group_membership
)
can_delete_own_message_group = get_group_setting_value_for_register_api(
raw_stream_dict["can_delete_own_message_group_id"], anonymous_group_membership
)
can_move_messages_out_of_channel_group = get_group_setting_value_for_register_api(
raw_stream_dict["can_move_messages_out_of_channel_group_id"], anonymous_group_membership
)
@@ -233,6 +240,7 @@ def build_stream_api_dict(
can_add_subscribers_group=can_add_subscribers_group,
can_administer_channel_group=can_administer_channel_group,
can_delete_any_message_group=can_delete_any_message_group,
can_delete_own_message_group=can_delete_own_message_group,
can_move_messages_out_of_channel_group=can_move_messages_out_of_channel_group,
can_move_messages_within_channel_group=can_move_messages_within_channel_group,
can_send_message_group=can_send_message_group,
@@ -270,6 +278,7 @@ def build_stream_dict_for_sub(
can_add_subscribers_group = stream_dict["can_add_subscribers_group"]
can_administer_channel_group = stream_dict["can_administer_channel_group"]
can_delete_any_message_group = stream_dict["can_delete_any_message_group"]
can_delete_own_message_group = stream_dict["can_delete_own_message_group"]
can_move_messages_out_of_channel_group = stream_dict["can_move_messages_out_of_channel_group"]
can_move_messages_within_channel_group = stream_dict["can_move_messages_within_channel_group"]
can_send_message_group = stream_dict["can_send_message_group"]
@@ -316,6 +325,7 @@ def build_stream_dict_for_sub(
can_add_subscribers_group=can_add_subscribers_group,
can_administer_channel_group=can_administer_channel_group,
can_delete_any_message_group=can_delete_any_message_group,
can_delete_own_message_group=can_delete_own_message_group,
can_move_messages_out_of_channel_group=can_move_messages_out_of_channel_group,
can_move_messages_within_channel_group=can_move_messages_within_channel_group,
can_send_message_group=can_send_message_group,
@@ -390,6 +400,9 @@ def build_stream_dict_for_never_sub(
can_delete_any_message_group_value = get_group_setting_value_for_register_api(
raw_stream_dict["can_delete_any_message_group_id"], anonymous_group_membership
)
can_delete_own_message_group_value = get_group_setting_value_for_register_api(
raw_stream_dict["can_delete_own_message_group_id"], anonymous_group_membership
)
can_move_messages_out_of_channel_group_value = get_group_setting_value_for_register_api(
raw_stream_dict["can_move_messages_out_of_channel_group_id"], anonymous_group_membership
)
@@ -418,6 +431,7 @@ def build_stream_dict_for_never_sub(
can_add_subscribers_group=can_add_subscribers_group_value,
can_administer_channel_group=can_administer_channel_group_value,
can_delete_any_message_group=can_delete_any_message_group_value,
can_delete_own_message_group=can_delete_own_message_group_value,
can_move_messages_out_of_channel_group=can_move_messages_out_of_channel_group_value,
can_move_messages_within_channel_group=can_move_messages_within_channel_group_value,
can_send_message_group=can_send_message_group_value,

View File

@@ -161,6 +161,7 @@ class RawStreamDict(TypedDict):
can_add_subscribers_group_id: int
can_administer_channel_group_id: int
can_delete_any_message_group_id: int
can_delete_own_message_group_id: int
can_move_messages_out_of_channel_group_id: int
can_move_messages_within_channel_group_id: int
can_send_message_group_id: int
@@ -214,6 +215,7 @@ class SubscriptionStreamDict(TypedDict):
can_add_subscribers_group: int | UserGroupMembersDict
can_administer_channel_group: int | UserGroupMembersDict
can_delete_any_message_group: int | UserGroupMembersDict
can_delete_own_message_group: int | UserGroupMembersDict
can_move_messages_out_of_channel_group: int | UserGroupMembersDict
can_move_messages_within_channel_group: int | UserGroupMembersDict
can_send_message_group: int | UserGroupMembersDict
@@ -256,6 +258,7 @@ class NeverSubscribedStreamDict(TypedDict):
can_add_subscribers_group: int | UserGroupMembersDict
can_administer_channel_group: int | UserGroupMembersDict
can_delete_any_message_group: int | UserGroupMembersDict
can_delete_own_message_group: int | UserGroupMembersDict
can_move_messages_out_of_channel_group: int | UserGroupMembersDict
can_move_messages_within_channel_group: int | UserGroupMembersDict
can_send_message_group: int | UserGroupMembersDict
@@ -294,6 +297,7 @@ class DefaultStreamDict(TypedDict):
can_add_subscribers_group: int | UserGroupMembersDict
can_administer_channel_group: int | UserGroupMembersDict
can_delete_any_message_group: int | UserGroupMembersDict
can_delete_own_message_group: int | UserGroupMembersDict
can_move_messages_out_of_channel_group: int | UserGroupMembersDict
can_move_messages_within_channel_group: int | UserGroupMembersDict
can_send_message_group: int | UserGroupMembersDict

View File

@@ -0,0 +1,23 @@
# Generated by Django 5.2.3 on 2025-06-26 13:21
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("zerver", "0733_alter_stream_can_delete_any_message_group"),
]
operations = [
migrations.AddField(
model_name="stream",
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,56 @@
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_delete_own_message_group(
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
) -> None:
Stream = apps.get_model("zerver", "stream")
NamedUserGroup = apps.get_model("zerver", "NamedUserGroup")
BATCH_SIZE = 1000
max_id = Stream.objects.filter(can_delete_own_message_group=None).aggregate(Max("id"))[
"id__max"
]
if max_id is None:
# Do nothing if there are no channels on the server.
return
lower_bound = Stream.objects.filter(can_delete_own_message_group=None).aggregate(Min("id"))[
"id__min"
]
while lower_bound <= max_id + BATCH_SIZE / 2:
upper_bound = lower_bound + BATCH_SIZE - 1
print(f"Processing batch {lower_bound} to {upper_bound} for Stream")
with transaction.atomic():
Stream.objects.filter(
id__range=(lower_bound, upper_bound),
can_delete_own_message_group=None,
).update(
can_delete_own_message_group=NamedUserGroup.objects.filter(
name="role:nobody",
realm_for_sharding=OuterRef("realm_id"),
is_system_group=True,
).values("pk")
)
lower_bound += BATCH_SIZE
class Migration(migrations.Migration):
atomic = False
dependencies = [
("zerver", "0734_stream_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.2.3 on 2025-06-26 13:24
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("zerver", "0735_set_default_value_for_can_delete_own_message_group"),
]
operations = [
migrations.AlterField(
model_name="stream",
name="can_delete_own_message_group",
field=models.ForeignKey(
on_delete=django.db.models.deletion.RESTRICT,
related_name="+",
to="zerver.usergroup",
),
),
]

View File

@@ -150,6 +150,9 @@ class Stream(models.Model):
can_delete_any_message_group = models.ForeignKey(
UserGroup, on_delete=models.RESTRICT, related_name="+"
)
can_delete_own_message_group = models.ForeignKey(
UserGroup, on_delete=models.RESTRICT, related_name="+"
)
can_move_messages_out_of_channel_group = models.ForeignKey(
UserGroup, on_delete=models.RESTRICT, related_name="+"
)
@@ -193,6 +196,11 @@ class Stream(models.Model):
allow_everyone_group=True,
default_group_name=SystemGroups.NOBODY,
),
"can_delete_own_message_group": GroupPermissionSetting(
allow_nobody_group=True,
allow_everyone_group=True,
default_group_name=SystemGroups.NOBODY,
),
"can_move_messages_out_of_channel_group": GroupPermissionSetting(
allow_nobody_group=True,
allow_everyone_group=True,
@@ -285,6 +293,7 @@ class Stream(models.Model):
"can_add_subscribers_group_id",
"can_administer_channel_group_id",
"can_delete_any_message_group_id",
"can_delete_own_message_group_id",
"can_move_messages_out_of_channel_group_id",
"can_move_messages_within_channel_group_id",
"can_send_message_group_id",

View File

@@ -11818,6 +11818,8 @@ paths:
$ref: "#/components/schemas/CanAdministerChannelGroup"
can_delete_any_message_group:
$ref: "#/components/schemas/CanDeleteAnyMessageGroup"
can_delete_own_message_group:
$ref: "#/components/schemas/CanDeleteOwnMessageGroup"
can_move_messages_out_of_channel_group:
$ref: "#/components/schemas/CanMoveMessagesOutOfChannelGroup"
can_move_messages_within_channel_group:
@@ -11885,6 +11887,8 @@ paths:
contentType: application/json
can_delete_any_message_group:
contentType: application/json
can_delete_own_message_group:
contentType: application/json
can_move_messages_out_of_channel_group:
contentType: application/json
can_move_messages_within_channel_group:
@@ -16644,6 +16648,7 @@ paths:
can_remove_subscribers_group: {}
can_administer_channel_group: {}
can_delete_any_message_group: {}
can_delete_own_message_group: {}
can_move_messages_out_of_channel_group: {}
can_move_messages_within_channel_group: {}
can_send_message_group: {}
@@ -21965,6 +21970,7 @@ paths:
can_remove_subscribers_group: {}
can_administer_channel_group: {}
can_delete_any_message_group: {}
can_delete_own_message_group: {}
can_move_messages_out_of_channel_group: {}
can_move_messages_within_channel_group: {}
can_send_message_group: {}
@@ -22416,6 +22422,33 @@ paths:
"old": 15,
}
- $ref: "#/components/schemas/GroupSettingValueUpdate"
can_delete_own_message_group:
allOf:
- description: |
The set of users who have permission to delete the messages that they have
sent in the channel expressed as an [update to a group-setting value][update-group-setting].
[update-group-setting]: /api/group-setting-values#updating-group-setting-values
Note that a user must [have content access](/help/channel-permissions) to a
channel in order to delete their own message in the channel.
Users with permission to delete any message in the channel
and users present in the organization-level `can_delete_own_message_group` setting
can always delete their own messages in the channel if they
[have content access](/help/channel-permissions) to that channel.
**Changes**: New in Zulip 11.0 (feature level ZF-e165db). Prior to this
change, only the users in the organization-level `can_delete_any_message_group`
and `can_delete_own_message_group` settings were able delete their own messages in
the organization.
example:
{
"new":
{"direct_members": [10], "direct_subgroups": [11]},
"old": 15,
}
- $ref: "#/components/schemas/GroupSettingValueUpdate"
can_move_messages_out_of_channel_group:
allOf:
- description: |
@@ -22554,6 +22587,8 @@ paths:
contentType: application/json
can_delete_any_message_group:
contentType: application/json
can_delete_own_message_group:
contentType: application/json
can_move_messages_out_of_channel_group:
contentType: application/json
can_move_messages_within_channel_group:
@@ -24549,6 +24584,7 @@ components:
can_remove_subscribers_group: {}
can_administer_channel_group: {}
can_delete_any_message_group: {}
can_delete_own_message_group: {}
can_move_messages_out_of_channel_group: {}
can_move_messages_within_channel_group: {}
can_send_message_group: {}
@@ -24751,6 +24787,8 @@ components:
$ref: "#/components/schemas/CanAdministerChannelGroup"
can_delete_any_message_group:
$ref: "#/components/schemas/CanDeleteAnyMessageGroup"
can_delete_own_message_group:
$ref: "#/components/schemas/CanDeleteOwnMessageGroup"
can_move_messages_out_of_channel_group:
$ref: "#/components/schemas/CanMoveMessagesOutOfChannelGroup"
can_move_messages_within_channel_group:
@@ -25887,6 +25925,8 @@ components:
$ref: "#/components/schemas/CanAdministerChannelGroup"
can_delete_any_message_group:
$ref: "#/components/schemas/CanDeleteAnyMessageGroup"
can_delete_own_message_group:
$ref: "#/components/schemas/CanDeleteOwnMessageGroup"
can_move_messages_out_of_channel_group:
$ref: "#/components/schemas/CanMoveMessagesOutOfChannelGroup"
can_move_messages_within_channel_group:
@@ -27877,6 +27917,29 @@ components:
change, only the users in `can_delete_any_message_group` were able
delete any message in the organization.
[setting-values]: /api/group-setting-values
CanDeleteOwnMessageGroup:
allOf:
- $ref: "#/components/schemas/GroupSettingValue"
- description: |
A [group-setting value][setting-values] defining the set of users
who have permission to delete the messages that they have sent in the channel.
[update-group-setting]: /api/group-setting-values#updating-group-setting-values
Note that a user must [have content access](/help/channel-permissions) to a
channel in order to delete their own message in the channel.
Users with permission to delete any message in the channel
and users present in the organization-level `can_delete_own_message_group` setting
can always delete their own messages in the channel if they
[have content access](/help/channel-permissions) to that channel.
**Changes**: New in Zulip 11.0 (feature level ZF-e165db). Prior to this
change, only the users in the organization-level `can_delete_any_message_group`
and `can_delete_own_message_group` settings were able delete their own messages in
the organization.
[setting-values]: /api/group-setting-values
CanMoveMessagesOutOfChannelGroup:
allOf:

View File

@@ -664,9 +664,15 @@ class DeleteMessageTest(ZulipTestCase):
)
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, is_stream_message: bool = True
) -> None:
sender = self.example_user(sender_name)
if is_stream_message:
msg_id = self.send_stream_message(sender, "Verona")
else:
msg_id = self.send_personal_message(sender, self.example_user("desdemona"))
self.login_user(sender)
result = self.client_delete(f"/json/messages/{msg_id}")
if error_msg is None:
@@ -675,6 +681,8 @@ class DeleteMessageTest(ZulipTestCase):
self.assert_json_error(result, error_msg)
realm = get_realm("zulip")
stream = get_stream("Verona", realm)
iago = self.example_user("iago")
administrators_system_group = NamedUserGroup.objects.get(
name=SystemGroups.ADMINISTRATORS, realm=realm, is_system_group=True
@@ -688,6 +696,9 @@ class DeleteMessageTest(ZulipTestCase):
members_system_group = NamedUserGroup.objects.get(
name=SystemGroups.MEMBERS, realm=realm, is_system_group=True
)
nobody_system_group = NamedUserGroup.objects.get(
name=SystemGroups.NOBODY, realm=realm, is_system_group=True
)
do_change_realm_permission_group_setting(
realm,
@@ -695,6 +706,12 @@ class DeleteMessageTest(ZulipTestCase):
administrators_system_group,
acting_user=None,
)
do_change_realm_permission_group_setting(
realm,
"can_delete_any_message_group",
nobody_system_group,
acting_user=None,
)
check_delete_message_by_sender("shiva", "You don't have permission to delete this message")
check_delete_message_by_sender("iago")
@@ -726,6 +743,53 @@ class DeleteMessageTest(ZulipTestCase):
check_delete_message_by_sender("cordelia")
check_delete_message_by_sender("polonius")
do_change_realm_permission_group_setting(
realm, "can_delete_own_message_group", nobody_system_group, acting_user=None
)
do_change_stream_group_based_setting(
stream,
"can_delete_own_message_group",
members_system_group,
acting_user=iago,
)
# Users in per-channel `can_delete_own_message_group` can delete their
# own messages.
check_delete_message_by_sender("hamlet")
check_delete_message_by_sender(
"polonius", "You don't have permission to delete this message"
)
do_change_stream_group_based_setting(
stream,
"can_delete_own_message_group",
nobody_system_group,
acting_user=iago,
)
do_change_stream_group_based_setting(
stream,
"can_administer_channel_group",
moderators_system_group,
acting_user=iago,
)
# Channel administrators can't delete messages if they don't have
# the required permissions.
check_delete_message_by_sender("shiva", "You don't have permission to delete this message")
check_delete_message_by_sender("iago", "You don't have permission to delete this message")
do_change_stream_group_based_setting(
stream,
"can_delete_own_message_group",
everyone_system_group,
acting_user=iago,
)
check_delete_message_by_sender("iago")
# Cannot delete DMs as organization-level permission is set to nobody.
check_delete_message_by_sender(
"iago", "You don't have permission to delete this message", is_stream_message=False
)
def test_delete_event_sent_after_transaction_commits(self) -> None:
"""
Tests that `send_event_rollback_unsafe` is hooked to `transaction.on_commit`.

View File

@@ -23,14 +23,14 @@ from zerver.lib.message import (
)
from zerver.lib.request import RequestNotes
from zerver.lib.response import json_success
from zerver.lib.streams import can_delete_any_message_in_channel
from zerver.lib.streams import can_delete_any_message_in_channel, can_delete_own_message_in_channel
from zerver.lib.timestamp import datetime_to_timestamp
from zerver.lib.topic import maybe_rename_empty_topic_to_general_chat
from zerver.lib.typed_endpoint import OptionalTopic, PathOnly, typed_endpoint
from zerver.lib.types import EditHistoryEvent, FormattedEditHistoryEvent
from zerver.models import Message, UserProfile
from zerver.models.realms import MessageEditHistoryVisibilityPolicyEnum
from zerver.models.streams import get_stream_by_id_in_realm
from zerver.models.streams import Stream, get_stream_by_id_in_realm
def fill_edit_history_entries(
@@ -184,6 +184,7 @@ def validate_can_delete_message(user_profile: UserProfile, message: Message) ->
if user_profile.can_delete_any_message():
return
stream: Stream | None = None
if message.is_stream_message():
stream = get_stream_by_id_in_realm(message.recipient.type_id, user_profile.realm)
if can_delete_any_message_in_channel(user_profile, stream):
@@ -192,8 +193,16 @@ def validate_can_delete_message(user_profile: UserProfile, message: Message) ->
if message.sender != user_profile and message.sender.bot_owner_id != user_profile.id:
# Users can only delete messages sent by them or by their bots.
raise JsonableError(_("You don't have permission to delete this message"))
if not user_profile.can_delete_own_message():
# Only user with roles as allowed by can_delete_own_message_group can delete message.
if not message.is_stream_message():
raise JsonableError(_("You don't have permission to delete this message"))
assert stream is not None
# For channel messages, users are required to have either the
# channel-level permission or the organization-level permission to delete
# their own messages.
if not can_delete_own_message_in_channel(user_profile, stream):
raise JsonableError(_("You don't have permission to delete this message"))
deadline_seconds: int | None = user_profile.realm.message_content_delete_limit_seconds

View File

@@ -288,6 +288,7 @@ def update_stream_backend(
can_add_subscribers_group: Json[GroupSettingChangeRequest] | None = None,
can_administer_channel_group: Json[GroupSettingChangeRequest] | None = None,
can_delete_any_message_group: Json[GroupSettingChangeRequest] | None = None,
can_delete_own_message_group: Json[GroupSettingChangeRequest] | None = None,
can_move_messages_out_of_channel_group: Json[GroupSettingChangeRequest] | None = None,
can_move_messages_within_channel_group: Json[GroupSettingChangeRequest] | None = None,
can_remove_subscribers_group: Json[GroupSettingChangeRequest] | None = None,
@@ -693,6 +694,7 @@ def add_subscriptions_backend(
authorization_errors_fatal: Json[bool] = True,
can_add_subscribers_group: Json[int | UserGroupMembersData] | None = None,
can_delete_any_message_group: Json[int | UserGroupMembersData] | None = None,
can_delete_own_message_group: Json[int | UserGroupMembersData] | None = None,
can_administer_channel_group: Json[int | UserGroupMembersData] | None = None,
can_move_messages_out_of_channel_group: Json[int | UserGroupMembersData] | None = None,
can_move_messages_within_channel_group: Json[int | UserGroupMembersData] | None = None,
@@ -770,6 +772,9 @@ def add_subscriptions_backend(
stream_dict_copy["can_delete_any_message_group"] = group_settings_map[
"can_delete_any_message_group"
]
stream_dict_copy["can_delete_own_message_group"] = group_settings_map[
"can_delete_own_message_group"
]
stream_dict_copy["can_move_messages_out_of_channel_group"] = group_settings_map[
"can_move_messages_out_of_channel_group"
]