Support optional Source, parse from header
The Email ``from`` header is either formatted as ``name <address>`` or ``address``. This commit will use `parseaddr` to extract a ``(name, address)`` tuple, which we will use the ``address`` to check if it's verified. Also support the case where ``Source`` is omitted (which AWS requires the ``from`` header to be set).
This commit is contained in:
		
							parent
							
								
									cb364eedc6
								
							
						
					
					
						commit
						d21c387eb6
					
				| @ -1,6 +1,7 @@ | |||||||
| from __future__ import unicode_literals | from __future__ import unicode_literals | ||||||
| 
 | 
 | ||||||
| import email | import email | ||||||
|  | from email.utils import parseaddr | ||||||
| 
 | 
 | ||||||
| from moto.core import BaseBackend, BaseModel | from moto.core import BaseBackend, BaseModel | ||||||
| from .exceptions import MessageRejectedError | from .exceptions import MessageRejectedError | ||||||
| @ -84,13 +85,27 @@ class SESBackend(BaseBackend): | |||||||
|         return message |         return message | ||||||
| 
 | 
 | ||||||
|     def send_raw_email(self, source, destinations, raw_data): |     def send_raw_email(self, source, destinations, raw_data): | ||||||
|         if source not in self.addresses: |         if source is not None: | ||||||
|  |             _, source_email_address = parseaddr(source) | ||||||
|  |             if source_email_address not in self.addresses: | ||||||
|                 raise MessageRejectedError( |                 raise MessageRejectedError( | ||||||
|                 "Did not have authority to send from email %s" % source |                     "Did not have authority to send from email %s" % source_email_address | ||||||
|                 ) |                 ) | ||||||
| 
 | 
 | ||||||
|         recipient_count = len(destinations) |         recipient_count = len(destinations) | ||||||
|         message = email.message_from_string(raw_data) |         message = email.message_from_string(raw_data) | ||||||
|  |         if source is None: | ||||||
|  |             if message['from'] is None: | ||||||
|  |                 raise MessageRejectedError( | ||||||
|  |                     "Source not specified" | ||||||
|  |                 ) | ||||||
|  | 
 | ||||||
|  |             _, source_email_address = parseaddr(message['from']) | ||||||
|  |             if source_email_address not in self.addresses: | ||||||
|  |                 raise MessageRejectedError( | ||||||
|  |                     "Did not have authority to send from email %s" % source_email_address | ||||||
|  |                 ) | ||||||
|  | 
 | ||||||
|         for header in 'TO', 'CC', 'BCC': |         for header in 'TO', 'CC', 'BCC': | ||||||
|             recipient_count += sum( |             recipient_count += sum( | ||||||
|                 d.strip() and 1 or 0 |                 d.strip() and 1 or 0 | ||||||
|  | |||||||
| @ -75,7 +75,10 @@ class EmailResponse(BaseResponse): | |||||||
|         return template.render(message=message) |         return template.render(message=message) | ||||||
| 
 | 
 | ||||||
|     def send_raw_email(self): |     def send_raw_email(self): | ||||||
|         source = self.querystring.get('Source')[0] |         source = self.querystring.get('Source') | ||||||
|  |         if source is not None: | ||||||
|  |             source, = source | ||||||
|  | 
 | ||||||
|         raw_data = self.querystring.get('RawMessage.Data')[0] |         raw_data = self.querystring.get('RawMessage.Data')[0] | ||||||
|         raw_data = base64.b64decode(raw_data) |         raw_data = base64.b64decode(raw_data) | ||||||
|         if six.PY3: |         if six.PY3: | ||||||
|  | |||||||
| @ -136,3 +136,59 @@ def test_send_raw_email(): | |||||||
|     send_quota = conn.get_send_quota() |     send_quota = conn.get_send_quota() | ||||||
|     sent_count = int(send_quota['SentLast24Hours']) |     sent_count = int(send_quota['SentLast24Hours']) | ||||||
|     sent_count.should.equal(2) |     sent_count.should.equal(2) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_ses | ||||||
|  | def test_send_raw_email_without_source(): | ||||||
|  |     conn = boto3.client('ses', region_name='us-east-1') | ||||||
|  | 
 | ||||||
|  |     message = MIMEMultipart() | ||||||
|  |     message['Subject'] = 'Test' | ||||||
|  |     message['From'] = 'test@example.com' | ||||||
|  |     message['To'] = 'to@example.com, foo@example.com' | ||||||
|  | 
 | ||||||
|  |     # Message body | ||||||
|  |     part = MIMEText('test file attached') | ||||||
|  |     message.attach(part) | ||||||
|  | 
 | ||||||
|  |     # Attachment | ||||||
|  |     part = MIMEText('contents of test file here') | ||||||
|  |     part.add_header('Content-Disposition', 'attachment; filename=test.txt') | ||||||
|  |     message.attach(part) | ||||||
|  | 
 | ||||||
|  |     kwargs = dict( | ||||||
|  |         RawMessage={'Data': message.as_string()}, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     conn.send_raw_email.when.called_with(**kwargs).should.throw(ClientError) | ||||||
|  | 
 | ||||||
|  |     conn.verify_email_identity(EmailAddress="test@example.com") | ||||||
|  |     conn.send_raw_email(**kwargs) | ||||||
|  | 
 | ||||||
|  |     send_quota = conn.get_send_quota() | ||||||
|  |     sent_count = int(send_quota['SentLast24Hours']) | ||||||
|  |     sent_count.should.equal(2) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @mock_ses | ||||||
|  | def test_send_raw_email_without_source_or_from(): | ||||||
|  |     conn = boto3.client('ses', region_name='us-east-1') | ||||||
|  | 
 | ||||||
|  |     message = MIMEMultipart() | ||||||
|  |     message['Subject'] = 'Test' | ||||||
|  |     message['To'] = 'to@example.com, foo@example.com' | ||||||
|  | 
 | ||||||
|  |     # Message body | ||||||
|  |     part = MIMEText('test file attached') | ||||||
|  |     message.attach(part) | ||||||
|  |     # Attachment | ||||||
|  |     part = MIMEText('contents of test file here') | ||||||
|  |     part.add_header('Content-Disposition', 'attachment; filename=test.txt') | ||||||
|  |     message.attach(part) | ||||||
|  | 
 | ||||||
|  |     kwargs = dict( | ||||||
|  |         RawMessage={'Data': message.as_string()}, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     conn.send_raw_email.when.called_with(**kwargs).should.throw(ClientError) | ||||||
|  | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user