def test_signatures_added(self):
        """
        Tests that signatures are correctly added to the news which previously
        didn't have any, despite having signed content.
        """
        # Set up news based on a signed message.
        signed_news = []
        unsigned_news = []
        with make_temp_directory('-pts-keyring') as TEST_KEYRING_DIRECTORY:
            self.TEST_KEYRING_DIRECTORY = TEST_KEYRING_DIRECTORY
            with self.settings(
                    PTS_KEYRING_DIRECTORY=self.TEST_KEYRING_DIRECTORY):
                self.import_key_from_test_file('key1.pub')
                # The content of the test news item is found in a file
                file_path = self.get_test_file_path(
                    'signed-message-quoted-printable')
                with open(file_path, 'rb') as f:
                    content = f.read()
                expected_name = 'PTS Tests'
                expected_email = '*****@*****.**'
                sender_name = 'Some User'
                # The first signed news has the same content as what is found
                # the signed test file.
                signed_news.append(EmailNews.objects.create_email_news(
                    message=message_from_bytes(content),
                    package=self.package))
                # For the second one, add some text after the signature: this
                # should still mean that the correct signature can be extracted!
                signed_news.append(EmailNews.objects.create_email_news(
                    message=message_from_bytes(content + b'\nMore content'),
                    package=self.package))
                # Set up some unsigned news.
                unsigned_news.append(EmailNews.objects.create_email_news(
                    message=message_from_bytes(b'Subject: Hi\n\nPayload.'),
                    package=self.package))
                # A non-email based news item
                unsigned_news.append(News.objects.create(
                    package=self.package,
                    content="Some content.."
                ))
                # Make sure that the signed news do not have associated
                # signature information
                for signed in signed_news:
                    signed.signed_by.clear()

                # Run the command
                call_command("pts_update_news_signatures")

                # The signed news items have associated signature information
                for signed in signed_news:
                    self.assertEqual(1, signed.signed_by.count())
                    signer = signed.signed_by.all()[0]
                    # The signature is actually correct too?
                    self.assertEqual(expected_name, signer.name)
                    self.assertEqual(expected_email, signer.email)
                # The unsigned messages still do not have any signature info
                for unsigned in unsigned_news:
                    self.assertEqual(0, unsigned.signed_by.count())
Esempio n. 2
0
    def test_integrate_with_django(self):
        """
        Tests that the message obtained by the message_from_bytes function can
        be sent out using the Django email API.

        In the same time, this test makes sure that Django keeps using
        the as_string method as expected.
        """
        from django.core.mail import get_connection
        backend = get_connection()
        # Replace the backend's SMTP connection with a mock.
        mock_connection = self.get_mock_connection()
        backend.connection = mock_connection
        # Send the message over the backend
        message = message_from_bytes(self.message_bytes)
        custom_message = CustomEmailMessage(
            msg=message,
            from_email='*****@*****.**',
            to=['*****@*****.**'])

        backend.send_messages([custom_message])
        backend.close()

        # The backend sent the mail over SMTP & it is not corrupted
        mock_connection.sendmail.assert_called_with(
            '*****@*****.**',
            ['*****@*****.**'],
            message.as_string())
Esempio n. 3
0
    def test_as_string_returns_bytes(self):
        """
        Tests that the as_string message returns bytes.
        """
        message = message_from_bytes(self.message_bytes)

        self.assertEqual(self.message_bytes, message.as_string())
        self.assertTrue(isinstance(message.as_string(), six.binary_type))
Esempio n. 4
0
    def test_get_payload_decode_idempotent(self):
        """
        Tests that the get_payload method returns bytes which can be decoded
        using the message's encoding and that they are identical to the
        ones given to the function in the first place.
        """
        message = message_from_bytes(self.message_bytes)

        self.assertEqual(self.body,
                         message.get_payload(decode=True).decode('utf-8'))
Esempio n. 5
0
def process(message, sent_to_address=None):
    """
    Handles the dispatching of received messages.

    :param message: The received message
    :type message: ``bytes``

    :param sent_to_address: The address to which the message was sent.
        Necessary in order to determine which package it was sent to.
    """
    assert isinstance(message, six.binary_type), "Message must be given as bytes"
    msg = message_from_bytes(message)

    if sent_to_address is None:
        # No MTA was recognized, the last resort is to try and use the message
        # To header.
        sent_to_address = extract_email_address_from_header(msg["To"])

    if sent_to_address.startswith("bounces+"):
        return handle_bounces(sent_to_address)

    local_part = sent_to_address.split("@")[0]

    # Extract package name
    package_name = get_package_name(local_part)
    # Check loop
    package_email = "{package}@{pts_fqdn}".format(package=package_name, pts_fqdn=PTS_FQDN)
    if package_email in msg.get_all("X-Loop", ()):
        # Bad X-Loop, discard the message
        logger.info("Bad X-Loop, message discarded")
        return

    # Extract keyword
    keyword = get_keyword(local_part, msg)
    # Default keywords require special approvement
    if keyword == "default" and not approved_default(msg):
        logger.info("Discarding default keyword message")
        return

    # Now send the message to subscribers
    add_new_headers(msg, package_name, keyword)
    send_to_subscribers(msg, package_name, keyword)
    send_to_teams(msg, package_name, keyword)
Esempio n. 6
0
def process(message):
    """
    Process an incoming message which is potentially a news item.

    The function first tries to call the vendor-provided function
    :func:`create_news_from_email_message <pts.vendor.skeleton.rules.create_news_from_email_message>`.

    If this function does not exist a news item is created only if there is a
    ``X-PTS-Package`` header set giving the name of an existing source or
    pseudo package.

    If the ``X-PTS-Url`` is also set then the content of the message will not
    be the email content, rather the URL given in this header.

    :param message: The received message
    :type message: :class:`bytes`
    """
    assert isinstance(message, bytes), 'Message must be given as bytes'

    msg = message_from_bytes(message)

    # Try asking the vendor function first.
    created, implemented = vendor.call('create_news_from_email_message', msg)
    if implemented and created:
        return

    # If the message has an X-PTS-Package header, it is automatically made into
    # a news item.
    if 'X-PTS-Package' in msg:
        package_name = msg['X-PTS-Package']
        package = get_or_none(PackageName, name=package_name)
        if not package:
            return
        if 'X-PTS-Url' not in msg:
            create_news(msg, package)
        else:
            pts_url = msg['X-PTS-Url']
            News.objects.create(
                title=pts_url,
                content="<a href={url}>{url}</a>".format(url=escape(pts_url)),
                package=package,
                content_type='text/html')
Esempio n. 7
0
def process(message):
    """
    The function which actually processes a received command email message.

    :param message: The received command email message.
    :type message: ``bytes``
    """
    assert isinstance(message, six.binary_type), 'Message must be given as bytes'
    msg = message_from_bytes(message)
    # msg = message_from_string(message)
    if 'X-Loop' in msg and PTS_CONTROL_EMAIL in msg.get_all('X-Loop'):
        return
    # Get the first plain-text part of the message
    plain_text_part = next(typed_subpart_iterator(msg, 'text', 'plain'), None)
    if not plain_text_part:
        # There is no plain text in the email
        send_plain_text_warning(msg)
        return

    # Decode the plain text into a unicode string
    try:
        text = get_decoded_message_payload(plain_text_part)
    except UnicodeDecodeError:
        send_plain_text_warning(msg)
        return

    lines = extract_command_from_subject(msg) + text.splitlines()
    # Process the commands
    factory = CommandFactory({
        'email': extract_email_address_from_header(msg['From']),
    })
    confirmation_set = ConfirmationSet()
    processor = CommandProcessor(factory)
    processor.confirmation_set = confirmation_set
    processor.process(lines)

    confirmation_set.ask_confirmation_all()
    # Send a response only if there were some commands processed
    if processor.is_success():
        send_response(
            msg, processor.get_output(), set(confirmation_set.get_emails()))