scheduled-messages: Limit `to` parameter to user and stream IDs.

For scheduled stream messages, we already limited the `to`
parameter to be the stream ID, but here we return a JsonableError
in the case of a ValueError when the passed value is not an integer.

For scheduled direct messages, we limit the list for the `to`
parameter to be user IDs. Previously, we accepted emails like
we do when sending messages.
This commit is contained in:
Lauryn Menard 2023-05-09 20:45:42 +02:00
parent cce0c57a04
commit 38f6807af1
4 changed files with 79 additions and 13 deletions

View File

@ -1,6 +1,7 @@
import datetime
from typing import List, Optional, Sequence, Tuple, Union
import orjson
from django.db import transaction
from django.utils.translation import gettext as _
@ -14,6 +15,31 @@ from zerver.models import Client, Realm, ScheduledMessage, UserProfile
from zerver.tornado.django_api import send_event
def extract_stream_id(req_to: str) -> List[int]:
# Recipient should only be a single stream ID.
try:
stream_id = int(req_to)
except ValueError:
raise JsonableError(_("Invalid data type for stream ID"))
return [stream_id]
def extract_direct_message_recipient_ids(req_to: str) -> List[int]:
try:
user_ids = orjson.loads(req_to)
except orjson.JSONDecodeError:
user_ids = req_to
if not isinstance(user_ids, list):
raise JsonableError(_("Invalid data type for recipients"))
for user_id in user_ids:
if not isinstance(user_id, int):
raise JsonableError(_("Recipient list may only contain user IDs"))
return list(set(user_ids))
def check_schedule_message(
sender: UserProfile,
client: Client,

View File

@ -5189,17 +5189,13 @@ paths:
description: |
The scheduled message's tentative target audience.
For stream messages, integer ID of the stream. For direct messages,
either a list containing integer user IDs or a list containing string
email addresses.
For stream messages, the integer ID of the stream.
For direct messages, a list containing integer user IDs.
content:
application/json:
schema:
oneOf:
- type: integer
- type: array
items:
type: string
- type: array
items:
type: integer

View File

@ -5,6 +5,11 @@ from typing import TYPE_CHECKING, List, Union
import orjson
from zerver.actions.scheduled_messages import (
extract_direct_message_recipient_ids,
extract_stream_id,
)
from zerver.lib.exceptions import JsonableError
from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.timestamp import timestamp_to_datetime
from zerver.models import Attachment, ScheduledMessage
@ -70,7 +75,7 @@ class ScheduledMessageTest(ZulipTestCase):
# Scheduling a private message is successful.
othello = self.example_user("othello")
result = self.do_schedule_message(
"direct", [othello.email], content + " 3", scheduled_delivery_timestamp
"direct", [othello.id], content + " 3", scheduled_delivery_timestamp
)
scheduled_message = self.last_scheduled_message()
self.assert_json_success(result)
@ -163,7 +168,7 @@ class ScheduledMessageTest(ZulipTestCase):
othello = self.example_user("othello")
result = self.do_schedule_message(
"direct", [othello.email], content + " 3", scheduled_delivery_timestamp
"direct", [othello.id], content + " 3", scheduled_delivery_timestamp
)
# Multiple scheduled messages
@ -288,3 +293,42 @@ class ScheduledMessageTest(ZulipTestCase):
[scheduled_message.id],
)
self.assertEqual(scheduled_message.has_attachment, True)
def test_extract_stream_id(self) -> None:
# Scheduled stream message recipient = single stream ID.
stream_id = extract_stream_id("1")
self.assertEqual(stream_id, [1])
with self.assertRaisesRegex(JsonableError, "Invalid data type for stream ID"):
extract_stream_id("1,2")
with self.assertRaisesRegex(JsonableError, "Invalid data type for stream ID"):
extract_stream_id("[1]")
with self.assertRaisesRegex(JsonableError, "Invalid data type for stream ID"):
extract_stream_id("general")
def test_extract_recipient_ids(self) -> None:
# Scheduled direct message recipients = user IDs.
user_ids = "[3,2,1]"
result = sorted(extract_direct_message_recipient_ids(user_ids))
self.assertEqual(result, [1, 2, 3])
# JSON list w/duplicates
user_ids = orjson.dumps([3, 3, 12]).decode()
result = sorted(extract_direct_message_recipient_ids(user_ids))
self.assertEqual(result, [3, 12])
# Invalid data
user_ids = "1, 12"
with self.assertRaisesRegex(JsonableError, "Invalid data type for recipients"):
extract_direct_message_recipient_ids(user_ids)
user_ids = orjson.dumps(dict(recipient=12)).decode()
with self.assertRaisesRegex(JsonableError, "Invalid data type for recipients"):
extract_direct_message_recipient_ids(user_ids)
# Heterogeneous lists are not supported
user_ids = orjson.dumps([3, 4, "eeshan@example.com"]).decode()
with self.assertRaisesRegex(JsonableError, "Recipient list may only contain user IDs"):
extract_direct_message_recipient_ids(user_ids)

View File

@ -1,13 +1,14 @@
from typing import Optional, Sequence, Union
from typing import Optional
from django.http import HttpRequest, HttpResponse
from django.utils.timezone import now as timezone_now
from django.utils.translation import gettext as _
from zerver.actions.message_send import extract_private_recipients
from zerver.actions.scheduled_messages import (
check_schedule_message,
delete_scheduled_message,
extract_direct_message_recipient_ids,
extract_stream_id,
)
from zerver.lib.exceptions import JsonableError
from zerver.lib.request import REQ, RequestNotes, has_request_variables
@ -61,10 +62,9 @@ def scheduled_messages_backend(
assert client is not None
if recipient_type_name == "stream":
# req_to is ID of the recipient stream.
message_to: Union[Sequence[str], Sequence[int]] = [int(req_to)]
message_to = extract_stream_id(req_to)
else:
message_to = extract_private_recipients(req_to)
message_to = extract_direct_message_recipient_ids(req_to)
scheduled_message_id = check_schedule_message(
sender,