diff --git a/zerver/tests/test_messages.py b/zerver/tests/test_messages.py index ace63f364b..062f477f16 100644 --- a/zerver/tests/test_messages.py +++ b/zerver/tests/test_messages.py @@ -114,7 +114,7 @@ from zerver.models import ( get_system_bot, get_user, ) -from zerver.views.messages import InvalidMirrorInput, create_mirrored_message_users +from zerver.views.message_send import InvalidMirrorInput, create_mirrored_message_users class MiscMessageTest(ZulipTestCase): @@ -2233,7 +2233,7 @@ class MessagePOSTTest(ZulipTestCase): subdomain="zephyr") self.assert_json_error(result, "User not authorized for this query") - @mock.patch("zerver.views.messages.create_mirrored_message_users") + @mock.patch("zerver.views.message_send.create_mirrored_message_users") def test_send_message_create_mirrored_message_user_returns_invalid_input( self, create_mirrored_message_users_mock: Any) -> None: create_mirrored_message_users_mock.side_effect = InvalidMirrorInput() @@ -2246,7 +2246,7 @@ class MessagePOSTTest(ZulipTestCase): subdomain="zephyr") self.assert_json_error(result, "Invalid mirrored message") - @mock.patch("zerver.views.messages.create_mirrored_message_users") + @mock.patch("zerver.views.message_send.create_mirrored_message_users") def test_send_message_when_client_is_zephyr_mirror_but_string_id_is_not_zephyr( self, create_mirrored_message_users_mock: Any) -> None: create_mirrored_message_users_mock.return_value = mock.Mock() @@ -2262,7 +2262,7 @@ class MessagePOSTTest(ZulipTestCase): subdomain="notzephyr") self.assert_json_error(result, "Zephyr mirroring is not allowed in this organization") - @mock.patch("zerver.views.messages.create_mirrored_message_users") + @mock.patch("zerver.views.message_send.create_mirrored_message_users") def test_send_message_when_client_is_zephyr_mirror_but_recipient_is_user_id( self, create_mirrored_message_users_mock: Any) -> None: create_mirrored_message_users_mock.return_value = mock.Mock() diff --git a/zerver/views/message_send.py b/zerver/views/message_send.py new file mode 100644 index 0000000000..8686f21624 --- /dev/null +++ b/zerver/views/message_send.py @@ -0,0 +1,300 @@ +from typing import Iterable, Optional, Sequence, Union, cast + +from dateutil.parser import parse as dateparser +from django.core import validators +from django.core.exceptions import ValidationError +from django.http import HttpRequest, HttpResponse +from django.utils.timezone import now as timezone_now +from django.utils.translation import ugettext as _ + +from zerver.decorator import REQ, has_request_variables +from zerver.lib.actions import ( + check_schedule_message, + check_send_message, + compute_irc_user_fullname, + compute_jabber_user_fullname, + create_mirror_user_if_needed, + extract_private_recipients, + extract_stream_indicator, +) +from zerver.lib.message import render_markdown +from zerver.lib.response import json_error, json_success +from zerver.lib.timestamp import convert_to_UTC +from zerver.lib.timezone import get_timezone +from zerver.lib.topic import REQ_topic +from zerver.lib.zcommand import process_zcommands +from zerver.lib.zephyr import compute_mit_user_fullname +from zerver.models import ( + Client, + Message, + Realm, + RealmDomain, + UserProfile, + email_to_domain, + get_realm, + get_user_including_cross_realm, +) + + +class InvalidMirrorInput(Exception): + pass + +def create_mirrored_message_users(request: HttpRequest, user_profile: UserProfile, + recipients: Iterable[str]) -> UserProfile: + if "sender" not in request.POST: + raise InvalidMirrorInput("No sender") + + sender_email = request.POST["sender"].strip().lower() + referenced_users = {sender_email} + if request.POST['type'] == 'private': + for email in recipients: + referenced_users.add(email.lower()) + + if request.client.name == "zephyr_mirror": + user_check = same_realm_zephyr_user + fullname_function = compute_mit_user_fullname + elif request.client.name == "irc_mirror": + user_check = same_realm_irc_user + fullname_function = compute_irc_user_fullname + elif request.client.name in ("jabber_mirror", "JabberMirror"): + user_check = same_realm_jabber_user + fullname_function = compute_jabber_user_fullname + else: + raise InvalidMirrorInput("Unrecognized mirroring client") + + for email in referenced_users: + # Check that all referenced users are in our realm: + if not user_check(user_profile, email): + raise InvalidMirrorInput("At least one user cannot be mirrored") + + # Create users for the referenced users, if needed. + for email in referenced_users: + create_mirror_user_if_needed(user_profile.realm, email, fullname_function) + + sender = get_user_including_cross_realm(sender_email, user_profile.realm) + return sender + +def same_realm_zephyr_user(user_profile: UserProfile, email: str) -> bool: + # + # Are the sender and recipient both addresses in the same Zephyr + # mirroring realm? We have to handle this specially, inferring + # the domain from the e-mail address, because the recipient may + # not existing in Zulip and we may need to make a stub Zephyr + # mirroring user on the fly. + try: + validators.validate_email(email) + except ValidationError: + return False + + domain = email_to_domain(email) + + # Assumes allow_subdomains=False for all RealmDomain's corresponding to + # these realms. + return user_profile.realm.is_zephyr_mirror_realm and \ + RealmDomain.objects.filter(realm=user_profile.realm, domain=domain).exists() + +def same_realm_irc_user(user_profile: UserProfile, email: str) -> bool: + # Check whether the target email address is an IRC user in the + # same realm as user_profile, i.e. if the domain were example.com, + # the IRC user would need to be username@irc.example.com + try: + validators.validate_email(email) + except ValidationError: + return False + + domain = email_to_domain(email).replace("irc.", "") + + # Assumes allow_subdomains=False for all RealmDomain's corresponding to + # these realms. + return RealmDomain.objects.filter(realm=user_profile.realm, domain=domain).exists() + +def same_realm_jabber_user(user_profile: UserProfile, email: str) -> bool: + try: + validators.validate_email(email) + except ValidationError: + return False + + # If your Jabber users have a different email domain than the + # Zulip users, this is where you would do any translation. + domain = email_to_domain(email) + + # Assumes allow_subdomains=False for all RealmDomain's corresponding to + # these realms. + return RealmDomain.objects.filter(realm=user_profile.realm, domain=domain).exists() + +def handle_deferred_message(sender: UserProfile, client: Client, + message_type_name: str, + message_to: Union[Sequence[str], Sequence[int]], + topic_name: Optional[str], + message_content: str, delivery_type: str, + defer_until: str, tz_guess: Optional[str], + forwarder_user_profile: UserProfile, + realm: Optional[Realm]) -> HttpResponse: + deliver_at = None + local_tz = 'UTC' + if tz_guess: + local_tz = tz_guess + elif sender.timezone: + local_tz = sender.timezone + try: + deliver_at = dateparser(defer_until) + except ValueError: + return json_error(_("Invalid time format")) + + deliver_at_usertz = deliver_at + if deliver_at_usertz.tzinfo is None: + user_tz = get_timezone(local_tz) + # Since mypy is not able to recognize localize and normalize as attributes of tzinfo we use ignore. + deliver_at_usertz = user_tz.normalize(user_tz.localize(deliver_at)) # type: ignore[attr-defined] # Reason in comment on previous line. + deliver_at = convert_to_UTC(deliver_at_usertz) + + if deliver_at <= timezone_now(): + return json_error(_("Time must be in the future.")) + + check_schedule_message(sender, client, message_type_name, message_to, + topic_name, message_content, delivery_type, + deliver_at, realm=realm, + forwarder_user_profile=forwarder_user_profile) + return json_success({"deliver_at": str(deliver_at_usertz)}) + +@has_request_variables +def send_message_backend(request: HttpRequest, user_profile: UserProfile, + message_type_name: str=REQ('type'), + req_to: Optional[str]=REQ('to', default=None), + forged_str: Optional[str]=REQ("forged", + default=None, + documentation_pending=True), + topic_name: Optional[str]=REQ_topic(), + message_content: str=REQ('content'), + widget_content: Optional[str]=REQ(default=None, + documentation_pending=True), + realm_str: Optional[str]=REQ('realm_str', default=None, + documentation_pending=True), + local_id: Optional[str]=REQ(default=None, + documentation_pending=True), + queue_id: Optional[str]=REQ(default=None, + documentation_pending=True), + delivery_type: str=REQ('delivery_type', default='send_now', + documentation_pending=True), + defer_until: Optional[str]=REQ('deliver_at', default=None, + documentation_pending=True), + tz_guess: Optional[str]=REQ('tz_guess', default=None, + documentation_pending=True), + ) -> HttpResponse: + + # If req_to is None, then we default to an + # empty list of recipients. + message_to: Union[Sequence[int], Sequence[str]] = [] + + if req_to is not None: + if message_type_name == 'stream': + stream_indicator = extract_stream_indicator(req_to) + + # For legacy reasons check_send_message expects + # a list of streams, instead of a single stream. + # + # Also, mypy can't detect that a single-item + # list populated from a Union[int, str] is actually + # a Union[Sequence[int], Sequence[str]]. + message_to = cast( + Union[Sequence[int], Sequence[str]], + [stream_indicator], + ) + else: + message_to = extract_private_recipients(req_to) + + # Temporary hack: We're transitioning `forged` from accepting + # `yes` to accepting `true` like all of our normal booleans. + forged = forged_str is not None and forged_str in ["yes", "true"] + + client = request.client + is_super_user = request.user.is_api_super_user + if forged and not is_super_user: + return json_error(_("User not authorized for this query")) + + realm = None + if realm_str and realm_str != user_profile.realm.string_id: + if not is_super_user: + # The email gateway bot needs to be able to send messages in + # any realm. + return json_error(_("User not authorized for this query")) + try: + realm = get_realm(realm_str) + except Realm.DoesNotExist: + return json_error(_("Unknown organization '{}'").format(realm_str)) + + if client.name in ["zephyr_mirror", "irc_mirror", "jabber_mirror", "JabberMirror"]: + # Here's how security works for mirroring: + # + # For private messages, the message must be (1) both sent and + # received exclusively by users in your realm, and (2) + # received by the forwarding user. + # + # For stream messages, the message must be (1) being forwarded + # by an API superuser for your realm and (2) being sent to a + # mirrored stream. + # + # The most important security checks are in + # `create_mirrored_message_users` below, which checks the + # same-realm constraint. + if "sender" not in request.POST: + return json_error(_("Missing sender")) + if message_type_name != "private" and not is_super_user: + return json_error(_("User not authorized for this query")) + + # For now, mirroring only works with recipient emails, not for + # recipient user IDs. + if not all(isinstance(to_item, str) for to_item in message_to): + return json_error(_("Mirroring not allowed with recipient user IDs")) + + # We need this manual cast so that mypy doesn't complain about + # create_mirrored_message_users not being able to accept a Sequence[int] + # type parameter. + message_to = cast(Sequence[str], message_to) + + try: + mirror_sender = create_mirrored_message_users(request, user_profile, message_to) + except InvalidMirrorInput: + return json_error(_("Invalid mirrored message")) + + if client.name == "zephyr_mirror" and not user_profile.realm.is_zephyr_mirror_realm: + return json_error(_("Zephyr mirroring is not allowed in this organization")) + sender = mirror_sender + else: + if "sender" in request.POST: + return json_error(_("Invalid mirrored message")) + sender = user_profile + + if (delivery_type == 'send_later' or delivery_type == 'remind') and defer_until is None: + return json_error(_("Missing deliver_at in a request for delayed message delivery")) + + if (delivery_type == 'send_later' or delivery_type == 'remind') and defer_until is not None: + return handle_deferred_message(sender, client, message_type_name, + message_to, topic_name, message_content, + delivery_type, defer_until, tz_guess, + forwarder_user_profile=user_profile, + realm=realm) + + ret = check_send_message(sender, client, message_type_name, message_to, + topic_name, message_content, forged=forged, + forged_timestamp = request.POST.get('time'), + forwarder_user_profile=user_profile, realm=realm, + local_id=local_id, sender_queue_id=queue_id, + widget_content=widget_content) + return json_success({"id": ret}) + +@has_request_variables +def zcommand_backend(request: HttpRequest, user_profile: UserProfile, + command: str=REQ('command')) -> HttpResponse: + return json_success(process_zcommands(command, user_profile)) + +@has_request_variables +def render_message_backend(request: HttpRequest, user_profile: UserProfile, + content: str=REQ()) -> HttpResponse: + message = Message() + message.sender = user_profile + message.content = content + message.sending_client = request.client + + rendered_content = render_markdown(message, content, realm=user_profile.realm) + return json_success({"rendered": rendered_content}) diff --git a/zerver/views/messages.py b/zerver/views/messages.py index a09c83e126..e435240024 100644 --- a/zerver/views/messages.py +++ b/zerver/views/messages.py @@ -1,15 +1,12 @@ import re -from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, Union, cast +from typing import Any, Dict, Iterable, List, Optional, Tuple, Union import ujson -from dateutil.parser import parse as dateparser from django.conf import settings -from django.core import validators from django.core.exceptions import ValidationError from django.db import connection from django.http import HttpRequest, HttpResponse from django.utils.html import escape as escape_html -from django.utils.timezone import now as timezone_now from django.utils.translation import ugettext as _ from sqlalchemy import func from sqlalchemy.dialects import postgresql @@ -30,19 +27,10 @@ from sqlalchemy.sql import ( ) from zerver.decorator import REQ, has_request_variables -from zerver.lib.actions import ( - check_schedule_message, - check_send_message, - compute_irc_user_fullname, - compute_jabber_user_fullname, - create_mirror_user_if_needed, - extract_private_recipients, - extract_stream_indicator, - recipient_for_user_profiles, -) +from zerver.lib.actions import recipient_for_user_profiles from zerver.lib.addressee import get_user_profiles, get_user_profiles_by_ids from zerver.lib.exceptions import ErrorCode, JsonableError -from zerver.lib.message import get_first_visible_message_id, messages_for_ids, render_markdown +from zerver.lib.message import get_first_visible_message_id, messages_for_ids from zerver.lib.response import json_error, json_success from zerver.lib.sqlalchemy_utils import get_sqlalchemy_connection from zerver.lib.streams import ( @@ -51,9 +39,7 @@ from zerver.lib.streams import ( get_public_streams_queryset, get_stream_by_narrow_operand_access_unchecked, ) -from zerver.lib.timestamp import convert_to_UTC -from zerver.lib.timezone import get_timezone -from zerver.lib.topic import DB_TOPIC_NAME, MATCH_TOPIC, REQ_topic, topic_column_sa, topic_match_sa +from zerver.lib.topic import DB_TOPIC_NAME, MATCH_TOPIC, topic_column_sa, topic_match_sa from zerver.lib.topic_mutes import exclude_topic_mutes from zerver.lib.utils import statsd from zerver.lib.validator import ( @@ -67,21 +53,14 @@ from zerver.lib.validator import ( check_string_or_int_list, to_non_negative_int, ) -from zerver.lib.zcommand import process_zcommands -from zerver.lib.zephyr import compute_mit_user_fullname from zerver.models import ( - Client, - Message, Realm, - RealmDomain, Recipient, Stream, Subscription, UserMessage, UserProfile, - email_to_domain, get_active_streams, - get_realm, get_user_by_id_in_realm_including_cross_realm, get_user_including_cross_realm, ) @@ -770,11 +749,6 @@ def find_first_unread_anchor(sa_conn: Any, return anchor -@has_request_variables -def zcommand_backend(request: HttpRequest, user_profile: UserProfile, - command: str=REQ('command')) -> HttpResponse: - return json_success(process_zcommands(command, user_profile)) - def parse_anchor_value(anchor_val: Optional[str], use_first_unread_anchor: bool) -> Optional[int]: """Given the anchor and use_first_unread_anchor parameters passed by @@ -1131,265 +1105,6 @@ def post_process_limited_query(rows: List[Any], found_oldest=found_oldest, history_limited=history_limited, ) - -class InvalidMirrorInput(Exception): - pass - -def create_mirrored_message_users(request: HttpRequest, user_profile: UserProfile, - recipients: Iterable[str]) -> UserProfile: - if "sender" not in request.POST: - raise InvalidMirrorInput("No sender") - - sender_email = request.POST["sender"].strip().lower() - referenced_users = {sender_email} - if request.POST['type'] == 'private': - for email in recipients: - referenced_users.add(email.lower()) - - if request.client.name == "zephyr_mirror": - user_check = same_realm_zephyr_user - fullname_function = compute_mit_user_fullname - elif request.client.name == "irc_mirror": - user_check = same_realm_irc_user - fullname_function = compute_irc_user_fullname - elif request.client.name in ("jabber_mirror", "JabberMirror"): - user_check = same_realm_jabber_user - fullname_function = compute_jabber_user_fullname - else: - raise InvalidMirrorInput("Unrecognized mirroring client") - - for email in referenced_users: - # Check that all referenced users are in our realm: - if not user_check(user_profile, email): - raise InvalidMirrorInput("At least one user cannot be mirrored") - - # Create users for the referenced users, if needed. - for email in referenced_users: - create_mirror_user_if_needed(user_profile.realm, email, fullname_function) - - sender = get_user_including_cross_realm(sender_email, user_profile.realm) - return sender - -def same_realm_zephyr_user(user_profile: UserProfile, email: str) -> bool: - # - # Are the sender and recipient both addresses in the same Zephyr - # mirroring realm? We have to handle this specially, inferring - # the domain from the e-mail address, because the recipient may - # not existing in Zulip and we may need to make a stub Zephyr - # mirroring user on the fly. - try: - validators.validate_email(email) - except ValidationError: - return False - - domain = email_to_domain(email) - - # Assumes allow_subdomains=False for all RealmDomain's corresponding to - # these realms. - return user_profile.realm.is_zephyr_mirror_realm and \ - RealmDomain.objects.filter(realm=user_profile.realm, domain=domain).exists() - -def same_realm_irc_user(user_profile: UserProfile, email: str) -> bool: - # Check whether the target email address is an IRC user in the - # same realm as user_profile, i.e. if the domain were example.com, - # the IRC user would need to be username@irc.example.com - try: - validators.validate_email(email) - except ValidationError: - return False - - domain = email_to_domain(email).replace("irc.", "") - - # Assumes allow_subdomains=False for all RealmDomain's corresponding to - # these realms. - return RealmDomain.objects.filter(realm=user_profile.realm, domain=domain).exists() - -def same_realm_jabber_user(user_profile: UserProfile, email: str) -> bool: - try: - validators.validate_email(email) - except ValidationError: - return False - - # If your Jabber users have a different email domain than the - # Zulip users, this is where you would do any translation. - domain = email_to_domain(email) - - # Assumes allow_subdomains=False for all RealmDomain's corresponding to - # these realms. - return RealmDomain.objects.filter(realm=user_profile.realm, domain=domain).exists() - -def handle_deferred_message(sender: UserProfile, client: Client, - message_type_name: str, - message_to: Union[Sequence[str], Sequence[int]], - topic_name: Optional[str], - message_content: str, delivery_type: str, - defer_until: str, tz_guess: Optional[str], - forwarder_user_profile: UserProfile, - realm: Optional[Realm]) -> HttpResponse: - deliver_at = None - local_tz = 'UTC' - if tz_guess: - local_tz = tz_guess - elif sender.timezone: - local_tz = sender.timezone - try: - deliver_at = dateparser(defer_until) - except ValueError: - return json_error(_("Invalid time format")) - - deliver_at_usertz = deliver_at - if deliver_at_usertz.tzinfo is None: - user_tz = get_timezone(local_tz) - # Since mypy is not able to recognize localize and normalize as attributes of tzinfo we use ignore. - deliver_at_usertz = user_tz.normalize(user_tz.localize(deliver_at)) # type: ignore[attr-defined] # Reason in comment on previous line. - deliver_at = convert_to_UTC(deliver_at_usertz) - - if deliver_at <= timezone_now(): - return json_error(_("Time must be in the future.")) - - check_schedule_message(sender, client, message_type_name, message_to, - topic_name, message_content, delivery_type, - deliver_at, realm=realm, - forwarder_user_profile=forwarder_user_profile) - return json_success({"deliver_at": str(deliver_at_usertz)}) - -@has_request_variables -def send_message_backend(request: HttpRequest, user_profile: UserProfile, - message_type_name: str=REQ('type'), - req_to: Optional[str]=REQ('to', default=None), - forged_str: Optional[str]=REQ("forged", - default=None, - documentation_pending=True), - topic_name: Optional[str]=REQ_topic(), - message_content: str=REQ('content'), - widget_content: Optional[str]=REQ(default=None, - documentation_pending=True), - realm_str: Optional[str]=REQ('realm_str', default=None, - documentation_pending=True), - local_id: Optional[str]=REQ(default=None, - documentation_pending=True), - queue_id: Optional[str]=REQ(default=None, - documentation_pending=True), - delivery_type: str=REQ('delivery_type', default='send_now', - documentation_pending=True), - defer_until: Optional[str]=REQ('deliver_at', default=None, - documentation_pending=True), - tz_guess: Optional[str]=REQ('tz_guess', default=None, - documentation_pending=True), - ) -> HttpResponse: - - # If req_to is None, then we default to an - # empty list of recipients. - message_to: Union[Sequence[int], Sequence[str]] = [] - - if req_to is not None: - if message_type_name == 'stream': - stream_indicator = extract_stream_indicator(req_to) - - # For legacy reasons check_send_message expects - # a list of streams, instead of a single stream. - # - # Also, mypy can't detect that a single-item - # list populated from a Union[int, str] is actually - # a Union[Sequence[int], Sequence[str]]. - message_to = cast( - Union[Sequence[int], Sequence[str]], - [stream_indicator], - ) - else: - message_to = extract_private_recipients(req_to) - - # Temporary hack: We're transitioning `forged` from accepting - # `yes` to accepting `true` like all of our normal booleans. - forged = forged_str is not None and forged_str in ["yes", "true"] - - client = request.client - is_super_user = request.user.is_api_super_user - if forged and not is_super_user: - return json_error(_("User not authorized for this query")) - - realm = None - if realm_str and realm_str != user_profile.realm.string_id: - if not is_super_user: - # The email gateway bot needs to be able to send messages in - # any realm. - return json_error(_("User not authorized for this query")) - try: - realm = get_realm(realm_str) - except Realm.DoesNotExist: - return json_error(_("Unknown organization '{}'").format(realm_str)) - - if client.name in ["zephyr_mirror", "irc_mirror", "jabber_mirror", "JabberMirror"]: - # Here's how security works for mirroring: - # - # For private messages, the message must be (1) both sent and - # received exclusively by users in your realm, and (2) - # received by the forwarding user. - # - # For stream messages, the message must be (1) being forwarded - # by an API superuser for your realm and (2) being sent to a - # mirrored stream. - # - # The most important security checks are in - # `create_mirrored_message_users` below, which checks the - # same-realm constraint. - if "sender" not in request.POST: - return json_error(_("Missing sender")) - if message_type_name != "private" and not is_super_user: - return json_error(_("User not authorized for this query")) - - # For now, mirroring only works with recipient emails, not for - # recipient user IDs. - if not all(isinstance(to_item, str) for to_item in message_to): - return json_error(_("Mirroring not allowed with recipient user IDs")) - - # We need this manual cast so that mypy doesn't complain about - # create_mirrored_message_users not being able to accept a Sequence[int] - # type parameter. - message_to = cast(Sequence[str], message_to) - - try: - mirror_sender = create_mirrored_message_users(request, user_profile, message_to) - except InvalidMirrorInput: - return json_error(_("Invalid mirrored message")) - - if client.name == "zephyr_mirror" and not user_profile.realm.is_zephyr_mirror_realm: - return json_error(_("Zephyr mirroring is not allowed in this organization")) - sender = mirror_sender - else: - if "sender" in request.POST: - return json_error(_("Invalid mirrored message")) - sender = user_profile - - if (delivery_type == 'send_later' or delivery_type == 'remind') and defer_until is None: - return json_error(_("Missing deliver_at in a request for delayed message delivery")) - - if (delivery_type == 'send_later' or delivery_type == 'remind') and defer_until is not None: - return handle_deferred_message(sender, client, message_type_name, - message_to, topic_name, message_content, - delivery_type, defer_until, tz_guess, - forwarder_user_profile=user_profile, - realm=realm) - - ret = check_send_message(sender, client, message_type_name, message_to, - topic_name, message_content, forged=forged, - forged_timestamp = request.POST.get('time'), - forwarder_user_profile=user_profile, realm=realm, - local_id=local_id, sender_queue_id=queue_id, - widget_content=widget_content) - return json_success({"id": ret}) - -@has_request_variables -def render_message_backend(request: HttpRequest, user_profile: UserProfile, - content: str=REQ()) -> HttpResponse: - message = Message() - message.sender = user_profile - message.content = content - message.sending_client = request.client - - rendered_content = render_markdown(message, content, realm=user_profile.realm) - return json_success({"rendered": rendered_content}) - @has_request_variables def messages_in_narrow_backend(request: HttpRequest, user_profile: UserProfile, msg_ids: List[int]=REQ(validator=check_list(check_int)), diff --git a/zproject/urls.py b/zproject/urls.py index 9ea1101801..812746917b 100644 --- a/zproject/urls.py +++ b/zproject/urls.py @@ -25,6 +25,7 @@ import zerver.views.email_mirror import zerver.views.home import zerver.views.message_edit import zerver.views.message_flags +import zerver.views.message_send import zerver.views.messages import zerver.views.muting import zerver.views.portico @@ -189,20 +190,20 @@ v1_api_and_json_patterns = [ {'POST': 'zerver.views.message_flags.mark_topic_as_read'}), url(r'^zcommand$', rest_dispatch, - {'POST': 'zerver.views.messages.zcommand_backend'}), + {'POST': 'zerver.views.message_send.zcommand_backend'}), # messages -> zerver.views.message* # GET returns messages, possibly filtered, POST sends a message url(r'^messages$', rest_dispatch, {'GET': 'zerver.views.messages.get_messages_backend', - 'POST': ('zerver.views.messages.send_message_backend', + 'POST': ('zerver.views.message_send.send_message_backend', {'allow_incoming_webhooks'})}), url(r'^messages/(?P[0-9]+)$', rest_dispatch, {'GET': 'zerver.views.message_edit.json_fetch_raw_message', 'PATCH': 'zerver.views.message_edit.update_message_backend', 'DELETE': 'zerver.views.message_edit.delete_message_backend'}), url(r'^messages/render$', rest_dispatch, - {'POST': 'zerver.views.messages.render_message_backend'}), + {'POST': 'zerver.views.message_send.render_message_backend'}), url(r'^messages/flags$', rest_dispatch, {'POST': 'zerver.views.message_flags.update_message_flags'}), url(r'^messages/(?P\d+)/history$', rest_dispatch,