settings: Add `can_add_custom_emoji_group` realm setting.

Added `can_add_custom_emoji_group` setting to replace `add_custom_emoji_policy`.
This commit is contained in:
Vector73 2024-10-13 12:39:11 +05:30 committed by Tim Abbott
parent b95225d071
commit f733ab112c
26 changed files with 296 additions and 58 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 307**
* `PATCH /realm`, [`GET /events`](/api/get-events),
[`POST /register`](/api/register-queue):
Added `can_add_custom_emoji_group` realm setting which is a
[group-setting value](/api/group-setting-values) describing the set of users
with permission to add custom emoji in the organization.
**Feature level 306** **Feature level 306**
* [`GET /events`](/api/get-events): Removed the `extra_data` optional * [`GET /events`](/api/get-events): Removed the `extra_data` optional

View File

@ -34,7 +34,7 @@ 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 = 306 # Last bumped for adding `max_file_upload_size_mib`. API_FEATURE_LEVEL = 307 # Last bumped for can_add_custom_emoji_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

@ -21,6 +21,7 @@ import * as settings_sections from "./settings_sections";
import * as settings_toggle from "./settings_toggle"; import * as settings_toggle from "./settings_toggle";
import * as settings_users from "./settings_users"; import * as settings_users from "./settings_users";
import {current_user, realm} from "./state_data"; import {current_user, realm} from "./state_data";
import * as user_groups from "./user_groups";
const admin_settings_label = { const admin_settings_label = {
// Organization profile // Organization profile
@ -125,7 +126,6 @@ export function build_page() {
realm_require_unique_names: realm.realm_require_unique_names, realm_require_unique_names: realm.realm_require_unique_names,
realm_email_changes_disabled: realm.realm_email_changes_disabled, realm_email_changes_disabled: realm.realm_email_changes_disabled,
realm_avatar_changes_disabled: realm.realm_avatar_changes_disabled, realm_avatar_changes_disabled: realm.realm_avatar_changes_disabled,
realm_add_custom_emoji_policy: realm.realm_add_custom_emoji_policy,
can_add_emojis: settings_data.user_can_add_custom_emoji(), can_add_emojis: settings_data.user_can_add_custom_emoji(),
can_create_new_bots: settings_bots.can_create_new_bots(), can_create_new_bots: settings_bots.can_create_new_bots(),
realm_message_content_edit_limit_minutes: realm_message_content_edit_limit_minutes:
@ -186,6 +186,10 @@ export function build_page() {
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_can_delete_own_message_group: realm.realm_can_delete_own_message_group, realm_can_delete_own_message_group: realm.realm_can_delete_own_message_group,
realm_can_add_custom_emoji_group: realm.realm_can_add_custom_emoji_group,
realm_can_add_custom_emoji_group_name: user_groups.get_user_group_from_id(
realm.realm_can_add_custom_emoji_group,
).name,
...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

@ -205,7 +205,6 @@ export function dispatch_normal_event(event) {
case "realm": { case "realm": {
const realm_settings = { const realm_settings = {
add_custom_emoji_policy: settings_emoji.update_custom_emoji_ui,
allow_edit_history: noop, allow_edit_history: noop,
allow_message_editing: noop, allow_message_editing: noop,
edit_topic_policy: noop, edit_topic_policy: noop,
@ -300,6 +299,10 @@ export function dispatch_normal_event(event) {
gear_menu.rerender(); gear_menu.rerender();
} }
if (key === "can_add_custom_emoji_group") {
settings_emoji.update_custom_emoji_ui();
}
if ( if (
key === "can_create_public_channel_group" || key === "can_create_public_channel_group" ||
key === "can_create_private_channel_group" || key === "can_create_private_channel_group" ||

View File

@ -218,7 +218,6 @@ export function get_subsection_property_elements($subsection: JQuery): HTMLEleme
type simple_dropdown_realm_settings = Pick< type simple_dropdown_realm_settings = Pick<
typeof realm, typeof realm,
| "realm_invite_to_stream_policy" | "realm_invite_to_stream_policy"
| "realm_add_custom_emoji_policy"
| "realm_invite_to_realm_policy" | "realm_invite_to_realm_policy"
| "realm_wildcard_mention_policy" | "realm_wildcard_mention_policy"
| "realm_move_messages_between_streams_policy" | "realm_move_messages_between_streams_policy"
@ -482,6 +481,7 @@ const dropdown_widget_map = new Map<string, DropdownWidget | null>([
["can_remove_subscribers_group", null], ["can_remove_subscribers_group", null],
["realm_can_access_all_users_group", null], ["realm_can_access_all_users_group", null],
["can_mention_group", null], ["can_mention_group", null],
["realm_can_add_custom_emoji_group", null],
["realm_can_create_groups", null], ["realm_can_create_groups", null],
["realm_can_create_public_channel_group", null], ["realm_can_create_public_channel_group", null],
["realm_can_create_private_channel_group", null], ["realm_can_create_private_channel_group", null],
@ -807,6 +807,7 @@ export function check_realm_settings_property_changed(elem: HTMLElement): boolea
case "realm_default_code_block_language": case "realm_default_code_block_language":
case "realm_create_multiuse_invite_group": case "realm_create_multiuse_invite_group":
case "realm_can_access_all_users_group": case "realm_can_access_all_users_group":
case "realm_can_add_custom_emoji_group":
case "realm_can_create_groups": case "realm_can_create_groups":
case "realm_can_create_public_channel_group": case "realm_can_create_public_channel_group":
case "realm_can_create_private_channel_group": case "realm_can_create_private_channel_group":
@ -1047,6 +1048,7 @@ export function populate_data_for_realm_settings_request(
} }
const realm_group_settings_using_new_api_format = new Set([ const realm_group_settings_using_new_api_format = new Set([
"can_add_custom_emoji_group",
"can_create_groups", "can_create_groups",
"can_create_private_channel_group", "can_create_private_channel_group",
"can_create_public_channel_group", "can_create_public_channel_group",

View File

@ -240,7 +240,11 @@ export function user_can_create_user_groups(): boolean {
} }
export function user_can_add_custom_emoji(): boolean { export function user_can_add_custom_emoji(): boolean {
return user_has_permission(realm.realm_add_custom_emoji_policy); return user_has_permission_for_group_setting(
realm.realm_can_add_custom_emoji_group,
"can_add_custom_emoji_group",
"realm",
);
} }
export function user_can_move_messages_to_another_topic(): boolean { export function user_can_move_messages_to_another_topic(): boolean {

View File

@ -18,11 +18,11 @@ import * as ListWidget from "./list_widget";
import * as loading from "./loading"; import * as loading from "./loading";
import * as people from "./people"; import * as people from "./people";
import * as scroll_util from "./scroll_util"; import * as scroll_util from "./scroll_util";
import * as settings_config from "./settings_config";
import * as settings_data from "./settings_data"; import * as settings_data from "./settings_data";
import {current_user, realm} from "./state_data"; import {current_user, realm} from "./state_data";
import * as ui_report from "./ui_report"; import * as ui_report from "./ui_report";
import * as upload_widget from "./upload_widget"; import * as upload_widget from "./upload_widget";
import * as user_groups from "./user_groups";
import * as util from "./util"; import * as util from "./util";
const meta = { const meta = {
@ -45,8 +45,9 @@ function can_delete_emoji(emoji: ServerEmoji): boolean {
export function update_custom_emoji_ui(): void { export function update_custom_emoji_ui(): void {
const rendered_tip = render_settings_emoji_settings_tip({ const rendered_tip = render_settings_emoji_settings_tip({
realm_add_custom_emoji_policy: realm.realm_add_custom_emoji_policy, realm_can_add_custom_emoji_group_name: user_groups.get_user_group_from_id(
policy_values: settings_config.common_policy_values, realm.realm_can_add_custom_emoji_group,
).name,
}); });
$("#emoji-settings").find(".emoji-settings-tip-container").html(rendered_tip); $("#emoji-settings").find(".emoji-settings-tip-container").html(rendered_tip);
if (!settings_data.user_can_add_custom_emoji()) { if (!settings_data.user_can_add_custom_emoji()) {

View File

@ -125,7 +125,6 @@ export function get_org_type_dropdown_options() {
const simple_dropdown_properties = [ const simple_dropdown_properties = [
"realm_invite_to_stream_policy", "realm_invite_to_stream_policy",
"realm_add_custom_emoji_policy",
"realm_invite_to_realm_policy", "realm_invite_to_realm_policy",
"realm_wildcard_mention_policy", "realm_wildcard_mention_policy",
"realm_move_messages_between_streams_policy", "realm_move_messages_between_streams_policy",
@ -509,6 +508,7 @@ export function discard_realm_property_element_changes(elem) {
case "realm_create_multiuse_invite_group": case "realm_create_multiuse_invite_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":
case "realm_can_add_custom_emoji_group":
case "realm_can_access_all_users_group": case "realm_can_access_all_users_group":
case "realm_can_create_groups": case "realm_can_create_groups":
case "realm_can_create_public_channel_group": case "realm_can_create_public_channel_group":

View File

@ -267,7 +267,6 @@ const realm_schema = z.object({
max_topic_length: z.number(), max_topic_length: z.number(),
password_min_guesses: z.number(), password_min_guesses: z.number(),
password_min_length: z.number(), password_min_length: z.number(),
realm_add_custom_emoji_policy: z.number(),
realm_allow_edit_history: z.boolean(), realm_allow_edit_history: z.boolean(),
realm_allow_message_editing: z.boolean(), realm_allow_message_editing: z.boolean(),
realm_authentication_methods: z.record( realm_authentication_methods: z.record(
@ -287,6 +286,7 @@ const realm_schema = z.object({
realm_bot_creation_policy: z.number(), realm_bot_creation_policy: z.number(),
realm_bot_domain: z.string(), realm_bot_domain: z.string(),
realm_can_access_all_users_group: z.number(), realm_can_access_all_users_group: z.number(),
realm_can_add_custom_emoji_group: z.number(),
realm_can_create_groups: z.number(), realm_can_create_groups: z.number(),
realm_can_create_public_channel_group: z.number(), realm_can_create_public_channel_group: z.number(),
realm_can_create_private_channel_group: z.number(), realm_can_create_private_channel_group: z.number(),

View File

@ -1,10 +1,10 @@
{{#if is_guest}} {{#if is_guest}}
<div class='tip'>{{t "Guests cannot edit custom emoji." }}</div> <div class='tip'>{{t "Guests cannot edit custom emoji." }}</div>
{{else if (eq realm_add_custom_emoji_policy policy_values.by_admins_only.code) }} {{else if (eq realm_can_add_custom_emoji_group_name "role:administrators") }}
<div class='tip'>{{t "This organization is configured so that only administrators can add custom emoji." }}</div> <div class='tip'>{{t "This organization is configured so that only administrators can add custom emoji." }}</div>
{{else if (eq realm_add_custom_emoji_policy policy_values.by_moderators_only.code)}} {{else if (eq realm_can_add_custom_emoji_group_name "role:moderators")}}
<div class='tip'>{{t 'This organization is configured so that administrators and moderators can add custom emoji.'}}</div> <div class='tip'>{{t 'This organization is configured so that administrators and moderators can add custom emoji.'}}</div>
{{else if (eq realm_add_custom_emoji_policy policy_values.by_full_members.code)}} {{else if (eq realm_can_add_custom_emoji_group_name "role:fullmembers")}}
<div class='tip'>{{t 'This organization is configured so that full members can add custom emoji.'}}</div> <div class='tip'>{{t 'This organization is configured so that full members can add custom emoji.'}}</div>
{{else}} {{else}}
<div class='tip'>{{t "This organization is configured so that any member of this organization can add custom emoji." }}</div> <div class='tip'>{{t "This organization is configured so that any member of this organization can add custom emoji." }}</div>

View File

@ -350,12 +350,10 @@
value_type="number" value_type="number"
is_setting_disabled=(not is_owner)}} is_setting_disabled=(not is_owner)}}
<div class="input-group"> {{> ../dropdown_widget_with_label
<label for="realm_add_custom_emoji_policy" class="settings-field-label">{{t "Who can add custom emoji" }}</label> widget_name="realm_can_add_custom_emoji_group"
<select name="realm_add_custom_emoji_policy" class="setting-widget prop-element settings_select bootstrap-focus-style" id="id_realm_add_custom_emoji_policy" data-setting-widget-type="number"> label=(t 'Who can add custom emoji')
{{> dropdown_options_widget option_values=common_policy_values}} value_type="number"}}
</select>
</div>
</div> </div>
</div> </div>
</form> </form>

View File

@ -462,6 +462,7 @@ run_test("realm settings", ({override}) => {
override(settings_org, "check_disable_direct_message_initiator_group_dropdown", noop); override(settings_org, "check_disable_direct_message_initiator_group_dropdown", noop);
override(settings_org, "sync_realm_settings", noop); override(settings_org, "sync_realm_settings", noop);
override(settings_bots, "update_bot_permissions_ui", noop); override(settings_bots, "update_bot_permissions_ui", noop);
override(settings_emoji, "update_custom_emoji_ui", noop);
override(settings_invites, "update_invite_user_panel", noop); override(settings_invites, "update_invite_user_panel", noop);
override(sidebar_ui, "update_invite_user_option", noop); override(sidebar_ui, "update_invite_user_option", noop);
override(gear_menu, "rerender", noop); override(gear_menu, "rerender", noop);
@ -582,6 +583,7 @@ run_test("realm settings", ({override}) => {
override(realm, "realm_message_content_edit_limit_seconds", 0); override(realm, "realm_message_content_edit_limit_seconds", 0);
override(realm, "realm_edit_topic_policy", 3); override(realm, "realm_edit_topic_policy", 3);
override(realm, "realm_authentication_methods", {Google: {enabled: false, available: true}}); override(realm, "realm_authentication_methods", {Google: {enabled: false, available: true}});
override(realm, "realm_can_add_custom_emoji_group", 1);
override(realm, "realm_can_create_public_channel_group", 1); override(realm, "realm_can_create_public_channel_group", 1);
override(realm, "realm_direct_message_permission_group", 1); override(realm, "realm_direct_message_permission_group", 1);
override(realm, "realm_plan_type", 2); override(realm, "realm_plan_type", 2);
@ -596,6 +598,7 @@ run_test("realm settings", ({override}) => {
assert_same(realm.realm_authentication_methods, { assert_same(realm.realm_authentication_methods, {
Google: {enabled: true, available: true}, Google: {enabled: true, available: true},
}); });
assert_same(realm.realm_can_add_custom_emoji_group, 3);
assert_same(realm.realm_can_create_public_channel_group, 3); assert_same(realm.realm_can_create_public_channel_group, 3);
assert_same(realm.realm_direct_message_permission_group, 3); assert_same(realm.realm_direct_message_permission_group, 3);
assert_same(realm.realm_plan_type, 3); assert_same(realm.realm_plan_type, 3);

View File

@ -368,6 +368,7 @@ exports.fixtures = {
authentication_methods: { authentication_methods: {
Google: {enabled: true, available: true}, Google: {enabled: true, available: true},
}, },
can_add_custom_emoji_group: 3,
can_create_public_channel_group: 3, can_create_public_channel_group: 3,
direct_message_permission_group: 3, direct_message_permission_group: 3,
plan_type: 3, plan_type: 3,

View File

@ -167,11 +167,6 @@ test_policy(
"realm_move_messages_between_streams_policy", "realm_move_messages_between_streams_policy",
settings_data.user_can_move_messages_between_streams, settings_data.user_can_move_messages_between_streams,
); );
test_policy(
"user_can_add_custom_emoji",
"realm_add_custom_emoji_policy",
settings_data.user_can_add_custom_emoji,
);
function test_message_policy(label, policy, validation_func) { function test_message_policy(label, policy, validation_func) {
run_test(label, ({override}) => { run_test(label, ({override}) => {
@ -244,6 +239,11 @@ run_test("user_can_move_messages_between_streams_nobody_case", ({override}) => {
assert.equal(settings_data.user_can_move_messages_between_streams(), false); assert.equal(settings_data.user_can_move_messages_between_streams(), false);
}); });
test_realm_group_settings(
"realm_can_add_custom_emoji_group",
settings_data.user_can_add_custom_emoji,
);
test_realm_group_settings( test_realm_group_settings(
"realm_can_delete_any_message_group", "realm_can_delete_any_message_group",
settings_data.user_can_delete_any_message, settings_data.user_can_delete_any_message,

View File

@ -102,7 +102,6 @@ function createSaveButtons(subsection) {
function test_submit_settings_form(override, submit_form) { function test_submit_settings_form(override, submit_form) {
Object.assign(realm, { Object.assign(realm, {
realm_bot_creation_policy: settings_bots.bot_creation_policy_values.restricted.code, realm_bot_creation_policy: settings_bots.bot_creation_policy_values.restricted.code,
realm_add_custom_emoji_policy: settings_config.common_policy_values.by_admins_only.code,
realm_waiting_period_threshold: 1, realm_waiting_period_threshold: 1,
realm_default_language: '"es"', realm_default_language: '"es"',
realm_invite_to_stream_policy: settings_config.common_policy_values.by_admins_only.code, realm_invite_to_stream_policy: settings_config.common_policy_values.by_admins_only.code,
@ -138,11 +137,6 @@ function test_submit_settings_form(override, submit_form) {
$invite_to_stream_policy_elem.attr("id", "id_realm_invite_to_stream_policy"); $invite_to_stream_policy_elem.attr("id", "id_realm_invite_to_stream_policy");
$invite_to_stream_policy_elem.data = () => "number"; $invite_to_stream_policy_elem.data = () => "number";
const $add_custom_emoji_policy_elem = $("#id_realm_add_custom_emoji_policy");
$add_custom_emoji_policy_elem.val("1");
$add_custom_emoji_policy_elem.attr("id", "id_realm_add_custom_emoji_policy");
$add_custom_emoji_policy_elem.data = () => "number";
const $bot_creation_policy_elem = $("#id_realm_bot_creation_policy"); const $bot_creation_policy_elem = $("#id_realm_bot_creation_policy");
$bot_creation_policy_elem.val("1"); $bot_creation_policy_elem.val("1");
$bot_creation_policy_elem.attr("id", "id_realm_bot_creation_policy"); $bot_creation_policy_elem.attr("id", "id_realm_bot_creation_policy");
@ -156,7 +150,6 @@ function test_submit_settings_form(override, submit_form) {
let $subsection_elem = $(`#org-${CSS.escape(subsection)}`); let $subsection_elem = $(`#org-${CSS.escape(subsection)}`);
$subsection_elem.set_find_results(".prop-element", [ $subsection_elem.set_find_results(".prop-element", [
$bot_creation_policy_elem, $bot_creation_policy_elem,
$add_custom_emoji_policy_elem,
$invite_to_realm_policy_elem, $invite_to_realm_policy_elem,
$invite_to_stream_policy_elem, $invite_to_stream_policy_elem,
]); ]);
@ -169,7 +162,6 @@ function test_submit_settings_form(override, submit_form) {
bot_creation_policy: 1, bot_creation_policy: 1,
invite_to_realm_policy: 2, invite_to_realm_policy: 2,
invite_to_stream_policy: 1, invite_to_stream_policy: 1,
add_custom_emoji_policy: 1,
}; };
assert.deepEqual(data, expected_value); assert.deepEqual(data, expected_value);

View File

@ -35,6 +35,9 @@ mock_esm("../src/compose_state", {
mock_esm("../src/pm_list", { mock_esm("../src/pm_list", {
update_private_messages() {}, update_private_messages() {},
}); });
mock_esm("../src/settings", {
update_lock_icon_in_sidebar() {},
});
mock_esm("../src/settings_linkifiers", { mock_esm("../src/settings_linkifiers", {
maybe_disable_widgets() {}, maybe_disable_widgets() {},
}); });

View File

@ -1059,6 +1059,7 @@ group_setting_update_data_type = DictType(
optional_keys=[ optional_keys=[
("create_multiuse_invite_group", int), ("create_multiuse_invite_group", int),
("can_access_all_users_group", int), ("can_access_all_users_group", int),
("can_add_custom_emoji_group", group_setting_type),
("can_create_groups", group_setting_type), ("can_create_groups", group_setting_type),
("can_create_public_channel_group", group_setting_type), ("can_create_public_channel_group", group_setting_type),
("can_create_private_channel_group", group_setting_type), ("can_create_private_channel_group", group_setting_type),

View File

@ -0,0 +1,23 @@
# Generated by Django 5.0.9 on 2024-10-04 07:01
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("zerver", "0602_remap_can_manage_all_groups"),
]
operations = [
migrations.AddField(
model_name="realm",
name="can_add_custom_emoji_group",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.RESTRICT,
related_name="+",
to="zerver.usergroup",
),
),
]

View File

@ -0,0 +1,43 @@
# 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_add_custom_emoji_group(
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
) -> None:
Realm = apps.get_model("zerver", "Realm")
NamedUserGroup = apps.get_model("zerver", "NamedUserGroup")
add_custom_emoji_policy_to_group_name = {
1: "role:members",
2: "role:administrators",
3: "role:fullmembers",
4: "role:moderators",
}
for id, group_name in add_custom_emoji_policy_to_group_name.items():
Realm.objects.filter(can_add_custom_emoji_group=None, add_custom_emoji_policy=id).update(
can_add_custom_emoji_group=NamedUserGroup.objects.filter(
name=group_name, realm=OuterRef("id"), is_system_group=True
).values("pk")
)
class Migration(migrations.Migration):
atomic = False
dependencies = [
("zerver", "0603_realm_can_add_custom_emoji_group"),
]
operations = [
migrations.RunPython(
set_default_value_for_can_add_custom_emoji_group,
elidable=True,
reverse_code=migrations.RunPython.noop,
)
]

View File

@ -0,0 +1,22 @@
# Generated by Django 5.0.9 on 2024-10-04 07:06
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("zerver", "0604_set_default_value_for_can_add_custom_emoji_group"),
]
operations = [
migrations.AlterField(
model_name="realm",
name="can_add_custom_emoji_group",
field=models.ForeignKey(
on_delete=django.db.models.deletion.RESTRICT,
related_name="+",
to="zerver.usergroup",
),
),
]

View File

@ -287,6 +287,11 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
default=CommonPolicyEnum.MEMBERS_ONLY default=CommonPolicyEnum.MEMBERS_ONLY
) )
# Who in the organization is allowed to add custom emojis.
can_add_custom_emoji_group = models.ForeignKey(
"UserGroup", on_delete=models.RESTRICT, related_name="+"
)
# Who in the organization is allowed to create streams. # Who in the organization is allowed to create streams.
can_create_public_channel_group = models.ForeignKey( can_create_public_channel_group = models.ForeignKey(
"UserGroup", on_delete=models.RESTRICT, related_name="+" "UserGroup", on_delete=models.RESTRICT, related_name="+"
@ -704,6 +709,15 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
id_field_name="can_access_all_users_group_id", id_field_name="can_access_all_users_group_id",
allowed_system_groups=[SystemGroups.EVERYONE, SystemGroups.MEMBERS], allowed_system_groups=[SystemGroups.EVERYONE, SystemGroups.MEMBERS],
), ),
can_add_custom_emoji_group=GroupPermissionSetting(
require_system_group=False,
allow_internet_group=False,
allow_owners_group=False,
allow_nobody_group=False,
allow_everyone_group=False,
default_group_name=SystemGroups.MEMBERS,
id_field_name="can_add_custom_emoji_group_id",
),
can_create_groups=GroupPermissionSetting( can_create_groups=GroupPermissionSetting(
require_system_group=False, require_system_group=False,
allow_internet_group=False, allow_internet_group=False,
@ -794,6 +808,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub
) )
REALM_PERMISSION_GROUP_SETTINGS_WITH_NEW_API_FORMAT = [ REALM_PERMISSION_GROUP_SETTINGS_WITH_NEW_API_FORMAT = [
"can_add_custom_emoji_group",
"can_create_groups", "can_create_groups",
"can_create_private_channel_group", "can_create_private_channel_group",
"can_create_public_channel_group", "can_create_public_channel_group",
@ -1181,6 +1196,8 @@ def get_realm_with_settings(realm_id: int) -> Realm:
return Realm.objects.select_related( return Realm.objects.select_related(
"can_access_all_users_group", "can_access_all_users_group",
"can_access_all_users_group__named_user_group", "can_access_all_users_group__named_user_group",
"can_add_custom_emoji_group",
"can_add_custom_emoji_group__named_user_group",
"can_create_groups", "can_create_groups",
"can_create_groups__named_user_group", "can_create_groups__named_user_group",
"can_create_public_channel_group", "can_create_public_channel_group",

View File

@ -816,6 +816,7 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings):
if policy_name not in [ if policy_name not in [
"add_custom_emoji_policy", "add_custom_emoji_policy",
"can_add_custom_emoji_group",
"can_create_groups", "can_create_groups",
"can_create_private_channel_group", "can_create_private_channel_group",
"can_create_public_channel_group", "can_create_public_channel_group",
@ -910,7 +911,7 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings):
return self.has_permission("edit_topic_policy") return self.has_permission("edit_topic_policy")
def can_add_custom_emoji(self) -> bool: def can_add_custom_emoji(self) -> bool:
return self.has_permission("add_custom_emoji_policy") return self.has_permission("can_add_custom_emoji_group")
def can_delete_any_message(self) -> bool: def can_delete_any_message(self) -> bool:
return self.has_permission("can_delete_any_message_group") return self.has_permission("can_delete_any_message_group")

View File

@ -4410,6 +4410,20 @@ paths:
**Changes**: New in Zulip 10.0 (feature level 280). Previously **Changes**: New in Zulip 10.0 (feature level 280). Previously
`realm_create_web_public_stream_policy` field used to control `realm_create_web_public_stream_policy` field used to control
the permission to create web-public channels. the permission to create web-public channels.
can_add_custom_emoji_group:
allOf:
- description: |
A [group-setting value](/api/group-setting-values) defining the set of
users who have permission to add custom emoji in the organization.
**Changes**: New in Zulip 10.0 (feature level 307). Previously, this
permission was controlled by the enum `add_custom_emoji_policy`. Values
were 1=Members, 2=Admins, 3=Full members, 4=Moderators.
Before Zulip 5.0 (feature level 85), the `realm_add_emoji_by_admins_only`
boolean setting controlled this permission; `true` corresponded to `Admins`,
and `false` to `Everyone`.
- $ref: "#/components/schemas/GroupSettingValue"
can_delete_any_message_group: can_delete_any_message_group:
allOf: allOf:
- description: | - description: |
@ -16235,6 +16249,22 @@ paths:
Whether this organization is configured to allow users to access Whether this organization is configured to allow users to access
[message edit history](/help/view-a-messages-edit-history). [message edit history](/help/view-a-messages-edit-history).
realm_can_add_custom_emoji_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 add custom emoji in the organization.
**Changes**: New in Zulip 10.0 (feature level 307). Previously, this
permission was controlled by the enum `add_custom_emoji_policy`. Values
were 1=Members, 2=Admins, 3=Full members, 4=Moderators.
Before Zulip 5.0 (feature level 85), the `realm_add_emoji_by_admins_only`
boolean setting controlled this permission; `true` corresponded to `Admins`,
and `false` to `Everyone`.
- $ref: "#/components/schemas/GroupSettingValue"
realm_can_delete_any_message_group: realm_can_delete_any_message_group:
allOf: allOf:
- description: | - description: |

View File

@ -130,6 +130,7 @@ class HomeTest(ZulipTestCase):
"realm_bot_domain", "realm_bot_domain",
"realm_bots", "realm_bots",
"realm_can_access_all_users_group", "realm_can_access_all_users_group",
"realm_can_add_custom_emoji_group",
"realm_can_create_groups", "realm_can_create_groups",
"realm_can_create_private_channel_group", "realm_can_create_private_channel_group",
"realm_can_create_public_channel_group", "realm_can_create_public_channel_group",

View File

@ -3,15 +3,20 @@ from unittest import mock
from zerver.actions.create_realm import do_create_realm from zerver.actions.create_realm import do_create_realm
from zerver.actions.create_user import do_create_user from zerver.actions.create_user import do_create_user
from zerver.actions.realm_emoji import check_add_realm_emoji from zerver.actions.realm_emoji import check_add_realm_emoji
from zerver.actions.realm_settings import do_set_realm_property from zerver.actions.realm_settings import (
do_change_realm_permission_group_setting,
do_set_realm_property,
)
from zerver.actions.user_groups import check_add_user_group
from zerver.actions.users import do_change_user_role from zerver.actions.users import do_change_user_role
from zerver.lib.emoji import get_emoji_file_name from zerver.lib.emoji import get_emoji_file_name
from zerver.lib.exceptions import JsonableError from zerver.lib.exceptions import JsonableError
from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.test_helpers import get_test_image_file from zerver.lib.test_helpers import get_test_image_file
from zerver.lib.thumbnail import BadImageError from zerver.lib.thumbnail import BadImageError
from zerver.models import Realm, RealmEmoji, UserProfile from zerver.models import NamedUserGroup, Realm, RealmEmoji, UserProfile
from zerver.models.realms import CommonPolicyEnum, get_realm from zerver.models.groups import SystemGroups
from zerver.models.realms import get_realm
class RealmEmojiTest(ZulipTestCase): class RealmEmojiTest(ZulipTestCase):
@ -57,8 +62,15 @@ class RealmEmojiTest(ZulipTestCase):
# having no author are also there in the list. # having no author are also there in the list.
self.login("othello") self.login("othello")
realm = get_realm("zulip") realm = get_realm("zulip")
realm.add_custom_emoji_policy = CommonPolicyEnum.ADMINS_ONLY administrators_system_group = NamedUserGroup.objects.get(
realm.save() name=SystemGroups.ADMINISTRATORS, realm=realm, is_system_group=True
)
do_change_realm_permission_group_setting(
realm,
"can_add_custom_emoji_group",
administrators_system_group,
acting_user=None,
)
realm_emoji = self.create_test_emoji_with_no_author("my_emoji", realm) realm_emoji = self.create_test_emoji_with_no_author("my_emoji", realm)
result = self.client_get("/json/realm/emoji") result = self.client_get("/json/realm/emoji")
@ -165,19 +177,23 @@ class RealmEmojiTest(ZulipTestCase):
result = self.client_post("/json/realm/emoji/%20", info=emoji_data) result = self.client_post("/json/realm/emoji/%20", info=emoji_data)
self.assert_json_error(result, "Emoji name is missing") self.assert_json_error(result, "Emoji name is missing")
def test_can_add_custom_emoji(self) -> None:
def validation_func(user_profile: UserProfile) -> bool:
return user_profile.can_add_custom_emoji()
self.check_has_permission_policies("add_custom_emoji_policy", validation_func)
def test_user_settings_for_adding_custom_emoji(self) -> None: def test_user_settings_for_adding_custom_emoji(self) -> None:
othello = self.example_user("othello") othello = self.example_user("othello")
cordelia = self.example_user("cordelia")
iago = self.example_user("iago")
realm = othello.realm
self.login_user(othello) self.login_user(othello)
do_change_user_role(othello, UserProfile.ROLE_MODERATOR, acting_user=None) do_change_user_role(othello, UserProfile.ROLE_MODERATOR, acting_user=None)
do_set_realm_property( administrators_system_group = NamedUserGroup.objects.get(
othello.realm, "add_custom_emoji_policy", CommonPolicyEnum.ADMINS_ONLY, acting_user=None name=SystemGroups.ADMINISTRATORS, realm=realm, is_system_group=True
)
do_change_realm_permission_group_setting(
realm,
"can_add_custom_emoji_group",
administrators_system_group,
acting_user=None,
) )
with get_test_image_file("img.png") as fp1: with get_test_image_file("img.png") as fp1:
emoji_data = {"f1": fp1} emoji_data = {"f1": fp1}
@ -190,10 +206,13 @@ class RealmEmojiTest(ZulipTestCase):
result = self.client_post("/json/realm/emoji/my_emoji_1", info=emoji_data) result = self.client_post("/json/realm/emoji/my_emoji_1", info=emoji_data)
self.assert_json_success(result) self.assert_json_success(result)
do_set_realm_property( moderators_system_group = NamedUserGroup.objects.get(
othello.realm, name=SystemGroups.MODERATORS, realm=realm, is_system_group=True
"add_custom_emoji_policy", )
CommonPolicyEnum.MODERATORS_ONLY, do_change_realm_permission_group_setting(
realm,
"can_add_custom_emoji_group",
moderators_system_group,
acting_user=None, acting_user=None,
) )
do_change_user_role(othello, UserProfile.ROLE_MEMBER, acting_user=None) do_change_user_role(othello, UserProfile.ROLE_MEMBER, acting_user=None)
@ -208,10 +227,13 @@ class RealmEmojiTest(ZulipTestCase):
result = self.client_post("/json/realm/emoji/my_emoji_2", info=emoji_data) result = self.client_post("/json/realm/emoji/my_emoji_2", info=emoji_data)
self.assert_json_success(result) self.assert_json_success(result)
do_set_realm_property( full_members_system_group = NamedUserGroup.objects.get(
othello.realm, name=SystemGroups.FULL_MEMBERS, realm=realm, is_system_group=True
"add_custom_emoji_policy", )
CommonPolicyEnum.FULL_MEMBERS_ONLY, do_change_realm_permission_group_setting(
realm,
"can_add_custom_emoji_group",
full_members_system_group,
acting_user=None, acting_user=None,
) )
do_set_realm_property(othello.realm, "waiting_period_threshold", 100000, acting_user=None) do_set_realm_property(othello.realm, "waiting_period_threshold", 100000, acting_user=None)
@ -228,10 +250,13 @@ class RealmEmojiTest(ZulipTestCase):
result = self.client_post("/json/realm/emoji/my_emoji_3", info=emoji_data) result = self.client_post("/json/realm/emoji/my_emoji_3", info=emoji_data)
self.assert_json_success(result) self.assert_json_success(result)
do_set_realm_property( members_system_group = NamedUserGroup.objects.get(
othello.realm, name=SystemGroups.MEMBERS, realm=realm, is_system_group=True
"add_custom_emoji_policy", )
CommonPolicyEnum.MEMBERS_ONLY, do_change_realm_permission_group_setting(
realm,
"can_add_custom_emoji_group",
members_system_group,
acting_user=None, acting_user=None,
) )
do_change_user_role(othello, UserProfile.ROLE_GUEST, acting_user=None) do_change_user_role(othello, UserProfile.ROLE_GUEST, acting_user=None)
@ -246,6 +271,61 @@ class RealmEmojiTest(ZulipTestCase):
result = self.client_post("/json/realm/emoji/my_emoji_4", info=emoji_data) result = self.client_post("/json/realm/emoji/my_emoji_4", info=emoji_data)
self.assert_json_success(result) self.assert_json_success(result)
# Test for checking setting for non-system user group.
user_group = check_add_user_group(
realm, "newgroup", [othello, cordelia], acting_user=othello
)
do_change_realm_permission_group_setting(
realm, "can_add_custom_emoji_group", user_group, acting_user=None
)
# Othello is in the allowed user group, so can add custom emoji.
with get_test_image_file("img.png") as fp1:
emoji_data = {"f1": fp1}
result = self.client_post("/json/realm/emoji/my_emoji_5", info=emoji_data)
self.assert_json_success(result)
# Iago is not present in the allowed user group, so cannot add custom emoji.
self.login_user(iago)
with get_test_image_file("img.png") as fp1:
emoji_data = {"f1": fp1}
result = self.client_post("/json/realm/emoji/my_emoji_6", info=emoji_data)
self.assert_json_error(result, "Insufficient permission")
# Test for checking the setting for anonymous user group.
anonymous_user_group = self.create_or_update_anonymous_group_for_setting(
[othello],
[administrators_system_group],
)
do_change_realm_permission_group_setting(
realm,
"can_add_custom_emoji_group",
anonymous_user_group,
acting_user=None,
)
# Iago is present in the `administrators_system_group` subgroup, so can add
# custom emoji.
with get_test_image_file("img.png") as fp1:
emoji_data = {"f1": fp1}
result = self.client_post("/json/realm/emoji/my_emoji_6", info=emoji_data)
self.assert_json_success(result)
# Othello is the direct member of the allowed anonymous user group, so can add
# custom emoji.
self.login_user(othello)
with get_test_image_file("img.png") as fp1:
emoji_data = {"f1": fp1}
result = self.client_post("/json/realm/emoji/my_emoji_7", info=emoji_data)
self.assert_json_success(result)
# Cordelia is not present in the anonymous user group, so cannot add custom emoji.
self.login_user(cordelia)
with get_test_image_file("img.png") as fp1:
emoji_data = {"f1": fp1}
result = self.client_post("/json/realm/emoji/my_emoji_6", info=emoji_data)
self.assert_json_error(result, "Insufficient permission")
def test_delete(self) -> None: def test_delete(self) -> None:
emoji_author = self.example_user("iago") emoji_author = self.example_user("iago")
self.login_user(emoji_author) self.login_user(emoji_author)

View File

@ -112,6 +112,7 @@ def update_realm(
inline_image_preview: Json[bool] | None = None, inline_image_preview: Json[bool] | None = None,
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_add_custom_emoji_group: Json[GroupSettingChangeRequest] | 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, can_delete_own_message_group: Json[GroupSettingChangeRequest] | None = None,
message_content_delete_limit_seconds_raw: Annotated[ message_content_delete_limit_seconds_raw: Annotated[