mirror of
https://github.com/zulip/zulip.git
synced 2025-10-23 04:52:12 +00:00
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:
10
api_docs/unmerged.d/ZF-e165db.md
Normal file
10
api_docs/unmerged.d/ZF-e165db.md
Normal 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.
|
@@ -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",
|
||||
|
@@ -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;
|
||||
|
@@ -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],
|
||||
|
@@ -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",
|
||||
|
@@ -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],
|
||||
|
@@ -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,
|
||||
|
@@ -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">
|
||||
|
@@ -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,
|
||||
|
@@ -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);
|
||||
});
|
||||
|
@@ -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"
|
||||
],
|
||||
|
@@ -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
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
|
@@ -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",
|
||||
),
|
||||
),
|
||||
]
|
@@ -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,
|
||||
)
|
||||
]
|
@@ -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",
|
||||
),
|
||||
),
|
||||
]
|
@@ -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",
|
||||
|
@@ -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:
|
||||
|
@@ -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`.
|
||||
|
@@ -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
|
||||
|
@@ -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"
|
||||
]
|
||||
|
Reference in New Issue
Block a user