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())
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())
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))
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'))
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)
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')
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()))