From c136eebb330abbd9031cedfc3532ed93489b7234 Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Thu, 14 Apr 2022 14:29:05 -0700 Subject: [PATCH] actions: Split out zerver.lib.recipient_users. Signed-off-by: Anders Kaseorg --- zerver/lib/actions.py | 93 +-------------------------------- zerver/lib/drafts.py | 2 +- zerver/lib/recipient_users.py | 97 +++++++++++++++++++++++++++++++++++ zerver/views/message_fetch.py | 2 +- 4 files changed, 100 insertions(+), 94 deletions(-) create mode 100644 zerver/lib/recipient_users.py diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py index 00fa7112fa..e9627da5cc 100644 --- a/zerver/lib/actions.py +++ b/zerver/lib/actions.py @@ -118,6 +118,7 @@ from zerver.lib.pysa import mark_sanitized from zerver.lib.queue import queue_json_publish from zerver.lib.realm_icon import realm_icon_url from zerver.lib.realm_logo import get_realm_logo_data +from zerver.lib.recipient_users import recipient_for_user_profiles from zerver.lib.retention import move_messages_to_archive from zerver.lib.send_email import ( FromAddress, @@ -270,7 +271,6 @@ from zerver.models import ( get_client, get_default_stream_groups, get_fake_email_domain, - get_huddle_recipient, get_huddle_user_ids, get_old_unclaimed_attachments, get_realm, @@ -2710,97 +2710,6 @@ def ensure_stream( )[0] -def get_recipient_from_user_profiles( - recipient_profiles: Sequence[UserProfile], - forwarded_mirror_message: bool, - forwarder_user_profile: Optional[UserProfile], - sender: UserProfile, -) -> Recipient: - - # Avoid mutating the passed in list of recipient_profiles. - recipient_profiles_map = {user_profile.id: user_profile for user_profile in recipient_profiles} - - if forwarded_mirror_message: - # In our mirroring integrations with some third-party - # protocols, bots subscribed to the third-party protocol - # forward to Zulip messages that they received in the - # third-party service. The permissions model for that - # forwarding is that users can only submit to Zulip private - # messages they personally received, and here we do the check - # for whether forwarder_user_profile is among the private - # message recipients of the message. - assert forwarder_user_profile is not None - if forwarder_user_profile.id not in recipient_profiles_map: - raise ValidationError(_("User not authorized for this query")) - - # If the private message is just between the sender and - # another person, force it to be a personal internally - if len(recipient_profiles_map) == 2 and sender.id in recipient_profiles_map: - del recipient_profiles_map[sender.id] - - assert recipient_profiles_map - if len(recipient_profiles_map) == 1: - [user_profile] = recipient_profiles_map.values() - return Recipient( - id=user_profile.recipient_id, - type=Recipient.PERSONAL, - type_id=user_profile.id, - ) - - # Otherwise, we need a huddle. Make sure the sender is included in huddle messages - recipient_profiles_map[sender.id] = sender - - user_ids = set(recipient_profiles_map) - return get_huddle_recipient(user_ids) - - -def validate_recipient_user_profiles( - user_profiles: Sequence[UserProfile], sender: UserProfile, allow_deactivated: bool = False -) -> Sequence[UserProfile]: - recipient_profiles_map: Dict[int, UserProfile] = {} - - # We exempt cross-realm bots from the check that all the recipients - # are in the same realm. - realms = set() - if not is_cross_realm_bot_email(sender.email): - realms.add(sender.realm_id) - - for user_profile in user_profiles: - if ( - not user_profile.is_active - and not user_profile.is_mirror_dummy - and not allow_deactivated - ) or user_profile.realm.deactivated: - raise ValidationError( - _("'{email}' is no longer using Zulip.").format(email=user_profile.email) - ) - recipient_profiles_map[user_profile.id] = user_profile - if not is_cross_realm_bot_email(user_profile.email): - realms.add(user_profile.realm_id) - - if len(realms) > 1: - raise ValidationError(_("You can't send private messages outside of your organization.")) - - return list(recipient_profiles_map.values()) - - -def recipient_for_user_profiles( - user_profiles: Sequence[UserProfile], - forwarded_mirror_message: bool, - forwarder_user_profile: Optional[UserProfile], - sender: UserProfile, - allow_deactivated: bool = False, -) -> Recipient: - - recipient_profiles = validate_recipient_user_profiles( - user_profiles, sender, allow_deactivated=allow_deactivated - ) - - return get_recipient_from_user_profiles( - recipient_profiles, forwarded_mirror_message, forwarder_user_profile, sender - ) - - def already_sent_mirrored_message_id(message: Message) -> Optional[int]: if message.recipient.type == Recipient.HUDDLE: # For huddle messages, we use a 10-second window because the diff --git a/zerver/lib/drafts.py b/zerver/lib/drafts.py index 10e113385a..11667abfc1 100644 --- a/zerver/lib/drafts.py +++ b/zerver/lib/drafts.py @@ -6,10 +6,10 @@ from django.core.exceptions import ValidationError from django.http import HttpRequest, HttpResponse from django.utils.translation import gettext as _ -from zerver.lib.actions import recipient_for_user_profiles from zerver.lib.addressee import get_user_profiles_by_ids from zerver.lib.exceptions import JsonableError, ResourceNotFoundError from zerver.lib.message import normalize_body, truncate_topic +from zerver.lib.recipient_users import recipient_for_user_profiles from zerver.lib.streams import access_stream_by_id from zerver.lib.timestamp import timestamp_to_datetime from zerver.lib.types import ViewFuncT diff --git a/zerver/lib/recipient_users.py b/zerver/lib/recipient_users.py new file mode 100644 index 0000000000..d7378c9844 --- /dev/null +++ b/zerver/lib/recipient_users.py @@ -0,0 +1,97 @@ +from typing import Dict, Optional, Sequence + +from django.core.exceptions import ValidationError +from django.utils.translation import gettext as _ + +from zerver.models import Recipient, UserProfile, get_huddle_recipient, is_cross_realm_bot_email + + +def get_recipient_from_user_profiles( + recipient_profiles: Sequence[UserProfile], + forwarded_mirror_message: bool, + forwarder_user_profile: Optional[UserProfile], + sender: UserProfile, +) -> Recipient: + + # Avoid mutating the passed in list of recipient_profiles. + recipient_profiles_map = {user_profile.id: user_profile for user_profile in recipient_profiles} + + if forwarded_mirror_message: + # In our mirroring integrations with some third-party + # protocols, bots subscribed to the third-party protocol + # forward to Zulip messages that they received in the + # third-party service. The permissions model for that + # forwarding is that users can only submit to Zulip private + # messages they personally received, and here we do the check + # for whether forwarder_user_profile is among the private + # message recipients of the message. + assert forwarder_user_profile is not None + if forwarder_user_profile.id not in recipient_profiles_map: + raise ValidationError(_("User not authorized for this query")) + + # If the private message is just between the sender and + # another person, force it to be a personal internally + if len(recipient_profiles_map) == 2 and sender.id in recipient_profiles_map: + del recipient_profiles_map[sender.id] + + assert recipient_profiles_map + if len(recipient_profiles_map) == 1: + [user_profile] = recipient_profiles_map.values() + return Recipient( + id=user_profile.recipient_id, + type=Recipient.PERSONAL, + type_id=user_profile.id, + ) + + # Otherwise, we need a huddle. Make sure the sender is included in huddle messages + recipient_profiles_map[sender.id] = sender + + user_ids = set(recipient_profiles_map) + return get_huddle_recipient(user_ids) + + +def validate_recipient_user_profiles( + user_profiles: Sequence[UserProfile], sender: UserProfile, allow_deactivated: bool = False +) -> Sequence[UserProfile]: + recipient_profiles_map: Dict[int, UserProfile] = {} + + # We exempt cross-realm bots from the check that all the recipients + # are in the same realm. + realms = set() + if not is_cross_realm_bot_email(sender.email): + realms.add(sender.realm_id) + + for user_profile in user_profiles: + if ( + not user_profile.is_active + and not user_profile.is_mirror_dummy + and not allow_deactivated + ) or user_profile.realm.deactivated: + raise ValidationError( + _("'{email}' is no longer using Zulip.").format(email=user_profile.email) + ) + recipient_profiles_map[user_profile.id] = user_profile + if not is_cross_realm_bot_email(user_profile.email): + realms.add(user_profile.realm_id) + + if len(realms) > 1: + raise ValidationError(_("You can't send private messages outside of your organization.")) + + return list(recipient_profiles_map.values()) + + +def recipient_for_user_profiles( + user_profiles: Sequence[UserProfile], + forwarded_mirror_message: bool, + forwarder_user_profile: Optional[UserProfile], + sender: UserProfile, + allow_deactivated: bool = False, +) -> Recipient: + + recipient_profiles = validate_recipient_user_profiles( + user_profiles, sender, allow_deactivated=allow_deactivated + ) + + return get_recipient_from_user_profiles( + recipient_profiles, forwarded_mirror_message, forwarder_user_profile, sender + ) diff --git a/zerver/views/message_fetch.py b/zerver/views/message_fetch.py index 4d339c672b..6f7a4ef225 100644 --- a/zerver/views/message_fetch.py +++ b/zerver/views/message_fetch.py @@ -31,11 +31,11 @@ from sqlalchemy.sql.selectable import SelectBase from sqlalchemy.types import ARRAY, Boolean, Integer, Text from zerver.context_processors import get_valid_realm_from_request -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, MissingAuthenticationError from zerver.lib.message import get_first_visible_message_id, messages_for_ids from zerver.lib.narrow import is_spectator_compatible, is_web_public_narrow +from zerver.lib.recipient_users import recipient_for_user_profiles from zerver.lib.request import REQ, RequestNotes, has_request_variables from zerver.lib.response import json_success from zerver.lib.sqlalchemy_utils import get_sqlalchemy_connection