mirror of https://github.com/zulip/zulip.git
views: Extract message_send.py for sending views.
This commit is contained in:
parent
55448ecf24
commit
3657717ade
|
@ -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()
|
||||
|
|
|
@ -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})
|
|
@ -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)),
|
||||
|
|
|
@ -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<message_id>[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<message_id>\d+)/history$', rest_dispatch,
|
||||
|
|
Loading…
Reference in New Issue