diff --git a/zerver/actions/realm_emoji.py b/zerver/actions/realm_emoji.py new file mode 100644 index 0000000000..d568e26b7b --- /dev/null +++ b/zerver/actions/realm_emoji.py @@ -0,0 +1,55 @@ +from typing import IO + +import django.db.utils +from django.utils.translation import gettext as _ + +from zerver.lib.emoji import get_emoji_file_name +from zerver.lib.exceptions import JsonableError +from zerver.lib.pysa import mark_sanitized +from zerver.lib.upload import upload_emoji_image +from zerver.models import Realm, RealmEmoji, UserProfile, active_user_ids +from zerver.tornado.django_api import send_event + + +def notify_realm_emoji(realm: Realm) -> None: + event = dict(type="realm_emoji", op="update", realm_emoji=realm.get_emoji()) + send_event(realm, event, active_user_ids(realm.id)) + + +def check_add_realm_emoji( + realm: Realm, name: str, author: UserProfile, image_file: IO[bytes] +) -> RealmEmoji: + try: + realm_emoji = RealmEmoji(realm=realm, name=name, author=author) + realm_emoji.full_clean() + realm_emoji.save() + except django.db.utils.IntegrityError: + # Match the string in upload_emoji. + raise JsonableError(_("A custom emoji with this name already exists.")) + + emoji_file_name = get_emoji_file_name(image_file.name, realm_emoji.id) + + # The only user-controlled portion of 'emoji_file_name' is an extension, + # which can not contain '..' or '/' or '\', making it difficult to exploit + emoji_file_name = mark_sanitized(emoji_file_name) + + emoji_uploaded_successfully = False + is_animated = False + try: + is_animated = upload_emoji_image(image_file, emoji_file_name, author) + emoji_uploaded_successfully = True + finally: + if not emoji_uploaded_successfully: + realm_emoji.delete() + realm_emoji.file_name = emoji_file_name + realm_emoji.is_animated = is_animated + realm_emoji.save(update_fields=["file_name", "is_animated"]) + notify_realm_emoji(realm_emoji.realm) + return realm_emoji + + +def do_remove_realm_emoji(realm: Realm, name: str) -> None: + emoji = RealmEmoji.objects.get(realm=realm, name=name, deactivated=False) + emoji.deactivated = True + emoji.save(update_fields=["deactivated"]) + notify_realm_emoji(realm) diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py index c0e83bb35f..f452c0bd91 100644 --- a/zerver/lib/actions.py +++ b/zerver/lib/actions.py @@ -7,7 +7,6 @@ from collections import defaultdict from dataclasses import asdict, dataclass, field from operator import itemgetter from typing import ( - IO, AbstractSet, Any, Callable, @@ -23,7 +22,6 @@ from typing import ( Union, ) -import django.db.utils import orjson from django.conf import settings from django.core.exceptions import ValidationError @@ -77,7 +75,7 @@ from zerver.lib.create_user import create_user, get_display_email_address from zerver.lib.email_mirror_helpers import encode_email_address from zerver.lib.email_notifications import enqueue_welcome_emails from zerver.lib.email_validation import email_reserved_for_system_bots_error -from zerver.lib.emoji import check_emoji_request, emoji_name_to_emoji_code, get_emoji_file_name +from zerver.lib.emoji import check_emoji_request, emoji_name_to_emoji_code from zerver.lib.exceptions import ( JsonableError, MarkdownRenderingException, @@ -105,7 +103,6 @@ from zerver.lib.message import ( wildcard_mention_allowed, ) from zerver.lib.notification_data import UserMessageNotificationsData, get_user_group_mentions_data -from zerver.lib.pysa import mark_sanitized from zerver.lib.queue import queue_json_publish from zerver.lib.recipient_users import recipient_for_user_profiles from zerver.lib.retention import move_messages_to_archive @@ -173,12 +170,7 @@ from zerver.lib.types import ( SubscriptionInfo, SubscriptionStreamDict, ) -from zerver.lib.upload import ( - claim_attachment, - delete_avatar_image, - delete_message_image, - upload_emoji_image, -) +from zerver.lib.upload import claim_attachment, delete_avatar_image, delete_message_image from zerver.lib.user_counts import realm_user_count, realm_user_count_by_role from zerver.lib.user_groups import ( create_system_user_groups_for_realm, @@ -220,7 +212,6 @@ from zerver.models import ( Realm, RealmAuditLog, RealmDomain, - RealmEmoji, RealmUserDefault, Recipient, ScheduledEmail, @@ -6937,50 +6928,6 @@ def email_not_system_bot(email: str) -> None: ) -def notify_realm_emoji(realm: Realm) -> None: - event = dict(type="realm_emoji", op="update", realm_emoji=realm.get_emoji()) - send_event(realm, event, active_user_ids(realm.id)) - - -def check_add_realm_emoji( - realm: Realm, name: str, author: UserProfile, image_file: IO[bytes] -) -> RealmEmoji: - try: - realm_emoji = RealmEmoji(realm=realm, name=name, author=author) - realm_emoji.full_clean() - realm_emoji.save() - except django.db.utils.IntegrityError: - # Match the string in upload_emoji. - raise JsonableError(_("A custom emoji with this name already exists.")) - - emoji_file_name = get_emoji_file_name(image_file.name, realm_emoji.id) - - # The only user-controlled portion of 'emoji_file_name' is an extension, - # which can not contain '..' or '/' or '\', making it difficult to exploit - emoji_file_name = mark_sanitized(emoji_file_name) - - emoji_uploaded_successfully = False - is_animated = False - try: - is_animated = upload_emoji_image(image_file, emoji_file_name, author) - emoji_uploaded_successfully = True - finally: - if not emoji_uploaded_successfully: - realm_emoji.delete() - realm_emoji.file_name = emoji_file_name - realm_emoji.is_animated = is_animated - realm_emoji.save(update_fields=["file_name", "is_animated"]) - notify_realm_emoji(realm_emoji.realm) - return realm_emoji - - -def do_remove_realm_emoji(realm: Realm, name: str) -> None: - emoji = RealmEmoji.objects.get(realm=realm, name=name, deactivated=False) - emoji.deactivated = True - emoji.save(update_fields=["deactivated"]) - notify_realm_emoji(realm) - - def do_mute_topic( user_profile: UserProfile, stream: Stream, diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index 86d50e4338..3f6bace3e0 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -32,6 +32,7 @@ from zerver.actions.invites import ( do_revoke_multi_use_invite, do_revoke_user_invite, ) +from zerver.actions.realm_emoji import check_add_realm_emoji, do_remove_realm_emoji from zerver.actions.realm_icon import do_change_icon_source from zerver.actions.realm_linkifiers import ( do_add_linkifier, @@ -54,7 +55,6 @@ from zerver.actions.video_calls import do_set_zoom_token from zerver.lib.actions import ( bulk_add_subscriptions, bulk_remove_subscriptions, - check_add_realm_emoji, do_add_reaction, do_add_realm_domain, do_change_avatar_fields, @@ -86,7 +86,6 @@ from zerver.lib.actions import ( do_remove_reaction, do_remove_realm_custom_profile_field, do_remove_realm_domain, - do_remove_realm_emoji, do_rename_stream, do_set_realm_authentication_methods, do_set_realm_message_editing, diff --git a/zerver/tests/test_import_export.py b/zerver/tests/test_import_export.py index ee34e77724..3d90c776c2 100644 --- a/zerver/tests/test_import_export.py +++ b/zerver/tests/test_import_export.py @@ -11,12 +11,12 @@ from django.utils.timezone import now as timezone_now from analytics.models import UserCount from zerver.actions.alert_words import do_add_alert_words +from zerver.actions.realm_emoji import check_add_realm_emoji from zerver.actions.realm_icon import do_change_icon_source from zerver.actions.realm_logo import do_change_logo_source from zerver.lib import upload from zerver.lib.actions import ( check_add_reaction, - check_add_realm_emoji, do_add_reaction, do_change_realm_plan_type, do_create_user, diff --git a/zerver/tests/test_markdown.py b/zerver/tests/test_markdown.py index a413ab0be9..0d9f2ab34b 100644 --- a/zerver/tests/test_markdown.py +++ b/zerver/tests/test_markdown.py @@ -11,11 +11,11 @@ from django.test import override_settings from markdown import Markdown from zerver.actions.alert_words import do_add_alert_words +from zerver.actions.realm_emoji import do_remove_realm_emoji from zerver.lib.actions import ( change_user_is_active, do_change_user_setting, do_create_realm, - do_remove_realm_emoji, do_set_realm_property, ) from zerver.lib.alert_words import get_alert_word_automaton diff --git a/zerver/tests/test_realm_emoji.py b/zerver/tests/test_realm_emoji.py index 2f9e5b4681..90bc5f1d83 100644 --- a/zerver/tests/test_realm_emoji.py +++ b/zerver/tests/test_realm_emoji.py @@ -1,7 +1,7 @@ from unittest import mock +from zerver.actions.realm_emoji import check_add_realm_emoji from zerver.lib.actions import ( - check_add_realm_emoji, do_change_user_role, do_create_realm, do_create_user, diff --git a/zerver/tests/test_transfer.py b/zerver/tests/test_transfer.py index ed658d37bc..116409fe5d 100644 --- a/zerver/tests/test_transfer.py +++ b/zerver/tests/test_transfer.py @@ -4,7 +4,7 @@ from unittest.mock import Mock, patch from django.conf import settings from moto import mock_s3 -from zerver.lib.actions import check_add_realm_emoji +from zerver.actions.realm_emoji import check_add_realm_emoji from zerver.lib.avatar_hash import user_avatar_path from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import ( diff --git a/zerver/views/realm_emoji.py b/zerver/views/realm_emoji.py index edde4d70a4..d8e6df4965 100644 --- a/zerver/views/realm_emoji.py +++ b/zerver/views/realm_emoji.py @@ -2,8 +2,8 @@ from django.conf import settings from django.http import HttpRequest, HttpResponse from django.utils.translation import gettext as _ +from zerver.actions.realm_emoji import check_add_realm_emoji, do_remove_realm_emoji 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_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 diff --git a/zilencer/management/commands/populate_db.py b/zilencer/management/commands/populate_db.py index 4e28b41602..088688aece 100644 --- a/zilencer/management/commands/populate_db.py +++ b/zilencer/management/commands/populate_db.py @@ -18,9 +18,9 @@ from django.utils.timezone import now as timezone_now from django.utils.timezone import timedelta as timezone_timedelta from scripts.lib.zulip_tools import get_or_create_dev_uuid_var_path +from zerver.actions.realm_emoji import check_add_realm_emoji from zerver.lib.actions import ( build_message_send_dict, - check_add_realm_emoji, do_change_user_role, do_create_realm, do_send_messages,