diff --git a/zerver/lib/emoji.py b/zerver/lib/emoji.py index 12b4646c5d..e75d154b4b 100644 --- a/zerver/lib/emoji.py +++ b/zerver/lib/emoji.py @@ -5,7 +5,7 @@ from typing import Tuple import orjson from django.utils.translation import gettext as _ -from zerver.lib.exceptions import JsonableError, OrganizationAdministratorRequired +from zerver.lib.exceptions import JsonableError from zerver.lib.storage import static_path from zerver.lib.upload import upload_backend from zerver.models import Reaction, Realm, RealmEmoji, UserProfile @@ -84,16 +84,6 @@ def check_emoji_request(realm: Realm, emoji_name: str, emoji_code: str, emoji_ty raise JsonableError(_("Invalid emoji type.")) -def check_add_emoji_admin(user_profile: UserProfile) -> None: - """Raises an exception if the user cannot add the emoji in their organization.""" - - # Realm administrators can always add emoji - if user_profile.is_realm_admin: - return - if user_profile.realm.add_custom_emoji_policy == Realm.ADD_CUSTOM_EMOJI_ADMINS_ONLY: - raise OrganizationAdministratorRequired() - - def check_remove_custom_emoji(user_profile: UserProfile, emoji_name: str) -> None: # normal users can remove emoji they themselves added if user_profile.is_realm_admin: diff --git a/zerver/models.py b/zerver/models.py index d3905dda26..c8c340b58d 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -245,17 +245,8 @@ class Realm(models.Model): send_welcome_emails: bool = models.BooleanField(default=True) message_content_allowed_in_email_notifications: bool = models.BooleanField(default=True) - ADD_CUSTOM_EMOJI_MEMBERS_ONLY = 1 - ADD_CUSTOM_EMOJI_ADMINS_ONLY = 2 - ADD_CUSTOM_EMOJI_POLICY_TYPES = [ - ADD_CUSTOM_EMOJI_MEMBERS_ONLY, - ADD_CUSTOM_EMOJI_ADMINS_ONLY, - ] - mandatory_topics: bool = models.BooleanField(default=False) - add_custom_emoji_policy: int = models.PositiveSmallIntegerField( - default=ADD_CUSTOM_EMOJI_MEMBERS_ONLY - ) + name_changes_disabled: bool = models.BooleanField(default=False) email_changes_disabled: bool = models.BooleanField(default=False) avatar_changes_disabled: bool = models.BooleanField(default=False) @@ -292,6 +283,9 @@ class Realm(models.Model): DEFAULT_COMMUNITY_TOPIC_EDITING_LIMIT_SECONDS = 259200 + # Who in the organization is allowed to add custom emojis. + add_custom_emoji_policy: int = models.PositiveSmallIntegerField(default=POLICY_MEMBERS_ONLY) + # Who in the organization is allowed to create streams. create_stream_policy: int = models.PositiveSmallIntegerField(default=POLICY_MEMBERS_ONLY) diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index 90211dcd2e..2ba1e3ccd8 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -8739,6 +8739,8 @@ paths: * 1 = Members only * 2 = Administrators only + * 3 = Full members only + * 4 = Moderators only **Changes**: New in Zulip 5.0 (feature level 85) replacing the previous `realm_add_emoji_by_admins_only` boolean. diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index 3e542583d9..453d8a3fc1 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -2047,7 +2047,7 @@ class RealmPropertyActionTest(BaseAction): message_content_delete_limit_seconds=[1000, 1100, 1200], invite_to_realm_policy=[6, 4, 3, 2, 1], move_messages_between_streams_policy=[4, 3, 2, 1], - add_custom_emoji_policy=[2, 1], + add_custom_emoji_policy=[4, 3, 2, 1], ) vals = test_values.get(name) diff --git a/zerver/tests/test_realm.py b/zerver/tests/test_realm.py index 70156e6186..b6c384564e 100644 --- a/zerver/tests/test_realm.py +++ b/zerver/tests/test_realm.py @@ -781,7 +781,7 @@ class RealmAPITest(ZulipTestCase): message_content_delete_limit_seconds=[1000, 1100, 1200], invite_to_realm_policy=Realm.INVITE_TO_REALM_POLICY_TYPES, move_messages_between_streams_policy=Realm.COMMON_POLICY_TYPES, - add_custom_emoji_policy=Realm.ADD_CUSTOM_EMOJI_POLICY_TYPES, + add_custom_emoji_policy=Realm.COMMON_POLICY_TYPES, ) vals = test_values.get(name) diff --git a/zerver/tests/test_realm_emoji.py b/zerver/tests/test_realm_emoji.py index 5775cbbff0..ed7cf05073 100644 --- a/zerver/tests/test_realm_emoji.py +++ b/zerver/tests/test_realm_emoji.py @@ -1,6 +1,12 @@ from unittest import mock -from zerver.lib.actions import check_add_realm_emoji, do_create_realm, do_create_user +from zerver.lib.actions import ( + check_add_realm_emoji, + do_change_user_role, + do_create_realm, + do_create_user, + do_set_realm_property, +) from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import get_test_image_file from zerver.models import Realm, RealmEmoji, UserProfile, get_realm @@ -47,7 +53,7 @@ class RealmEmojiTest(ZulipTestCase): # having no author are also there in the list. self.login("othello") realm = get_realm("zulip") - realm.add_custom_emoji_policy = Realm.ADD_CUSTOM_EMOJI_ADMINS_ONLY + realm.add_custom_emoji_policy = Realm.POLICY_ADMINS_ONLY realm.save() realm_emoji = self.create_test_emoji_with_no_author("my_emoji", realm) @@ -139,33 +145,75 @@ class RealmEmojiTest(ZulipTestCase): self.check_has_permission_policies("add_custom_emoji_policy", validation_func) - def test_upload_admins_only(self) -> None: - self.login("othello") - realm = get_realm("zulip") - realm.add_custom_emoji_policy = Realm.ADD_CUSTOM_EMOJI_ADMINS_ONLY - realm.save() - with get_test_image_file("img.png") as fp1: - emoji_data = {"f1": fp1} - result = self.client_post("/json/realm/emoji/my_emoji", info=emoji_data) - self.assert_json_error(result, "Must be an organization administrator") + def test_user_settings_for_adding_custom_emoji(self) -> None: + othello = self.example_user("othello") + self.login_user(othello) - def test_upload_anyone(self) -> None: - self.login("othello") - realm = get_realm("zulip") - realm.add_custom_emoji_policy = Realm.ADD_CUSTOM_EMOJI_MEMBERS_ONLY - realm.save() + do_change_user_role(othello, UserProfile.ROLE_MODERATOR, acting_user=None) + do_set_realm_property( + othello.realm, "add_custom_emoji_policy", Realm.POLICY_ADMINS_ONLY, acting_user=None + ) with get_test_image_file("img.png") as fp1: emoji_data = {"f1": fp1} - result = self.client_post("/json/realm/emoji/my_emoji", info=emoji_data) + result = self.client_post("/json/realm/emoji/my_emoji_1", info=emoji_data) + self.assert_json_error(result, "Insufficient permission") + + do_change_user_role(othello, UserProfile.ROLE_REALM_ADMINISTRATOR, acting_user=None) + with get_test_image_file("img.png") as fp1: + emoji_data = {"f1": fp1} + result = self.client_post("/json/realm/emoji/my_emoji_1", info=emoji_data) self.assert_json_success(result) - def test_emoji_upload_by_guest_user(self) -> None: - self.login("polonius") + do_set_realm_property( + othello.realm, "add_custom_emoji_policy", Realm.POLICY_MODERATORS_ONLY, acting_user=None + ) + do_change_user_role(othello, UserProfile.ROLE_MEMBER, acting_user=None) with get_test_image_file("img.png") as fp1: emoji_data = {"f1": fp1} - result = self.client_post("/json/realm/emoji/my_emoji", info=emoji_data) + result = self.client_post("/json/realm/emoji/my_emoji_2", info=emoji_data) + self.assert_json_error(result, "Insufficient permission") + + do_change_user_role(othello, UserProfile.ROLE_MODERATOR, acting_user=None) + with get_test_image_file("img.png") as fp1: + emoji_data = {"f1": fp1} + result = self.client_post("/json/realm/emoji/my_emoji_2", info=emoji_data) + self.assert_json_success(result) + + do_set_realm_property( + othello.realm, + "add_custom_emoji_policy", + Realm.POLICY_FULL_MEMBERS_ONLY, + acting_user=None, + ) + do_set_realm_property(othello.realm, "waiting_period_threshold", 100000, acting_user=None) + do_change_user_role(othello, UserProfile.ROLE_MEMBER, acting_user=None) + + with get_test_image_file("img.png") as fp1: + emoji_data = {"f1": fp1} + result = self.client_post("/json/realm/emoji/my_emoji_3", info=emoji_data) + self.assert_json_error(result, "Insufficient permission") + + do_set_realm_property(othello.realm, "waiting_period_threshold", 0, acting_user=None) + with get_test_image_file("img.png") as fp1: + emoji_data = {"f1": fp1} + result = self.client_post("/json/realm/emoji/my_emoji_3", info=emoji_data) + self.assert_json_success(result) + + do_set_realm_property( + othello.realm, "add_custom_emoji_policy", Realm.POLICY_MEMBERS_ONLY, acting_user=None + ) + do_change_user_role(othello, UserProfile.ROLE_GUEST, acting_user=None) + with get_test_image_file("img.png") as fp1: + emoji_data = {"f1": fp1} + result = self.client_post("/json/realm/emoji/my_emoji_4", info=emoji_data) self.assert_json_error(result, "Not allowed for guest users") + do_change_user_role(othello, UserProfile.ROLE_MEMBER, acting_user=None) + with get_test_image_file("img.png") as fp1: + emoji_data = {"f1": fp1} + result = self.client_post("/json/realm/emoji/my_emoji_4", info=emoji_data) + self.assert_json_success(result) + def test_delete(self) -> None: emoji_author = self.example_user("iago") self.login_user(emoji_author) diff --git a/zerver/views/realm.py b/zerver/views/realm.py index a05fae122a..2849caa235 100644 --- a/zerver/views/realm.py +++ b/zerver/views/realm.py @@ -61,7 +61,7 @@ def update_realm( inline_image_preview: Optional[bool] = REQ(json_validator=check_bool, default=None), inline_url_embed_preview: Optional[bool] = REQ(json_validator=check_bool, default=None), add_custom_emoji_policy: Optional[int] = REQ( - json_validator=check_int_in(Realm.ADD_CUSTOM_EMOJI_POLICY_TYPES), default=None + json_validator=check_int_in(Realm.COMMON_POLICY_TYPES), default=None ), allow_message_deleting: Optional[bool] = REQ(json_validator=check_bool, default=None), message_content_delete_limit_seconds: Optional[int] = REQ( diff --git a/zerver/views/realm_emoji.py b/zerver/views/realm_emoji.py index ac00341764..6595fa140d 100644 --- a/zerver/views/realm_emoji.py +++ b/zerver/views/realm_emoji.py @@ -4,12 +4,7 @@ from django.utils.translation import gettext as _ from zerver.decorator import require_member_or_admin from zerver.lib.actions import check_add_realm_emoji, do_remove_realm_emoji -from zerver.lib.emoji import ( - check_add_emoji_admin, - check_remove_custom_emoji, - check_valid_emoji_name, - name_to_codepoint, -) +from zerver.lib.emoji import check_remove_custom_emoji, check_valid_emoji_name, name_to_codepoint from zerver.lib.exceptions import JsonableError from zerver.lib.request import REQ, has_request_variables from zerver.lib.response import json_success @@ -31,7 +26,10 @@ def upload_emoji( emoji_name = emoji_name.strip().replace(" ", "_") valid_built_in_emoji = name_to_codepoint.keys() check_valid_emoji_name(emoji_name) - check_add_emoji_admin(user_profile) + + if not user_profile.can_add_custom_emoji(): + raise JsonableError(_("Insufficient permission")) + if RealmEmoji.objects.filter( realm=user_profile.realm, name=emoji_name, deactivated=False ).exists():