Merge pull request #2582 from alphagov/punycode

punycode encode emails before sending
This commit is contained in:
Leo Hemsted
2019-08-12 15:16:44 +01:00
committed by GitHub
2 changed files with 40 additions and 14 deletions

View File

@@ -83,7 +83,7 @@ class AwsSesClient(EmailClient):
response = self._client.send_email( response = self._client.send_email(
Source=source, Source=source,
Destination={ Destination={
'ToAddresses': to_addresses, 'ToAddresses': [punycode_encode_email(addr) for addr in to_addresses],
'CcAddresses': [], 'CcAddresses': [],
'BccAddresses': [] 'BccAddresses': []
}, },
@@ -93,7 +93,7 @@ class AwsSesClient(EmailClient):
}, },
'Body': body 'Body': body
}, },
ReplyToAddresses=reply_to_addresses ReplyToAddresses=[punycode_encode_email(addr) for addr in reply_to_addresses]
) )
except botocore.exceptions.ClientError as e: except botocore.exceptions.ClientError as e:
self.statsd_client.incr("clients.ses.error") self.statsd_client.incr("clients.ses.error")
@@ -116,3 +116,9 @@ class AwsSesClient(EmailClient):
self.statsd_client.timing("clients.ses.request-time", elapsed_time) self.statsd_client.timing("clients.ses.request-time", elapsed_time)
self.statsd_client.incr("clients.ses.success") self.statsd_client.incr("clients.ses.success")
return response['MessageId'] return response['MessageId']
def punycode_encode_email(email_address):
# only the hostname should ever be punycode encoded.
local, hostname = email_address.split('@')
return '{}@{}'.format(local, hostname.encode('idna').decode('utf-8'))

View File

@@ -45,21 +45,21 @@ def test_should_be_none_if_unrecognised_status_code():
assert '99' in str(e.value) assert '99' in str(e.value)
@pytest.mark.parametrize( @pytest.mark.parametrize('reply_to_address, expected_value', [
'reply_to_address, expected_value', (None, []),
[(None, []), ('foo@bar.com', ['foo@bar.com'])], ('foo@bar.com', ['foo@bar.com']),
ids=['empty', 'single_email'] ('føøøø@bååååår.com', ['føøøø@xn--br-yiaaaaa.com'])
) ], ids=['empty', 'single_email', 'punycode'])
def test_send_email_handles_reply_to_address(notify_api, mocker, reply_to_address, expected_value): def test_send_email_handles_reply_to_address(notify_api, mocker, reply_to_address, expected_value):
boto_mock = mocker.patch.object(aws_ses_client, '_client', create=True) boto_mock = mocker.patch.object(aws_ses_client, '_client', create=True)
mocker.patch.object(aws_ses_client, 'statsd_client', create=True) mocker.patch.object(aws_ses_client, 'statsd_client', create=True)
with notify_api.app_context(): with notify_api.app_context():
aws_ses_client.send_email( aws_ses_client.send_email(
Mock(), source=Mock(),
Mock(), to_addresses='to@address.com',
Mock(), subject=Mock(),
Mock(), body=Mock(),
reply_to_address=reply_to_address reply_to_address=reply_to_address
) )
@@ -71,6 +71,26 @@ def test_send_email_handles_reply_to_address(notify_api, mocker, reply_to_addres
) )
def test_send_email_handles_punycode_to_address(notify_api, mocker):
boto_mock = mocker.patch.object(aws_ses_client, '_client', create=True)
mocker.patch.object(aws_ses_client, 'statsd_client', create=True)
with notify_api.app_context():
aws_ses_client.send_email(
Mock(),
to_addresses='føøøø@bååååår.com',
subject=Mock(),
body=Mock()
)
boto_mock.send_email.assert_called_once_with(
Source=ANY,
Destination={'ToAddresses': ['føøøø@xn--br-yiaaaaa.com'], 'CcAddresses': [], 'BccAddresses': []},
Message=ANY,
ReplyToAddresses=ANY
)
def test_send_email_raises_bad_email_as_InvalidEmailError(mocker): def test_send_email_raises_bad_email_as_InvalidEmailError(mocker):
boto_mock = mocker.patch.object(aws_ses_client, '_client', create=True) boto_mock = mocker.patch.object(aws_ses_client, '_client', create=True)
mocker.patch.object(aws_ses_client, 'statsd_client', create=True) mocker.patch.object(aws_ses_client, 'statsd_client', create=True)
@@ -87,13 +107,13 @@ def test_send_email_raises_bad_email_as_InvalidEmailError(mocker):
with pytest.raises(InvalidEmailError) as excinfo: with pytest.raises(InvalidEmailError) as excinfo:
aws_ses_client.send_email( aws_ses_client.send_email(
source=Mock(), source=Mock(),
to_addresses='clearly@invalid@email.com', to_addresses='definitely@invalid_email.com',
subject=Mock(), subject=Mock(),
body=Mock() body=Mock()
) )
assert 'some error message from amazon' in str(excinfo.value) assert 'some error message from amazon' in str(excinfo.value)
assert 'clearly@invalid@email.com' in str(excinfo.value) assert 'definitely@invalid_email.com' in str(excinfo.value)
def test_send_email_raises_other_errs_as_AwsSesClientException(mocker): def test_send_email_raises_other_errs_as_AwsSesClientException(mocker):
@@ -112,7 +132,7 @@ def test_send_email_raises_other_errs_as_AwsSesClientException(mocker):
with pytest.raises(AwsSesClientException) as excinfo: with pytest.raises(AwsSesClientException) as excinfo:
aws_ses_client.send_email( aws_ses_client.send_email(
source=Mock(), source=Mock(),
to_addresses=Mock(), to_addresses='foo@bar.com',
subject=Mock(), subject=Mock(),
body=Mock() body=Mock()
) )