Esempio n. 1
0
    def test_missed_message(self) -> None:
        email = self.example_email('othello')
        self.login(email)
        result = self.client_post(
            "/json/messages", {
                "type":
                "private",
                "content":
                "test_receive_missed_message_email_messages",
                "client":
                "test suite",
                "to":
                ujson.dumps([
                    self.example_email('cordelia'),
                    self.example_email('iago')
                ])
            })
        self.assert_json_success(result)

        user_profile = self.example_user('cordelia')
        usermessage = most_recent_usermessage(user_profile)
        with self.settings(EMAIL_GATEWAY_PATTERN=''):
            mm_address = create_missed_message_address(user_profile,
                                                       usermessage.message)
            self.assertEqual(mm_address, FromAddress.NOREPLY)
Esempio n. 2
0
    def test_receive_missed_huddle_message_email_messages(self) -> None:

        # build dummy messages for missed messages email reply
        # have Othello send Iago and Cordelia a PM. Cordelia will reply via email
        # Iago and Othello will receive the message.
        email = self.example_email('othello')
        self.login(email)
        result = self.client_post(
            "/json/messages", {
                "type":
                "private",
                "content":
                "test_receive_missed_message_email_messages",
                "client":
                "test suite",
                "to":
                ujson.dumps([
                    self.example_email('cordelia'),
                    self.example_email('iago')
                ])
            })
        self.assert_json_success(result)

        user_profile = self.example_user('cordelia')
        usermessage = most_recent_usermessage(user_profile)

        # we don't want to send actual emails but we do need to create and store the
        # token for looking up who did reply.
        mm_address = create_missed_message_address(user_profile,
                                                   usermessage.message)

        incoming_valid_message = MIMEText(
            'TestMissedHuddleMessageEmailMessages Body'
        )  # type: Any # https://github.com/python/typeshed/issues/275

        incoming_valid_message[
            'Subject'] = 'TestMissedHuddleMessageEmailMessages Subject'
        incoming_valid_message['From'] = self.example_email('cordelia')
        incoming_valid_message['To'] = mm_address
        incoming_valid_message['Reply-to'] = self.example_email('cordelia')

        process_message(incoming_valid_message)

        # Confirm Iago received the message.
        user_profile = self.example_user('iago')
        message = most_recent_message(user_profile)

        self.assertEqual(message.content,
                         "TestMissedHuddleMessageEmailMessages Body")
        self.assertEqual(message.sender, self.example_user('cordelia'))
        self.assertEqual(message.recipient.type, Recipient.HUDDLE)

        # Confirm Othello received the message.
        user_profile = self.example_user('othello')
        message = most_recent_message(user_profile)

        self.assertEqual(message.content,
                         "TestMissedHuddleMessageEmailMessages Body")
        self.assertEqual(message.sender, self.example_user('cordelia'))
        self.assertEqual(message.recipient.type, Recipient.HUDDLE)
    def test_receive_missed_message_email_token_missing_data(self) -> None:
        email = self.example_email('hamlet')
        self.login(email)
        result = self.client_post("/json/messages", {"type": "private",
                                                     "content": "test_receive_missed_message_email_token_missing_data",
                                                     "client": "test suite",
                                                     "to": self.example_email('othello')})
        self.assert_json_success(result)

        user_profile = self.example_user('othello')
        usermessage = most_recent_usermessage(user_profile)

        mm_address = create_missed_message_address(user_profile, usermessage.message)

        incoming_valid_message = MIMEText('TestMissedMessageEmailMessages Body')

        incoming_valid_message['Subject'] = 'TestMissedMessageEmailMessages Subject'
        incoming_valid_message['From'] = self.example_email('othello')
        incoming_valid_message['To'] = mm_address
        incoming_valid_message['Reply-to'] = self.example_email('othello')

        # We need to force redis_client.hmget to return some None values:
        with mock.patch('zerver.lib.email_mirror.redis_client.hmget',
                        return_value=[None, None, None]):
            exception_message = ''
            try:
                process_missed_message(mm_address, incoming_valid_message, False)
            except ZulipEmailForwardError as e:
                exception_message = str(e)

            self.assertEqual(exception_message, 'Missing missed message address data')
Esempio n. 4
0
def do_send_missedmessage_events_reply_in_zulip(user_profile, missed_messages, message_count):
    # type: (UserProfile, List[Message], int) -> None
    """
    Send a reminder email to a user if she's missed some PMs by being offline.

    The email will have its reply to address set to a limited used email
    address that will send a zulip message to the correct recipient. This
    allows the user to respond to missed PMs, huddles, and @-mentions directly
    from the email.

    `user_profile` is the user to send the reminder to
    `missed_messages` is a list of Message objects to remind about they should
                      all have the same recipient and subject
    """
    # Disabled missedmessage emails internally
    if not user_profile.enable_offline_email_notifications:
        return

    recipients = set((msg.recipient_id, msg.subject) for msg in missed_messages)
    if len(recipients) != 1:
        raise ValueError(
            'All missed_messages must have the same recipient and subject %r' %
            recipients
        )

    template_payload = {
        'name': user_profile.full_name,
        'messages': build_message_list(user_profile, missed_messages),
        'message_count': message_count,
        'url': 'https://%s' % (settings.EXTERNAL_HOST,),
        'reply_warning': False,
        'external_host': settings.EXTERNAL_HOST,
        'mention': missed_messages[0].recipient.type == Recipient.STREAM,
        'reply_to_zulip': True,
    }

    headers = {}
    from zerver.lib.email_mirror import create_missed_message_address
    address = create_missed_message_address(user_profile, missed_messages[0])
    headers['Reply-To'] = address

    senders = set(m.sender.full_name for m in missed_messages)
    sender_str = ", ".join(senders)
    plural_messages = 's' if len(missed_messages) > 1 else ''

    subject = "Missed Zulip%s from %s" % (plural_messages, sender_str)
    from_email = "%s (via Zulip) <%s>" % (sender_str, settings.NOREPLY_EMAIL_ADDRESS)

    text_content = loader.render_to_string('zerver/missed_message_email.txt', template_payload)
    html_content = loader.render_to_string('zerver/missed_message_email_html.txt', template_payload)

    msg = EmailMultiAlternatives(subject, text_content, from_email, [user_profile.email],
                                 headers = headers)
    msg.attach_alternative(html_content, "text/html")
    msg.send()

    user_profile.last_reminder = datetime.datetime.now()
    user_profile.save(update_fields=['last_reminder'])
Esempio n. 5
0
def do_send_missedmessage_events_reply_in_zulip(user_profile, missed_messages, message_count):
    # type: (UserProfile, List[Message], int) -> None
    """
    Send a reminder email to a user if she's missed some PMs by being offline.

    The email will have its reply to address set to a limited used email
    address that will send a zulip message to the correct recipient. This
    allows the user to respond to missed PMs, huddles, and @-mentions directly
    from the email.

    `user_profile` is the user to send the reminder to
    `missed_messages` is a list of Message objects to remind about they should
                      all have the same recipient and subject
    """
    # Disabled missedmessage emails internally
    if not user_profile.enable_offline_email_notifications:
        return

    recipients = set((msg.recipient_id, msg.subject) for msg in missed_messages)
    if len(recipients) != 1:
        raise ValueError(
            'All missed_messages must have the same recipient and subject %r' %
            recipients
        )

    template_payload = {
        'name': user_profile.full_name,
        'messages': build_message_list(user_profile, missed_messages),
        'message_count': message_count,
        'url': 'https://%s' % (settings.EXTERNAL_HOST,),
        'reply_warning': False,
        'external_host': settings.EXTERNAL_HOST,
        'mention': missed_messages[0].recipient.type == Recipient.STREAM,
        'reply_to_zulip': True,
    }

    headers = {}
    from zerver.lib.email_mirror import create_missed_message_address
    address = create_missed_message_address(user_profile, missed_messages[0])
    headers['Reply-To'] = address

    senders = set(m.sender.full_name for m in missed_messages)
    sender_str = ", ".join(senders)
    plural_messages = 's' if len(missed_messages) > 1 else ''

    subject = "Missed Zulip%s from %s" % (plural_messages, sender_str)
    from_email = "%s (via Zulip) <%s>" % (sender_str, settings.NOREPLY_EMAIL_ADDRESS)

    text_content = loader.render_to_string('zerver/missed_message_email.txt', template_payload)
    html_content = loader.render_to_string('zerver/missed_message_email_html.txt', template_payload)

    msg = EmailMultiAlternatives(subject, text_content, from_email, [user_profile.email],
                                 headers = headers)
    msg.attach_alternative(html_content, "text/html")
    msg.send()

    user_profile.last_reminder = datetime.datetime.now()
    user_profile.save(update_fields=['last_reminder'])
Esempio n. 6
0
    def test_receive_missed_huddle_message_email_messages(self):

        # build dummy messages for missed messages email reply
        # have Othello send Iago and Cordelia a PM. Cordelia will reply via email
        # Iago and Othello will receive the message.
        self.login("*****@*****.**")
        result = self.client.post(
            "/json/messages", {
                "type": "private",
                "content": "test_receive_missed_message_email_messages",
                "client": "test suite",
                "to": ujson.dumps(["*****@*****.**", "*****@*****.**"])
            })
        self.assert_json_success(result)

        user_profile = get_user_profile_by_email("*****@*****.**")
        usermessage = most_recent_usermessage(user_profile)

        # we don't want to send actual emails but we do need to create and store the
        # token for looking up who did reply.
        mm_address = create_missed_message_address(user_profile,
                                                   usermessage.message)

        incoming_valid_message = MIMEText(
            'TestMissedHuddleMessageEmailMessages Body')

        incoming_valid_message[
            'Subject'] = 'TestMissedHuddleMessageEmailMessages Subject'
        incoming_valid_message['From'] = "*****@*****.**"
        incoming_valid_message['To'] = mm_address
        incoming_valid_message['Reply-to'] = "*****@*****.**"

        process_message(incoming_valid_message)

        # Confirm Iago received the message.
        user_profile = get_user_profile_by_email("*****@*****.**")
        message = most_recent_message(user_profile)

        self.assertEqual(message.content,
                         "TestMissedHuddleMessageEmailMessages Body")
        self.assertEqual(message.sender,
                         get_user_profile_by_email("*****@*****.**"))
        self.assertEqual(message.recipient.type, Recipient.HUDDLE)

        # Confirm Othello received the message.
        user_profile = get_user_profile_by_email("*****@*****.**")
        message = most_recent_message(user_profile)

        self.assertEqual(message.content,
                         "TestMissedHuddleMessageEmailMessages Body")
        self.assertEqual(message.sender,
                         get_user_profile_by_email("*****@*****.**"))
        self.assertEqual(message.recipient.type, Recipient.HUDDLE)
Esempio n. 7
0
    def test_missed_message(self) -> None:
        email = self.example_email('othello')
        self.login(email)
        result = self.client_post("/json/messages", {"type": "private",
                                                     "content": "test_receive_missed_message_email_messages",
                                                     "client": "test suite",
                                                     "to": ujson.dumps([self.example_email('cordelia'),
                                                                        self.example_email('iago')])})
        self.assert_json_success(result)

        user_profile = self.example_user('cordelia')
        usermessage = most_recent_usermessage(user_profile)
        with self.settings(EMAIL_GATEWAY_PATTERN=''):
            mm_address = create_missed_message_address(user_profile, usermessage.message)
            self.assertEqual(mm_address, FromAddress.NOREPLY)
Esempio n. 8
0
    def send_private_message(self) -> str:
        email = self.example_email('othello')
        self.login(email)
        result = self.client_post(
            "/json/messages",
            {
                "type": "private",
                "content": "test_receive_missed_message_email_messages",
                "client": "test suite",
                "to": ujson.dumps([self.example_email('cordelia'), self.example_email('iago')])
            })
        self.assert_json_success(result)

        user_profile = self.example_user('cordelia')
        user_message = most_recent_usermessage(user_profile)
        return create_missed_message_address(user_profile, user_message.message)
Esempio n. 9
0
    def send_private_message(self) -> Text:
        email = self.example_email('othello')
        self.login(email)
        result = self.client_post(
            "/json/messages",
            {
                "type": "private",
                "content": "test_receive_missed_message_email_messages",
                "client": "test suite",
                "to": ujson.dumps([self.example_email('cordelia'), self.example_email('iago')])
            })
        self.assert_json_success(result)

        user_profile = self.example_user('cordelia')
        user_message = most_recent_usermessage(user_profile)
        return create_missed_message_address(user_profile, user_message.message)
Esempio n. 10
0
    def test_receive_missed_personal_message_email_messages(self):
        # type: () -> None

        # build dummy messages for missed messages email reply
        # have Hamlet send Othello a PM. Othello will reply via email
        # Hamlet will receive the message.
        email = self.example_email('hamlet')
        self.login(email)
        result = self.client_post(
            "/json/messages", {
                "type": "private",
                "content": "test_receive_missed_message_email_messages",
                "client": "test suite",
                "to": self.example_email('othello')
            })
        self.assert_json_success(result)

        user_profile = self.example_user('othello')
        usermessage = most_recent_usermessage(user_profile)

        # we don't want to send actual emails but we do need to create and store the
        # token for looking up who did reply.
        mm_address = create_missed_message_address(user_profile,
                                                   usermessage.message)

        incoming_valid_message = MIMEText(
            'TestMissedMessageEmailMessages Body'
        )  # type: Any # https://github.com/python/typeshed/issues/275

        incoming_valid_message[
            'Subject'] = 'TestMissedMessageEmailMessages Subject'
        incoming_valid_message['From'] = self.example_email('othello')
        incoming_valid_message['To'] = mm_address
        incoming_valid_message['Reply-to'] = self.example_email('othello')

        process_message(incoming_valid_message)

        # self.login(self.example_email("hamlet"))
        # confirm that Hamlet got the message
        user_profile = self.example_user('hamlet')
        message = most_recent_message(user_profile)

        self.assertEqual(message.content,
                         "TestMissedMessageEmailMessages Body")
        self.assertEqual(message.sender, self.example_user('othello'))
        self.assertEqual(message.recipient.id, user_profile.id)
        self.assertEqual(message.recipient.type, Recipient.PERSONAL)
Esempio n. 11
0
    def test_receive_missed_huddle_message_email_messages(self):
        # type: () -> None

        # build dummy messages for missed messages email reply
        # have Othello send Iago and Cordelia a PM. Cordelia will reply via email
        # Iago and Othello will receive the message.
        email = self.example_email('othello')
        self.login(email)
        result = self.client_post("/json/messages", {"type": "private",
                                                     "content": "test_receive_missed_message_email_messages",
                                                     "client": "test suite",
                                                     "to": ujson.dumps([self.example_email('cordelia'),
                                                                        self.example_email('iago')])})
        self.assert_json_success(result)

        user_profile = self.example_user('cordelia')
        usermessage = most_recent_usermessage(user_profile)

        # we don't want to send actual emails but we do need to create and store the
        # token for looking up who did reply.
        mm_address = create_missed_message_address(user_profile, usermessage.message)

        incoming_valid_message = MIMEText('TestMissedHuddleMessageEmailMessages Body')  # type: Any # https://github.com/python/typeshed/issues/275

        incoming_valid_message['Subject'] = 'TestMissedHuddleMessageEmailMessages Subject'
        incoming_valid_message['From'] = self.example_email('cordelia')
        incoming_valid_message['To'] = mm_address
        incoming_valid_message['Reply-to'] = self.example_email('cordelia')

        process_message(incoming_valid_message)

        # Confirm Iago received the message.
        user_profile = self.example_user('iago')
        message = most_recent_message(user_profile)

        self.assertEqual(message.content, "TestMissedHuddleMessageEmailMessages Body")
        self.assertEqual(message.sender, self.example_user('cordelia'))
        self.assertEqual(message.recipient.type, Recipient.HUDDLE)

        # Confirm Othello received the message.
        user_profile = self.example_user('othello')
        message = most_recent_message(user_profile)

        self.assertEqual(message.content, "TestMissedHuddleMessageEmailMessages Body")
        self.assertEqual(message.sender, self.example_user('cordelia'))
        self.assertEqual(message.recipient.type, Recipient.HUDDLE)
Esempio n. 12
0
    def test_receive_missed_personal_message_email_messages(self):

        # build dummy messages for missed messages email reply
        # have Hamlet send Othello a PM. Othello will reply via email
        # Hamlet will receive the message.
        self.login("*****@*****.**")
        result = self.client.post(
            "/json/messages", {
                "type": "private",
                "content": "test_receive_missed_message_email_messages",
                "client": "test suite",
                "to": "*****@*****.**"
            })
        self.assert_json_success(result)

        user_profile = get_user_profile_by_email("*****@*****.**")
        usermessage = most_recent_usermessage(user_profile)

        # we don't want to send actual emails but we do need to create and store the
        # token for looking up who did reply.
        mm_address = create_missed_message_address(user_profile,
                                                   usermessage.message)

        incoming_valid_message = MIMEText(
            'TestMissedMessageEmailMessages Body')

        incoming_valid_message[
            'Subject'] = 'TestMissedMessageEmailMessages Subject'
        incoming_valid_message['From'] = "*****@*****.**"
        incoming_valid_message['To'] = mm_address
        incoming_valid_message['Reply-to'] = "*****@*****.**"

        process_message(incoming_valid_message)

        # self.login("*****@*****.**")
        # confirm that Hamlet got the message
        user_profile = get_user_profile_by_email("*****@*****.**")
        message = most_recent_message(user_profile)

        self.assertEqual(message.content,
                         "TestMissedMessageEmailMessages Body")
        self.assertEqual(message.sender,
                         get_user_profile_by_email("*****@*****.**"))
        self.assertEqual(message.recipient.id, user_profile.id)
        self.assertEqual(message.recipient.type, Recipient.PERSONAL)
Esempio n. 13
0
    def test_receive_missed_huddle_message_email_messages(self):

        # build dummy messages for missed messages email reply
        # have Othello send Iago and Cordelia a PM. Cordelia will reply via email
        # Iago and Othello will receive the message.
        self.login("*****@*****.**")
        result = self.client.post("/json/send_message", {"type": "private",
                                                         "content": "test_receive_missed_message_email_messages",
                                                         "client": "test suite",
                                                         "to": ujson.dumps(["*****@*****.**",
                                                                            "*****@*****.**"])})
        self.assert_json_success(result)

        user_profile = get_user_profile_by_email("*****@*****.**")
        usermessage = most_recent_usermessage(user_profile)

        # we don't want to send actual emails but we do need to create and store the
        # token for looking up who did reply.
        mm_address = create_missed_message_address(user_profile, usermessage.message)

        incoming_valid_message = MIMEText('TestMissedHuddleMessageEmailMessages Body')

        incoming_valid_message['Subject'] = 'TestMissedHuddleMessageEmailMessages Subject'
        incoming_valid_message['From'] = "*****@*****.**"
        incoming_valid_message['To'] = mm_address
        incoming_valid_message['Reply-to'] = "*****@*****.**"

        process_message(incoming_valid_message)

        # Confirm Iago received the message.
        user_profile = get_user_profile_by_email("*****@*****.**")
        message = most_recent_message(user_profile)

        self.assertEqual(message.content, "TestMissedHuddleMessageEmailMessages Body")
        self.assertEqual(message.sender, get_user_profile_by_email("*****@*****.**"))
        self.assertEqual(message.recipient.type, Recipient.HUDDLE)

        # Confirm Othello received the message.
        user_profile = get_user_profile_by_email("*****@*****.**")
        message = most_recent_message(user_profile)

        self.assertEqual(message.content, "TestMissedHuddleMessageEmailMessages Body")
        self.assertEqual(message.sender, get_user_profile_by_email("*****@*****.**"))
        self.assertEqual(message.recipient.type, Recipient.HUDDLE)
Esempio n. 14
0
    def test_receive_missed_stream_message_email_messages(self) -> None:
        # build dummy messages for missed messages email reply
        # have Hamlet send a message to stream Denmark, that Othello
        # will receive a missed message email about.
        # Othello will reply via email.
        # Hamlet will see the message in the stream.
        self.subscribe(self.example_user("hamlet"), "Denmark")
        self.subscribe(self.example_user("othello"), "Denmark")
        email = self.example_email('hamlet')
        self.login(email)
        result = self.client_post("/json/messages", {"type": "stream",
                                                     "topic": "test topic",
                                                     "content": "test_receive_missed_stream_message_email_messages",
                                                     "client": "test suite",
                                                     "to": "Denmark"})
        self.assert_json_success(result)

        user_profile = self.example_user('othello')
        usermessage = most_recent_usermessage(user_profile)

        mm_address = create_missed_message_address(user_profile, usermessage.message)

        incoming_valid_message = MIMEText('TestMissedMessageEmailMessages Body')

        incoming_valid_message['Subject'] = 'TestMissedMessageEmailMessages Subject'
        incoming_valid_message['From'] = self.example_email('othello')
        incoming_valid_message['To'] = mm_address
        incoming_valid_message['Reply-to'] = self.example_email('othello')

        process_message(incoming_valid_message)

        # confirm that Hamlet got the message
        user_profile = self.example_user('hamlet')
        message = most_recent_message(user_profile)

        self.assertEqual(message.content, "TestMissedMessageEmailMessages Body")
        self.assertEqual(message.sender, self.example_user('othello'))
        self.assertEqual(message.recipient.type, Recipient.STREAM)
        self.assertEqual(message.recipient.id, usermessage.message.recipient.id)
Esempio n. 15
0
    def test_receive_missed_personal_message_email_messages(self):
        # type: () -> None

        # build dummy messages for missed messages email reply
        # have Hamlet send Othello a PM. Othello will reply via email
        # Hamlet will receive the message.
        email = self.example_email('hamlet')
        self.login(email)
        result = self.client_post("/json/messages", {"type": "private",
                                                     "content": "test_receive_missed_message_email_messages",
                                                     "client": "test suite",
                                                     "to": self.example_email('othello')})
        self.assert_json_success(result)

        user_profile = self.example_user('othello')
        usermessage = most_recent_usermessage(user_profile)

        # we don't want to send actual emails but we do need to create and store the
        # token for looking up who did reply.
        mm_address = create_missed_message_address(user_profile, usermessage.message)

        incoming_valid_message = MIMEText('TestMissedMessageEmailMessages Body')  # type: Any # https://github.com/python/typeshed/issues/275

        incoming_valid_message['Subject'] = 'TestMissedMessageEmailMessages Subject'
        incoming_valid_message['From'] = self.example_email('othello')
        incoming_valid_message['To'] = mm_address
        incoming_valid_message['Reply-to'] = self.example_email('othello')

        process_message(incoming_valid_message)

        # self.login(self.example_email("hamlet"))
        # confirm that Hamlet got the message
        user_profile = self.example_user('hamlet')
        message = most_recent_message(user_profile)

        self.assertEqual(message.content, "TestMissedMessageEmailMessages Body")
        self.assertEqual(message.sender, self.example_user('othello'))
        self.assertEqual(message.recipient.id, user_profile.id)
        self.assertEqual(message.recipient.type, Recipient.PERSONAL)
Esempio n. 16
0
    def test_receive_missed_personal_message_email_messages(self):

        # build dummy messages for missed messages email reply
        # have Hamlet send Othello a PM. Othello will reply via email
        # Hamlet will receive the message.
        self.login("*****@*****.**")
        result = self.client.post("/json/send_message", {"type": "private",
                                                         "content": "test_receive_missed_message_email_messages",
                                                         "client": "test suite",
                                                         "to": "*****@*****.**"})
        self.assert_json_success(result)

        user_profile = get_user_profile_by_email("*****@*****.**")
        usermessage = most_recent_usermessage(user_profile)

        # we don't want to send actual emails but we do need to create and store the
        # token for looking up who did reply.
        mm_address = create_missed_message_address(user_profile, usermessage.message)

        incoming_valid_message = MIMEText('TestMissedMessageEmailMessages Body')

        incoming_valid_message['Subject'] = 'TestMissedMessageEmailMessages Subject'
        incoming_valid_message['From'] = "*****@*****.**"
        incoming_valid_message['To'] = mm_address
        incoming_valid_message['Reply-to'] = "*****@*****.**"

        process_message(incoming_valid_message)

        # self.login("*****@*****.**")
        # confirm that Hamlet got the message
        user_profile = get_user_profile_by_email("*****@*****.**")
        message = most_recent_message(user_profile)

        self.assertEqual(message.content, "TestMissedMessageEmailMessages Body")
        self.assertEqual(message.sender, get_user_profile_by_email("*****@*****.**"))
        self.assertEqual(message.recipient.id, user_profile.id)
        self.assertEqual(message.recipient.type, Recipient.PERSONAL)
Esempio n. 17
0
def do_send_missedmessage_events_reply_in_zulip(user_profile: UserProfile,
                                                missed_messages: List[Dict[str, Any]],
                                                message_count: int) -> None:
    """
    Send a reminder email to a user if she's missed some PMs by being offline.

    The email will have its reply to address set to a limited used email
    address that will send a zulip message to the correct recipient. This
    allows the user to respond to missed PMs, huddles, and @-mentions directly
    from the email.

    `user_profile` is the user to send the reminder to
    `missed_messages` is a list of dictionaries to Message objects and other data
                      for a group of messages that share a recipient (and topic)
    """
    from zerver.context_processors import common_context
    # Disabled missedmessage emails internally
    if not user_profile.enable_offline_email_notifications:
        return

    recipients = set((msg['message'].recipient_id, msg['message'].topic_name()) for msg in missed_messages)
    if len(recipients) != 1:
        raise ValueError(
            'All missed_messages must have the same recipient and topic %r' %
            recipients
        )

    unsubscribe_link = one_click_unsubscribe_link(user_profile, "missed_messages")
    context = common_context(user_profile)
    context.update({
        'name': user_profile.full_name,
        'message_count': message_count,
        'unsubscribe_link': unsubscribe_link,
        'realm_name_in_notifications': user_profile.realm_name_in_notifications,
        'show_message_content': user_profile.message_content_in_email_notifications,
    })

    triggers = list(message['trigger'] for message in missed_messages)
    unique_triggers = set(triggers)
    context.update({
        'mention': 'mentioned' in unique_triggers,
        'mention_count': triggers.count('mentioned'),
    })

    # If this setting (email mirroring integration) is enabled, only then
    # can users reply to email to send message to Zulip. Thus, one must
    # ensure to display warning in the template.
    if settings.EMAIL_GATEWAY_PATTERN:
        context.update({
            'reply_warning': False,
            'reply_to_zulip': True,
        })
    else:
        context.update({
            'reply_warning': True,
            'reply_to_zulip': False,
        })

    from zerver.lib.email_mirror import create_missed_message_address
    reply_to_address = create_missed_message_address(user_profile, missed_messages[0]['message'])
    if reply_to_address == FromAddress.NOREPLY:
        reply_to_name = None
    else:
        reply_to_name = "Zulip"

    senders = list(set(m['message'].sender for m in missed_messages))
    if (missed_messages[0]['message'].recipient.type == Recipient.HUDDLE):
        display_recipient = get_display_recipient(missed_messages[0]['message'].recipient)
        # Make sure that this is a list of strings, not a string.
        assert not isinstance(display_recipient, str)
        other_recipients = [r['full_name'] for r in display_recipient
                            if r['id'] != user_profile.id]
        context.update({'group_pm': True})
        if len(other_recipients) == 2:
            huddle_display_name = "%s" % (" and ".join(other_recipients))
            context.update({'huddle_display_name': huddle_display_name})
        elif len(other_recipients) == 3:
            huddle_display_name = "%s, %s, and %s" % (
                other_recipients[0], other_recipients[1], other_recipients[2])
            context.update({'huddle_display_name': huddle_display_name})
        else:
            huddle_display_name = "%s, and %s others" % (
                ', '.join(other_recipients[:2]), len(other_recipients) - 2)
            context.update({'huddle_display_name': huddle_display_name})
    elif (missed_messages[0]['message'].recipient.type == Recipient.PERSONAL):
        context.update({'private_message': True})
    elif context['mention']:
        # Keep only the senders who actually mentioned the user
        senders = list(set(m['message'].sender for m in missed_messages
                           if m['trigger'] == 'mentioned'))
        # TODO: When we add wildcard mentions that send emails, we
        # should make sure the right logic applies here.
    elif ('stream_email_notify' in unique_triggers):
        context.update({'stream_email_notify': True})
    else:
        raise AssertionError("Invalid messages!")

    # If message content is disabled, then flush all information we pass to email.
    if not user_profile.message_content_in_email_notifications:
        context.update({
            'reply_to_zulip': False,
            'messages': [],
            'sender_str': "",
            'realm_str': user_profile.realm.name,
            'huddle_display_name': "",
        })
    else:
        context.update({
            'messages': build_message_list(user_profile, list(m['message'] for m in missed_messages)),
            'sender_str': ", ".join(sender.full_name for sender in senders),
            'realm_str': user_profile.realm.name,
        })

    from_name = "Zulip missed messages"  # type: str
    from_address = FromAddress.NOREPLY
    if len(senders) == 1 and settings.SEND_MISSED_MESSAGE_EMAILS_AS_USER:
        # If this setting is enabled, you can reply to the Zulip
        # missed message emails directly back to the original sender.
        # However, one must ensure the Zulip server is in the SPF
        # record for the domain, or there will be spam/deliverability
        # problems.
        sender = senders[0]
        from_name, from_address = (sender.full_name, sender.email)
        context.update({
            'reply_warning': False,
            'reply_to_zulip': False,
        })

    email_dict = {
        'template_prefix': 'zerver/emails/missed_message',
        'to_user_ids': [user_profile.id],
        'from_name': from_name,
        'from_address': from_address,
        'reply_to_email': formataddr((reply_to_name, reply_to_address)),
        'context': context}
    queue_json_publish("email_senders", email_dict)

    user_profile.last_reminder = timezone_now()
    user_profile.save(update_fields=['last_reminder'])
Esempio n. 18
0
def do_send_missedmessage_events_reply_in_zulip(user_profile, missed_messages,
                                                message_count):
    # type: (UserProfile, List[Message], int) -> None
    """
    Send a reminder email to a user if she's missed some PMs by being offline.

    The email will have its reply to address set to a limited used email
    address that will send a zulip message to the correct recipient. This
    allows the user to respond to missed PMs, huddles, and @-mentions directly
    from the email.

    `user_profile` is the user to send the reminder to
    `missed_messages` is a list of Message objects to remind about they should
                      all have the same recipient and subject
    """
    from zerver.context_processors import common_context
    # Disabled missedmessage emails internally
    if not user_profile.enable_offline_email_notifications:
        return

    recipients = set(
        (msg.recipient_id, msg.subject) for msg in missed_messages)
    if len(recipients) != 1:
        raise ValueError(
            'All missed_messages must have the same recipient and subject %r' %
            recipients)

    unsubscribe_link = one_click_unsubscribe_link(user_profile,
                                                  "missed_messages")
    context = common_context(user_profile)
    context.update({
        'name':
        user_profile.full_name,
        'messages':
        build_message_list(user_profile, missed_messages),
        'message_count':
        message_count,
        'mention':
        missed_messages[0].is_stream_message(),
        'unsubscribe_link':
        unsubscribe_link,
    })

    # If this setting (email mirroring integration) is enabled, only then
    # can users reply to email to send message to Zulip. Thus, one must
    # ensure to display warning in the template.
    if settings.EMAIL_GATEWAY_PATTERN:
        context.update({
            'reply_warning': False,
            'reply_to_zulip': True,
        })
    else:
        context.update({
            'reply_warning': True,
            'reply_to_zulip': False,
        })

    from zerver.lib.email_mirror import create_missed_message_address
    reply_to_address = create_missed_message_address(user_profile,
                                                     missed_messages[0])
    if reply_to_address == FromAddress.NOREPLY:
        reply_to_name = None
    else:
        reply_to_name = "Zulip"

    senders = list(set(m.sender for m in missed_messages))
    if (missed_messages[0].recipient.type == Recipient.HUDDLE):
        display_recipient = get_display_recipient(missed_messages[0].recipient)
        # Make sure that this is a list of strings, not a string.
        assert not isinstance(display_recipient, Text)
        other_recipients = [
            r['full_name'] for r in display_recipient
            if r['id'] != user_profile.id
        ]
        context.update({'group_pm': True})
        if len(other_recipients) == 2:
            huddle_display_name = u"%s" % (" and ".join(other_recipients))
            context.update({'huddle_display_name': huddle_display_name})
        elif len(other_recipients) == 3:
            huddle_display_name = u"%s, %s, and %s" % (
                other_recipients[0], other_recipients[1], other_recipients[2])
            context.update({'huddle_display_name': huddle_display_name})
        else:
            huddle_display_name = u"%s, and %s others" % (', '.join(
                other_recipients[:2]), len(other_recipients) - 2)
            context.update({'huddle_display_name': huddle_display_name})
    elif (missed_messages[0].recipient.type == Recipient.PERSONAL):
        context.update({'private_message': True})
    else:
        # Keep only the senders who actually mentioned the user
        #
        # TODO: When we add wildcard mentions that send emails, add
        # them to the filter here.
        senders = list(
            set(m.sender for m in missed_messages
                if UserMessage.objects.filter(
                    message=m,
                    user_profile=user_profile,
                    flags=UserMessage.flags.mentioned).exists()))
        context.update({'at_mention': True})

    context.update({
        'sender_str':
        ", ".join(sender.full_name for sender in senders),
        'realm_str':
        user_profile.realm.name,
    })

    from_name = "Zulip missed messages"  # type: Text
    from_address = FromAddress.NOREPLY
    if len(senders) == 1 and settings.SEND_MISSED_MESSAGE_EMAILS_AS_USER:
        # If this setting is enabled, you can reply to the Zulip
        # missed message emails directly back to the original sender.
        # However, one must ensure the Zulip server is in the SPF
        # record for the domain, or there will be spam/deliverability
        # problems.
        sender = senders[0]
        from_name, from_address = (sender.full_name, sender.email)
        context.update({
            'reply_warning': False,
            'reply_to_zulip': False,
        })

    email_dict = {
        'template_prefix': 'zerver/emails/missed_message',
        'to_user_id': user_profile.id,
        'from_name': from_name,
        'from_address': from_address,
        'reply_to_email': formataddr((reply_to_name, reply_to_address)),
        'context': context
    }
    queue_json_publish("missedmessage_email_senders",
                       email_dict,
                       send_email_from_dict,
                       call_consume_in_tests=True)

    user_profile.last_reminder = timezone_now()
    user_profile.save(update_fields=['last_reminder'])
Esempio n. 19
0
def do_send_missedmessage_events_reply_in_zulip(user_profile: UserProfile,
                                                missed_messages: List[Dict[
                                                    str, Any]],
                                                message_count: int) -> None:
    """
    Send a reminder email to a user if she's missed some PMs by being offline.

    The email will have its reply to address set to a limited used email
    address that will send a zulip message to the correct recipient. This
    allows the user to respond to missed PMs, huddles, and @-mentions directly
    from the email.

    `user_profile` is the user to send the reminder to
    `missed_messages` is a list of dictionaries to Message objects and other data
                      for a group of messages that share a recipient (and topic)
    """
    from zerver.context_processors import common_context

    # Disabled missedmessage emails internally
    if not user_profile.enable_offline_email_notifications:
        return

    recipients = {(msg['message'].recipient_id, msg['message'].topic_name())
                  for msg in missed_messages}
    if len(recipients) != 1:
        raise ValueError(
            f'All missed_messages must have the same recipient and topic {recipients!r}',
        )

    # This link is no longer a part of the email, but keeping the code in case
    # we find a clean way to add it back in the future
    unsubscribe_link = one_click_unsubscribe_link(user_profile,
                                                  "missed_messages")
    context = common_context(user_profile)
    context.update({
        'name':
        user_profile.full_name,
        'message_count':
        message_count,
        'unsubscribe_link':
        unsubscribe_link,
        'realm_name_in_notifications':
        user_profile.realm_name_in_notifications,
    })

    triggers = list(message['trigger'] for message in missed_messages)
    unique_triggers = set(triggers)
    context.update({
        'mention':
        'mentioned' in unique_triggers
        or 'wildcard_mentioned' in unique_triggers,
        'stream_email_notify':
        'stream_email_notify' in unique_triggers,
        'mention_count':
        triggers.count('mentioned') + triggers.count("wildcard_mentioned"),
    })

    # If this setting (email mirroring integration) is enabled, only then
    # can users reply to email to send message to Zulip. Thus, one must
    # ensure to display warning in the template.
    if settings.EMAIL_GATEWAY_PATTERN:
        context.update({
            'reply_to_zulip': True,
        })
    else:
        context.update({
            'reply_to_zulip': False,
        })

    from zerver.lib.email_mirror import create_missed_message_address
    reply_to_address = create_missed_message_address(
        user_profile, missed_messages[0]['message'])
    if reply_to_address == FromAddress.NOREPLY:
        reply_to_name = ""
    else:
        reply_to_name = "Zulip"

    narrow_url = get_narrow_url(user_profile, missed_messages[0]['message'])
    context.update({
        'narrow_url': narrow_url,
    })

    senders = list({m['message'].sender for m in missed_messages})
    if (missed_messages[0]['message'].recipient.type == Recipient.HUDDLE):
        display_recipient = get_display_recipient(
            missed_messages[0]['message'].recipient)
        # Make sure that this is a list of strings, not a string.
        assert not isinstance(display_recipient, str)
        other_recipients = [
            r['full_name'] for r in display_recipient
            if r['id'] != user_profile.id
        ]
        context.update({'group_pm': True})
        if len(other_recipients) == 2:
            huddle_display_name = " and ".join(other_recipients)
            context.update({'huddle_display_name': huddle_display_name})
        elif len(other_recipients) == 3:
            huddle_display_name = f"{other_recipients[0]}, {other_recipients[1]}, and {other_recipients[2]}"
            context.update({'huddle_display_name': huddle_display_name})
        else:
            huddle_display_name = "{}, and {} others".format(
                ', '.join(other_recipients[:2]),
                len(other_recipients) - 2)
            context.update({'huddle_display_name': huddle_display_name})
    elif (missed_messages[0]['message'].recipient.type == Recipient.PERSONAL):
        context.update({'private_message': True})
    elif (context['mention'] or context['stream_email_notify']):
        # Keep only the senders who actually mentioned the user
        if context['mention']:
            senders = list({
                m['message'].sender
                for m in missed_messages if m['trigger'] == 'mentioned'
                or m['trigger'] == 'wildcard_mentioned'
            })
        message = missed_messages[0]['message']
        stream = Stream.objects.only('id',
                                     'name').get(id=message.recipient.type_id)
        stream_header = f"{stream.name} > {message.topic_name()}"
        context.update({
            'stream_header': stream_header,
        })
    else:
        raise AssertionError("Invalid messages!")

    # If message content is disabled, then flush all information we pass to email.
    if not message_content_allowed_in_missedmessage_emails(user_profile):
        realm = user_profile.realm
        context.update({
            'reply_to_zulip':
            False,
            'messages': [],
            'sender_str':
            "",
            'realm_str':
            realm.name,
            'huddle_display_name':
            "",
            'show_message_content':
            False,
            'message_content_disabled_by_user':
            not user_profile.message_content_in_email_notifications,
            'message_content_disabled_by_realm':
            not realm.message_content_allowed_in_email_notifications,
        })
    else:
        context.update({
            'messages':
            build_message_list(user_profile,
                               list(m['message'] for m in missed_messages)),
            'sender_str':
            ", ".join(sender.full_name for sender in senders),
            'realm_str':
            user_profile.realm.name,
            'show_message_content':
            True,
        })

    with override_language(user_profile.default_language):
        from_name: str = _("Zulip missed messages")
    from_address = FromAddress.NOREPLY
    if len(senders) == 1 and settings.SEND_MISSED_MESSAGE_EMAILS_AS_USER:
        # If this setting is enabled, you can reply to the Zulip
        # missed message emails directly back to the original sender.
        # However, one must ensure the Zulip server is in the SPF
        # record for the domain, or there will be spam/deliverability
        # problems.
        #
        # Also, this setting is not really compatible with
        # EMAIL_ADDRESS_VISIBILITY_ADMINS.
        sender = senders[0]
        from_name, from_address = (sender.full_name, sender.email)
        context.update({
            'reply_to_zulip': False,
        })

    email_dict = {
        'template_prefix':
        'zerver/emails/missed_message',
        'to_user_ids': [user_profile.id],
        'from_name':
        from_name,
        'from_address':
        from_address,
        'reply_to_email':
        str(Address(display_name=reply_to_name, addr_spec=reply_to_address)),
        'context':
        context
    }
    queue_json_publish("email_senders", email_dict)

    user_profile.last_reminder = timezone_now()
    user_profile.save(update_fields=['last_reminder'])
Esempio n. 20
0
    def test_redact_email_address(self) -> None:
        user_profile = self.example_user('hamlet')
        self.login(user_profile.email)
        self.subscribe(user_profile, "errors")
        stream = get_stream("Denmark", user_profile.realm)

        # Test for a stream address:
        stream_to_address = encode_email_address(stream)
        stream_address_parts = stream_to_address.split('@')
        scrubbed_stream_address = 'X'*len(stream_address_parts[0]) + '@' + stream_address_parts[1]

        error_message = "test message {}"
        error_message = error_message.format(stream_to_address)
        expected_message = "test message {} <Address to stream id: {}>"
        expected_message = expected_message.format(scrubbed_stream_address, stream.id)

        redacted_message = redact_email_address(error_message)
        self.assertEqual(redacted_message, expected_message)

        # Test for an invalid email address:
        invalid_address = "invalid@testserver"
        error_message = "test message {}"
        error_message = error_message.format(invalid_address)
        expected_message = "test message {} <Invalid address>"
        expected_message = expected_message.format('XXXXXXX@testserver')

        redacted_message = redact_email_address(error_message)
        self.assertEqual(redacted_message, expected_message)

        # Test for a missed message address:
        result = self.client_post(
            "/json/messages",
            {
                "type": "private",
                "content": "test_redact_email_message",
                "client": "test suite",
                "to": ujson.dumps([self.example_email('cordelia'), self.example_email('iago')])
            })
        self.assert_json_success(result)

        cordelia_profile = self.example_user('cordelia')
        user_message = most_recent_usermessage(cordelia_profile)
        mm_address = create_missed_message_address(user_profile, user_message.message)

        error_message = "test message {}"
        error_message = error_message.format(mm_address)
        expected_message = "test message {} <Missed message address>"
        expected_message = expected_message.format('X'*34 + '@testserver')

        redacted_message = redact_email_address(error_message)
        self.assertEqual(redacted_message, expected_message)

        # Test if redacting correctly scrubs multiple occurrences of the address:
        error_message = "test message first occurrence: {} second occurrence: {}"
        error_message = error_message.format(stream_to_address, stream_to_address)
        expected_message = "test message first occurrence: {} <Address to stream id: {}>"
        expected_message += " second occurrence: {} <Address to stream id: {}>"
        expected_message = expected_message.format(scrubbed_stream_address, stream.id,
                                                   scrubbed_stream_address, stream.id)

        redacted_message = redact_email_address(error_message)
        self.assertEqual(redacted_message, expected_message)

        # Test with EMAIL_GATEWAY_EXTRA_PATTERN_HACK:
        with self.settings(EMAIL_GATEWAY_EXTRA_PATTERN_HACK='@zulip.org'):
            stream_to_address = stream_to_address.replace('@testserver', '@zulip.org')
            scrubbed_stream_address = scrubbed_stream_address.replace('@testserver', '@zulip.org')
            error_message = "test message {}"
            error_message = error_message.format(stream_to_address)
            expected_message = "test message {} <Address to stream id: {}>"
            expected_message = expected_message.format(scrubbed_stream_address, stream.id)

            redacted_message = redact_email_address(error_message)
            self.assertEqual(redacted_message, expected_message)
Esempio n. 21
0
def do_send_missedmessage_events_reply_in_zulip(user_profile: UserProfile,
                                                missed_messages: List[Message],
                                                message_count: int) -> None:
    """
    Send a reminder email to a user if she's missed some PMs by being offline.

    The email will have its reply to address set to a limited used email
    address that will send a zulip message to the correct recipient. This
    allows the user to respond to missed PMs, huddles, and @-mentions directly
    from the email.

    `user_profile` is the user to send the reminder to
    `missed_messages` is a list of Message objects to remind about they should
                      all have the same recipient and subject
    """
    from zerver.context_processors import common_context
    # Disabled missedmessage emails internally
    if not user_profile.enable_offline_email_notifications:
        return

    recipients = set((msg.recipient_id, msg.subject) for msg in missed_messages)
    if len(recipients) != 1:
        raise ValueError(
            'All missed_messages must have the same recipient and subject %r' %
            recipients
        )

    unsubscribe_link = one_click_unsubscribe_link(user_profile, "missed_messages")
    context = common_context(user_profile)
    context.update({
        'name': user_profile.full_name,
        'message_count': message_count,
        'mention': missed_messages[0].is_stream_message(),
        'unsubscribe_link': unsubscribe_link,
        'realm_name_in_notifications': user_profile.realm_name_in_notifications,
        'show_message_content': user_profile.message_content_in_email_notifications,
    })

    # If this setting (email mirroring integration) is enabled, only then
    # can users reply to email to send message to Zulip. Thus, one must
    # ensure to display warning in the template.
    if settings.EMAIL_GATEWAY_PATTERN:
        context.update({
            'reply_warning': False,
            'reply_to_zulip': True,
        })
    else:
        context.update({
            'reply_warning': True,
            'reply_to_zulip': False,
        })

    from zerver.lib.email_mirror import create_missed_message_address
    reply_to_address = create_missed_message_address(user_profile, missed_messages[0])
    if reply_to_address == FromAddress.NOREPLY:
        reply_to_name = None
    else:
        reply_to_name = "Zulip"

    senders = list(set(m.sender for m in missed_messages))
    if (missed_messages[0].recipient.type == Recipient.HUDDLE):
        display_recipient = get_display_recipient(missed_messages[0].recipient)
        # Make sure that this is a list of strings, not a string.
        assert not isinstance(display_recipient, str)
        other_recipients = [r['full_name'] for r in display_recipient
                            if r['id'] != user_profile.id]
        context.update({'group_pm': True})
        if len(other_recipients) == 2:
            huddle_display_name = "%s" % (" and ".join(other_recipients))
            context.update({'huddle_display_name': huddle_display_name})
        elif len(other_recipients) == 3:
            huddle_display_name = "%s, %s, and %s" % (
                other_recipients[0], other_recipients[1], other_recipients[2])
            context.update({'huddle_display_name': huddle_display_name})
        else:
            huddle_display_name = "%s, and %s others" % (
                ', '.join(other_recipients[:2]), len(other_recipients) - 2)
            context.update({'huddle_display_name': huddle_display_name})
    elif (missed_messages[0].recipient.type == Recipient.PERSONAL):
        context.update({'private_message': True})
    else:
        # Keep only the senders who actually mentioned the user
        #
        # TODO: When we add wildcard mentions that send emails, add
        # them to the filter here.
        senders = list(set(m.sender for m in missed_messages if
                           UserMessage.objects.filter(message=m, user_profile=user_profile,
                                                      flags=UserMessage.flags.mentioned).exists()))
        context.update({'at_mention': True})

    # If message content is disabled, then flush all information we pass to email.
    if not user_profile.message_content_in_email_notifications:
        context.update({
            'reply_to_zulip': False,
            'messages': [],
            'sender_str': "",
            'realm_str': user_profile.realm.name,
            'huddle_display_name': "",
        })
    else:
        context.update({
            'messages': build_message_list(user_profile, missed_messages),
            'sender_str': ", ".join(sender.full_name for sender in senders),
            'realm_str': user_profile.realm.name,
        })

    from_name = "Zulip missed messages"  # type: str
    from_address = FromAddress.NOREPLY
    if len(senders) == 1 and settings.SEND_MISSED_MESSAGE_EMAILS_AS_USER:
        # If this setting is enabled, you can reply to the Zulip
        # missed message emails directly back to the original sender.
        # However, one must ensure the Zulip server is in the SPF
        # record for the domain, or there will be spam/deliverability
        # problems.
        sender = senders[0]
        from_name, from_address = (sender.full_name, sender.email)
        context.update({
            'reply_warning': False,
            'reply_to_zulip': False,
        })

    email_dict = {
        'template_prefix': 'zerver/emails/missed_message',
        'to_user_id': user_profile.id,
        'from_name': from_name,
        'from_address': from_address,
        'reply_to_email': formataddr((reply_to_name, reply_to_address)),
        'context': context}
    queue_json_publish("email_senders", email_dict)

    user_profile.last_reminder = timezone_now()
    user_profile.save(update_fields=['last_reminder'])
Esempio n. 22
0
def do_send_missedmessage_events_reply_in_zulip(user_profile, missed_messages,
                                                message_count):
    # type: (UserProfile, List[Message], int) -> None
    """
    Send a reminder email to a user if she's missed some PMs by being offline.

    The email will have its reply to address set to a limited used email
    address that will send a zulip message to the correct recipient. This
    allows the user to respond to missed PMs, huddles, and @-mentions directly
    from the email.

    `user_profile` is the user to send the reminder to
    `missed_messages` is a list of Message objects to remind about they should
                      all have the same recipient and subject
    """
    from zerver.context_processors import common_context
    # Disabled missedmessage emails internally
    if not user_profile.enable_offline_email_notifications:
        return

    recipients = set(
        (msg.recipient_id, msg.subject) for msg in missed_messages)
    if len(recipients) != 1:
        raise ValueError(
            'All missed_messages must have the same recipient and subject %r' %
            recipients)

    unsubscribe_link = one_click_unsubscribe_link(user_profile,
                                                  "missed_messages")
    template_payload = common_context(user_profile)
    template_payload.update({
        'name':
        user_profile.full_name,
        'messages':
        build_message_list(user_profile, missed_messages),
        'message_count':
        message_count,
        'mention':
        missed_messages[0].recipient.type == Recipient.STREAM,
        'unsubscribe_link':
        unsubscribe_link,
    })

    # If this setting (email mirroring integration) is enabled, only then
    # can users reply to email to send message to Zulip. Thus, one must
    # ensure to display warning in the template.
    if settings.EMAIL_GATEWAY_PATTERN:
        template_payload.update({
            'reply_warning': False,
            'reply_to_zulip': True,
        })
    else:
        template_payload.update({
            'reply_warning': True,
            'reply_to_zulip': False,
        })

    headers = {}
    from zerver.lib.email_mirror import create_missed_message_address
    address = create_missed_message_address(user_profile, missed_messages[0])
    headers['Reply-To'] = address

    senders = set(m.sender.full_name for m in missed_messages)
    sender_str = ", ".join(senders)
    plural_messages = 's' if len(missed_messages) > 1 else ''

    subject = "Missed Zulip%s from %s" % (plural_messages, sender_str)
    from_email = 'Zulip <%s>' % (settings.NOREPLY_EMAIL_ADDRESS, )
    if len(senders) == 1 and settings.SEND_MISSED_MESSAGE_EMAILS_AS_USER:
        # If this setting is enabled, you can reply to the Zulip
        # missed message emails directly back to the original sender.
        # However, one must ensure the Zulip server is in the SPF
        # record for the domain, or there will be spam/deliverability
        # problems.
        headers['Sender'] = from_email
        sender = missed_messages[0].sender
        from_email = '"%s" <%s>' % (sender_str, sender.email)
        template_payload.update({
            'reply_warning': False,
            'reply_to_zulip': False,
        })

    text_content = loader.render_to_string('zerver/missed_message_email.txt',
                                           template_payload)
    html_content = loader.render_to_string('zerver/missed_message_email.html',
                                           template_payload)
    email_content = {
        'subject': subject,
        'text_content': text_content,
        'html_content': html_content,
        'from_email': from_email,
        'to': [user_profile.email],
        'headers': headers
    }
    queue_json_publish("missedmessage_email_senders", email_content,
                       send_missedmessage_email)

    user_profile.last_reminder = timezone.now()
    user_profile.save(update_fields=['last_reminder'])
Esempio n. 23
0
def do_send_missedmessage_events_reply_in_zulip(user_profile, missed_messages, message_count):
    # type: (UserProfile, List[Message], int) -> None
    """
    Send a reminder email to a user if she's missed some PMs by being offline.

    The email will have its reply to address set to a limited used email
    address that will send a zulip message to the correct recipient. This
    allows the user to respond to missed PMs, huddles, and @-mentions directly
    from the email.

    `user_profile` is the user to send the reminder to
    `missed_messages` is a list of Message objects to remind about they should
                      all have the same recipient and subject
    """
    # Disabled missedmessage emails internally
    if not user_profile.enable_offline_email_notifications:
        return

    recipients = set((msg.recipient_id, msg.subject) for msg in missed_messages)
    if len(recipients) != 1:
        raise ValueError(
            'All missed_messages must have the same recipient and subject %r' %
            recipients
        )

    unsubscribe_link = one_click_unsubscribe_link(user_profile, "missed_messages")
    template_payload = {
        'name': user_profile.full_name,
        'messages': build_message_list(user_profile, missed_messages),
        'message_count': message_count,
        'reply_warning': False,
        'external_host': settings.EXTERNAL_HOST,
        'external_uri_scheme': settings.EXTERNAL_URI_SCHEME,
        'server_uri': settings.SERVER_URI,
        'realm_uri': user_profile.realm.uri,
        'mention': missed_messages[0].recipient.type == Recipient.STREAM,
        'reply_to_zulip': True,
        'unsubscribe_link': unsubscribe_link,
    }

    headers = {}
    from zerver.lib.email_mirror import create_missed_message_address
    address = create_missed_message_address(user_profile, missed_messages[0])
    headers['Reply-To'] = address

    senders = set(m.sender.full_name for m in missed_messages)
    sender_str = ", ".join(senders)
    plural_messages = 's' if len(missed_messages) > 1 else ''

    subject = "Missed Zulip%s from %s" % (plural_messages, sender_str)
    from_email = 'Zulip <%s>' % (settings.NOREPLY_EMAIL_ADDRESS,)
    if len(senders) == 1 and settings.SEND_MISSED_MESSAGE_EMAILS_AS_USER:
        # If this setting is enabled, you can reply to the Zulip
        # missed message emails directly back to the original sender.
        # However, one must ensure the Zulip server is in the SPF
        # record for the domain, or there will be spam/deliverability
        # problems.
        headers['Sender'] = from_email
        sender = missed_messages[0].sender
        from_email = '"%s" <%s>' % (sender_str, sender.email)

    text_content = loader.render_to_string('zerver/missed_message_email.txt', template_payload)
    html_content = loader.render_to_string('zerver/missed_message_email_html.txt', template_payload)

    msg = EmailMultiAlternatives(subject, text_content, from_email, [user_profile.email],
                                 headers = headers)
    msg.attach_alternative(html_content, "text/html")
    msg.send()

    user_profile.last_reminder = datetime.datetime.now()
    user_profile.save(update_fields=['last_reminder'])
Esempio n. 24
0
def do_send_missedmessage_events_reply_in_zulip(user_profile: UserProfile,
                                                missed_messages: List[Dict[
                                                    str, Any]],
                                                message_count: int) -> None:
    """
    Send a reminder email to a user if she's missed some PMs by being offline.

    The email will have its reply to address set to a limited used email
    address that will send a Zulip message to the correct recipient. This
    allows the user to respond to missed PMs, huddles, and @-mentions directly
    from the email.

    `user_profile` is the user to send the reminder to
    `missed_messages` is a list of dictionaries to Message objects and other data
                      for a group of messages that share a recipient (and topic)
    """
    from zerver.context_processors import common_context

    # Disabled missedmessage emails internally
    if not user_profile.enable_offline_email_notifications:
        return

    recipients = {(msg["message"].recipient_id, msg["message"].topic_name())
                  for msg in missed_messages}
    if len(recipients) != 1:
        raise ValueError(
            f"All missed_messages must have the same recipient and topic {recipients!r}",
        )

    # This link is no longer a part of the email, but keeping the code in case
    # we find a clean way to add it back in the future
    unsubscribe_link = one_click_unsubscribe_link(user_profile,
                                                  "missed_messages")
    context = common_context(user_profile)
    context.update(
        name=user_profile.full_name,
        message_count=message_count,
        unsubscribe_link=unsubscribe_link,
        realm_name_in_notifications=user_profile.realm_name_in_notifications,
    )

    triggers = [message["trigger"] for message in missed_messages]
    unique_triggers = set(triggers)
    context.update(
        mention="mentioned" in unique_triggers
        or "wildcard_mentioned" in unique_triggers,
        stream_email_notify="stream_email_notify" in unique_triggers,
        mention_count=triggers.count("mentioned") +
        triggers.count("wildcard_mentioned"),
    )

    # If this setting (email mirroring integration) is enabled, only then
    # can users reply to email to send message to Zulip. Thus, one must
    # ensure to display warning in the template.
    if settings.EMAIL_GATEWAY_PATTERN:
        context.update(reply_to_zulip=True, )
    else:
        context.update(reply_to_zulip=False, )

    from zerver.lib.email_mirror import create_missed_message_address

    reply_to_address = create_missed_message_address(
        user_profile, missed_messages[0]["message"])
    if reply_to_address == FromAddress.NOREPLY:
        reply_to_name = ""
    else:
        reply_to_name = "Zulip"

    narrow_url = get_narrow_url(user_profile, missed_messages[0]["message"])
    context.update(narrow_url=narrow_url, )

    senders = list({m["message"].sender for m in missed_messages})
    if missed_messages[0]["message"].recipient.type == Recipient.HUDDLE:
        display_recipient = get_display_recipient(
            missed_messages[0]["message"].recipient)
        # Make sure that this is a list of strings, not a string.
        assert not isinstance(display_recipient, str)
        other_recipients = [
            r["full_name"] for r in display_recipient
            if r["id"] != user_profile.id
        ]
        context.update(group_pm=True)
        if len(other_recipients) == 2:
            huddle_display_name = " and ".join(other_recipients)
            context.update(huddle_display_name=huddle_display_name)
        elif len(other_recipients) == 3:
            huddle_display_name = (
                f"{other_recipients[0]}, {other_recipients[1]}, and {other_recipients[2]}"
            )
            context.update(huddle_display_name=huddle_display_name)
        else:
            huddle_display_name = "{}, and {} others".format(
                ", ".join(other_recipients[:2]),
                len(other_recipients) - 2)
            context.update(huddle_display_name=huddle_display_name)
    elif missed_messages[0]["message"].recipient.type == Recipient.PERSONAL:
        context.update(private_message=True)
    elif context["mention"] or context["stream_email_notify"]:
        # Keep only the senders who actually mentioned the user
        if context["mention"]:
            senders = list({
                m["message"].sender
                for m in missed_messages if m["trigger"] == "mentioned"
                or m["trigger"] == "wildcard_mentioned"
            })
        message = missed_messages[0]["message"]
        stream = Stream.objects.only("id",
                                     "name").get(id=message.recipient.type_id)
        stream_header = f"{stream.name} > {message.topic_name()}"
        context.update(stream_header=stream_header, )
    else:
        raise AssertionError("Invalid messages!")

    # If message content is disabled, then flush all information we pass to email.
    if not message_content_allowed_in_missedmessage_emails(user_profile):
        realm = user_profile.realm
        context.update(
            reply_to_zulip=False,
            messages=[],
            sender_str="",
            realm_str=realm.name,
            huddle_display_name="",
            show_message_content=False,
            message_content_disabled_by_user=not user_profile.
            message_content_in_email_notifications,
            message_content_disabled_by_realm=not realm.
            message_content_allowed_in_email_notifications,
        )
    else:
        context.update(
            messages=build_message_list(
                user=user_profile,
                messages=[m["message"] for m in missed_messages],
                stream_map={},
            ),
            sender_str=", ".join(sender.full_name for sender in senders),
            realm_str=user_profile.realm.name,
            show_message_content=True,
        )

    with override_language(user_profile.default_language):
        from_name: str = _("Zulip missed messages")
    from_address = FromAddress.NOREPLY
    if len(senders) == 1 and settings.SEND_MISSED_MESSAGE_EMAILS_AS_USER:
        # If this setting is enabled, you can reply to the Zulip
        # missed message emails directly back to the original sender.
        # However, one must ensure the Zulip server is in the SPF
        # record for the domain, or there will be spam/deliverability
        # problems.
        #
        # Also, this setting is not really compatible with
        # EMAIL_ADDRESS_VISIBILITY_ADMINS.
        sender = senders[0]
        from_name, from_address = (sender.full_name, sender.email)
        context.update(reply_to_zulip=False, )

    email_dict = {
        "template_prefix":
        "zerver/emails/missed_message",
        "to_user_ids": [user_profile.id],
        "from_name":
        from_name,
        "from_address":
        from_address,
        "reply_to_email":
        str(Address(display_name=reply_to_name, addr_spec=reply_to_address)),
        "context":
        context,
    }
    queue_json_publish("email_senders", email_dict)

    user_profile.last_reminder = timezone_now()
    user_profile.save(update_fields=["last_reminder"])
Esempio n. 25
0
def do_send_missedmessage_events_reply_in_zulip(user_profile, missed_messages, message_count):
    # type: (UserProfile, List[Message], int) -> None
    """
    Send a reminder email to a user if she's missed some PMs by being offline.

    The email will have its reply to address set to a limited used email
    address that will send a zulip message to the correct recipient. This
    allows the user to respond to missed PMs, huddles, and @-mentions directly
    from the email.

    `user_profile` is the user to send the reminder to
    `missed_messages` is a list of Message objects to remind about they should
                      all have the same recipient and subject
    """
    from zerver.context_processors import common_context
    # Disabled missedmessage emails internally
    if not user_profile.enable_offline_email_notifications:
        return

    recipients = set((msg.recipient_id, msg.subject) for msg in missed_messages)
    if len(recipients) != 1:
        raise ValueError(
            'All missed_messages must have the same recipient and subject %r' %
            recipients
        )

    unsubscribe_link = one_click_unsubscribe_link(user_profile, "missed_messages")
    template_payload = common_context(user_profile)
    template_payload.update({
        'name': user_profile.full_name,
        'messages': build_message_list(user_profile, missed_messages),
        'message_count': message_count,
        'mention': missed_messages[0].recipient.type == Recipient.STREAM,
        'unsubscribe_link': unsubscribe_link,
    })

    # If this setting (email mirroring integration) is enabled, only then
    # can users reply to email to send message to Zulip. Thus, one must
    # ensure to display warning in the template.
    if settings.EMAIL_GATEWAY_PATTERN:
        template_payload.update({
            'reply_warning': False,
            'reply_to_zulip': True,
        })
    else:
        template_payload.update({
            'reply_warning': True,
            'reply_to_zulip': False,
        })

    headers = {}
    from zerver.lib.email_mirror import create_missed_message_address
    address = create_missed_message_address(user_profile, missed_messages[0])
    headers['Reply-To'] = address

    senders = set(m.sender.full_name for m in missed_messages)
    sender_str = ", ".join(senders)
    plural_messages = 's' if len(missed_messages) > 1 else ''

    subject = "Missed Zulip%s from %s" % (plural_messages, sender_str)
    from_email = 'Zulip <%s>' % (settings.NOREPLY_EMAIL_ADDRESS,)
    if len(senders) == 1 and settings.SEND_MISSED_MESSAGE_EMAILS_AS_USER:
        # If this setting is enabled, you can reply to the Zulip
        # missed message emails directly back to the original sender.
        # However, one must ensure the Zulip server is in the SPF
        # record for the domain, or there will be spam/deliverability
        # problems.
        headers['Sender'] = from_email
        sender = missed_messages[0].sender
        from_email = '"%s" <%s>' % (sender_str, sender.email)
        template_payload.update({
            'reply_warning': False,
            'reply_to_zulip': False,
        })

    text_content = loader.render_to_string('zerver/missed_message_email.txt', template_payload)
    html_content = loader.render_to_string('zerver/missed_message_email.html', template_payload)
    email_content = {
        'subject': subject,
        'text_content': text_content,
        'html_content': html_content,
        'from_email': from_email,
        'to': [user_profile.email],
        'headers': headers
    }
    queue_json_publish("missedmessage_email_senders", email_content, send_missedmessage_email)

    user_profile.last_reminder = timezone.now()
    user_profile.save(update_fields=['last_reminder'])