diff --git a/zerver/actions/message_send.py b/zerver/actions/message_send.py index 692207a916..8437da7357 100644 --- a/zerver/actions/message_send.py +++ b/zerver/actions/message_send.py @@ -75,6 +75,7 @@ from zerver.lib.timestamp import timestamp_to_datetime from zerver.lib.topic import participants_for_topic from zerver.lib.url_preview.types import UrlEmbedData from zerver.lib.user_message import UserMessageLite, bulk_insert_ums +from zerver.lib.users import check_user_can_access_all_users, get_accessible_user_ids from zerver.lib.validator import check_widget_content from zerver.lib.widget import do_widget_post_save_actions from zerver.models import ( @@ -1460,6 +1461,21 @@ def check_private_message_policy( raise JsonableError(_("Direct messages are disabled in this organization.")) +def check_sender_can_access_recipients( + realm: Realm, sender: UserProfile, user_profiles: Sequence[UserProfile] +) -> None: + if check_user_can_access_all_users(sender): + return + + users_accessible_to_sender = set(get_accessible_user_ids(realm, sender)) + # Guest users can access all the bots (including cross-realm bots). + non_bot_recipient_user_ids = {user.id for user in user_profiles if not user.is_bot} + + inaccessible_recipients = non_bot_recipient_user_ids - users_accessible_to_sender + if inaccessible_recipients: + raise JsonableError(_("You do not have permission to access some of the recipients.")) + + # check_message: # Returns message ready for sending with do_send_message on success or the error message (string) on error. def check_message( @@ -1545,6 +1561,8 @@ def check_message( "JabberMirror", ] + check_sender_can_access_recipients(realm, sender, user_profiles) + check_private_message_policy(realm, sender, user_profiles) # API super-users who set the `forged` flag are allowed to diff --git a/zerver/tests/test_message_send.py b/zerver/tests/test_message_send.py index 82a07f9a7b..9b7332d206 100644 --- a/zerver/tests/test_message_send.py +++ b/zerver/tests/test_message_send.py @@ -777,6 +777,75 @@ class MessagePOSTTest(ZulipTestCase): ) self.assert_json_error(result, f"'{othello.email}' is no longer using Zulip.") + def test_personal_message_to_inaccessible_users(self) -> None: + othello = self.example_user("othello") + cordelia = self.example_user("cordelia") + hamlet = self.example_user("hamlet") + iago = self.example_user("iago") + + self.set_up_db_for_testing_user_access() + self.login("polonius") + + result = self.client_post( + "/json/messages", + { + "type": "direct", + "content": "Test direct message", + "to": orjson.dumps([othello.id]).decode(), + }, + ) + self.assert_json_error( + result, "You do not have permission to access some of the recipients." + ) + + result = self.client_post( + "/json/messages", + { + "type": "direct", + "content": "Test direct message", + "to": orjson.dumps([hamlet.id]).decode(), + }, + ) + self.assert_json_success(result) + msg = self.get_last_message() + self.assertEqual(msg.content, "Test direct message") + + result = self.client_post( + "/json/messages", + { + "type": "direct", + "content": "Test group direct message", + "to": orjson.dumps([othello.id, cordelia.id]).decode(), + }, + ) + self.assert_json_error( + result, "You do not have permission to access some of the recipients." + ) + + result = self.client_post( + "/json/messages", + { + "type": "direct", + "content": "Test group direct message", + "to": orjson.dumps([hamlet.id, cordelia.id]).decode(), + }, + ) + self.assert_json_error( + result, "You do not have permission to access some of the recipients." + ) + + result = self.client_post( + "/json/messages", + { + "type": "direct", + "content": "Test group direct message", + "to": orjson.dumps([hamlet.id, iago.id]).decode(), + }, + ) + self.assert_json_success(result) + msg = self.get_last_message() + self.assertEqual(msg.content, "Test group direct message") + def test_invalid_type(self) -> None: """ Sending a message of unknown type returns error JSON.