mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	email_mirror: Add send_to_email_mirror management command.
Closes #11195. We add a management command to allow us to send emails to the email mirror directly. The command doesn't require any configuring of email sending or receiving for the email mirror, it passes the emails directly using the process_message function.
This commit is contained in:
		
				
					committed by
					
						
						Tim Abbott
					
				
			
			
				
	
			
			
			
						parent
						
							726767ece5
						
					
				
				
					commit
					3e5f89f2fe
				
			
							
								
								
									
										117
									
								
								zerver/management/commands/send_to_email_mirror.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								zerver/management/commands/send_to_email_mirror.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,117 @@
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import email
 | 
			
		||||
import ujson
 | 
			
		||||
 | 
			
		||||
from email.message import Message
 | 
			
		||||
from email.mime.text import MIMEText
 | 
			
		||||
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.core.management.base import CommandParser
 | 
			
		||||
from django.core.mail import send_mail
 | 
			
		||||
 | 
			
		||||
from zerver.lib.actions import encode_email_address
 | 
			
		||||
from zerver.lib.email_mirror import logger, process_message
 | 
			
		||||
from zerver.lib.send_email import FromAddress
 | 
			
		||||
from zerver.lib.management import ZulipBaseCommand
 | 
			
		||||
 | 
			
		||||
from zerver.models import Realm, get_stream, get_realm
 | 
			
		||||
 | 
			
		||||
from typing import Any, Dict
 | 
			
		||||
 | 
			
		||||
# This command loads an email from a specified file and sends it
 | 
			
		||||
# to the email mirror. Simple emails can be passed in a JSON file,
 | 
			
		||||
# Look at zerver/tests/fixtures/email/1.json for an example of how
 | 
			
		||||
# it should look. You can also pass a file which has the raw email,
 | 
			
		||||
# for example by writing an email.message.Message type object
 | 
			
		||||
# to a file using as_string() or as_bytes() methods, or copy-pasting
 | 
			
		||||
# the content of "Show original" on an email in Gmail.
 | 
			
		||||
# See zerver/tests/fixtures/email/1.txt for a very simple example,
 | 
			
		||||
# but anything that the message_from_binary_file function
 | 
			
		||||
# from the email library can parse should work.
 | 
			
		||||
# Value of the TO: header doesn't matter, as it is overriden
 | 
			
		||||
# by the command in order for the email to be sent to the correct stream.
 | 
			
		||||
 | 
			
		||||
class Command(ZulipBaseCommand):
 | 
			
		||||
    help = """
 | 
			
		||||
Send specified email from a fixture file to the email mirror
 | 
			
		||||
Example:
 | 
			
		||||
./manage.py send_to_email_mirror --fixture=zerver/tests/fixtures/emails/filename
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
    def add_arguments(self, parser: CommandParser) -> None:
 | 
			
		||||
        parser.add_argument('-f', '--fixture',
 | 
			
		||||
                            dest='fixture',
 | 
			
		||||
                            type=str,
 | 
			
		||||
                            help='The path to the email message you\'d like to send '
 | 
			
		||||
                                 'to the email mirror.\n'
 | 
			
		||||
                                 'Accepted formats: json or raw email file. '
 | 
			
		||||
                                 'See zerver/tests/fixtures/email/ for examples')
 | 
			
		||||
        parser.add_argument('-s', '--stream',
 | 
			
		||||
                            dest='stream',
 | 
			
		||||
                            type=str,
 | 
			
		||||
                            help='The name of the stream to which you\'d like to send '
 | 
			
		||||
                            'the message. Default: Denmark')
 | 
			
		||||
 | 
			
		||||
        self.add_realm_args(parser, help="Specify which realm to connect to; default is zulip")
 | 
			
		||||
 | 
			
		||||
    def handle(self, **options: str) -> None:
 | 
			
		||||
        if options['fixture'] is None:
 | 
			
		||||
            self.print_help('./manage.py', 'send_to_email_mirror')
 | 
			
		||||
            exit(1)
 | 
			
		||||
 | 
			
		||||
        if options['stream'] is None:
 | 
			
		||||
            stream = "Denmark"
 | 
			
		||||
        else:
 | 
			
		||||
            stream = options['stream']
 | 
			
		||||
 | 
			
		||||
        realm = self.get_realm(options)
 | 
			
		||||
        if realm is None:
 | 
			
		||||
            realm = get_realm("zulip")
 | 
			
		||||
 | 
			
		||||
        full_fixture_path = os.path.join(settings.DEPLOY_ROOT, options['fixture'])
 | 
			
		||||
 | 
			
		||||
        # parse the input email into Message type and prepare to process_message() it
 | 
			
		||||
        message = self._parse_email_fixture(full_fixture_path)
 | 
			
		||||
        self._prepare_message(message, realm, stream)
 | 
			
		||||
 | 
			
		||||
        process_message(message)
 | 
			
		||||
 | 
			
		||||
    def _does_fixture_path_exist(self, fixture_path: str) -> bool:
 | 
			
		||||
        return os.path.exists(fixture_path)
 | 
			
		||||
 | 
			
		||||
    def _parse_email_json_fixture(self, fixture_path: str) -> Message:
 | 
			
		||||
        with open(fixture_path) as fp:
 | 
			
		||||
            json_content = ujson.load(fp)[0]
 | 
			
		||||
 | 
			
		||||
        message = MIMEText(json_content['body'])
 | 
			
		||||
        message['From'] = json_content['from']
 | 
			
		||||
        message['Subject'] = json_content['subject']
 | 
			
		||||
        return message
 | 
			
		||||
 | 
			
		||||
    def _parse_email_fixture(self, fixture_path: str) -> Message:
 | 
			
		||||
        if not self._does_fixture_path_exist(fixture_path):
 | 
			
		||||
            print('Fixture {} does not exist'.format(fixture_path))
 | 
			
		||||
            exit(1)
 | 
			
		||||
 | 
			
		||||
        if fixture_path.endswith('.json'):
 | 
			
		||||
            message = self._parse_email_json_fixture(fixture_path)
 | 
			
		||||
        else:
 | 
			
		||||
            with open(fixture_path, "rb") as fp:
 | 
			
		||||
                message = email.message_from_binary_file(fp)
 | 
			
		||||
 | 
			
		||||
        return message
 | 
			
		||||
 | 
			
		||||
    def _prepare_message(self, message: Message, realm: Realm, stream_name: str) -> None:
 | 
			
		||||
        stream = get_stream(stream_name, realm)
 | 
			
		||||
 | 
			
		||||
        recipient_headers = ["X-Gm-Original-To", "Delivered-To",
 | 
			
		||||
                             "Resent-To", "Resent-CC", "To", "CC"]
 | 
			
		||||
        for header in recipient_headers:
 | 
			
		||||
            if header in message:
 | 
			
		||||
                del message[header]
 | 
			
		||||
                message[header] = encode_email_address(stream)
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
        message['To'] = encode_email_address(stream)
 | 
			
		||||
							
								
								
									
										8
									
								
								zerver/tests/fixtures/email/1.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								zerver/tests/fixtures/email/1.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
[
 | 
			
		||||
    {
 | 
			
		||||
        "from": "hamlet@zulip.com",
 | 
			
		||||
        "to": "foo@zulip.com",
 | 
			
		||||
        "subject": "1.json",
 | 
			
		||||
        "body": "Email fixture 1.json body"
 | 
			
		||||
    }
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										9
									
								
								zerver/tests/fixtures/email/1.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								zerver/tests/fixtures/email/1.txt
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
Content-Type: text/plain; charset="us-ascii"
 | 
			
		||||
MIME-Version: 1.0
 | 
			
		||||
Content-Transfer-Encoding: 7bit
 | 
			
		||||
Subject: Normal subject
 | 
			
		||||
From: hamlet@zulip.com
 | 
			
		||||
To: foo@zulipdev.com
 | 
			
		||||
Reply-to: othello@zulip.com
 | 
			
		||||
 | 
			
		||||
Email fixture 1.txt body
 | 
			
		||||
@@ -3,7 +3,9 @@
 | 
			
		||||
import glob
 | 
			
		||||
import os
 | 
			
		||||
import re
 | 
			
		||||
import email
 | 
			
		||||
from datetime import timedelta
 | 
			
		||||
from email.mime.text import MIMEText
 | 
			
		||||
from email.utils import parseaddr
 | 
			
		||||
from mock import MagicMock, patch, call
 | 
			
		||||
from typing import List, Dict, Any, Optional
 | 
			
		||||
@@ -16,8 +18,9 @@ from zerver.lib.management import ZulipBaseCommand, CommandError, check_config
 | 
			
		||||
from zerver.lib.test_classes import ZulipTestCase
 | 
			
		||||
from zerver.lib.test_helpers import stdout_suppressed
 | 
			
		||||
from zerver.lib.test_runner import slow
 | 
			
		||||
from zerver.models import get_user_profile_by_email
 | 
			
		||||
from zerver.models import Recipient, get_user_profile_by_email, get_stream
 | 
			
		||||
 | 
			
		||||
from zerver.lib.test_helpers import most_recent_message
 | 
			
		||||
from zerver.models import get_realm, UserProfile, Realm
 | 
			
		||||
from confirmation.models import RealmCreationKey, generate_realm_creation_url
 | 
			
		||||
 | 
			
		||||
@@ -295,3 +298,46 @@ class TestRealmReactivationEmail(ZulipTestCase):
 | 
			
		||||
        realm = get_realm('zulip')
 | 
			
		||||
        with self.assertRaisesRegex(CommandError, "The realm %s is already active." % (realm.name,)):
 | 
			
		||||
            call_command(self.COMMAND_NAME, "--realm=zulip")
 | 
			
		||||
 | 
			
		||||
class TestSendToEmailMirror(ZulipTestCase):
 | 
			
		||||
    COMMAND_NAME = "send_to_email_mirror"
 | 
			
		||||
 | 
			
		||||
    def test_sending_a_fixture(self) -> None:
 | 
			
		||||
        fixture_path = "zerver/tests/fixtures/email/1.txt"
 | 
			
		||||
        user_profile = self.example_user('hamlet')
 | 
			
		||||
        self.login(user_profile.email)
 | 
			
		||||
        self.subscribe(user_profile, "Denmark")
 | 
			
		||||
 | 
			
		||||
        call_command(self.COMMAND_NAME, "--fixture={}".format(fixture_path))
 | 
			
		||||
        message = most_recent_message(user_profile)
 | 
			
		||||
 | 
			
		||||
        # last message should be equal to the body of the email in 1.txt
 | 
			
		||||
        self.assertEqual(message.content, "Email fixture 1.txt body")
 | 
			
		||||
 | 
			
		||||
    def test_sending_a_json_fixture(self) -> None:
 | 
			
		||||
        fixture_path = "zerver/tests/fixtures/email/1.json"
 | 
			
		||||
        user_profile = self.example_user('hamlet')
 | 
			
		||||
        self.login(user_profile.email)
 | 
			
		||||
        self.subscribe(user_profile, "Denmark")
 | 
			
		||||
 | 
			
		||||
        call_command(self.COMMAND_NAME, "--fixture={}".format(fixture_path))
 | 
			
		||||
        message = most_recent_message(user_profile)
 | 
			
		||||
 | 
			
		||||
        # last message should be equal to the body of the email in 1.json
 | 
			
		||||
        self.assertEqual(message.content, "Email fixture 1.json body")
 | 
			
		||||
 | 
			
		||||
    def test_stream_option(self) -> None:
 | 
			
		||||
        fixture_path = "zerver/tests/fixtures/email/1.txt"
 | 
			
		||||
        user_profile = self.example_user('hamlet')
 | 
			
		||||
        self.login(user_profile.email)
 | 
			
		||||
        self.subscribe(user_profile, "Denmark2")
 | 
			
		||||
 | 
			
		||||
        call_command(self.COMMAND_NAME, "--fixture={}".format(fixture_path), "--stream=Denmark2")
 | 
			
		||||
        message = most_recent_message(user_profile)
 | 
			
		||||
 | 
			
		||||
        # last message should be equal to the body of the email in 1.txt
 | 
			
		||||
        self.assertEqual(message.content, "Email fixture 1.txt body")
 | 
			
		||||
 | 
			
		||||
        stream_id = get_stream("Denmark2", message.sender.realm).id
 | 
			
		||||
        self.assertEqual(message.recipient.type, Recipient.STREAM)
 | 
			
		||||
        self.assertEqual(message.recipient.type_id, stream_id)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user