Amazon SES: confirm webhook subscriptions in SNS topic's own region (#236)

Allow inbound and tracking webhooks using SNS topics from any AWS region.
The topic subscription must be confirmed in the topic's own region (not
the boto3 default), determined by examing the topic's ARN.

Fixes #235
This commit is contained in:
Mike Edmunds
2021-04-11 13:02:56 -07:00
committed by GitHub
parent a08052e7f8
commit b1a4f9809a
3 changed files with 27 additions and 3 deletions

View File

@@ -33,6 +33,11 @@ vNext
Fixes Fixes
~~~~~ ~~~~~
* **Amazon SES:** Support receiving and tracking mail in non-default (or multiple)
AWS regions. Anymail now always confirms an SNS subscription in the region where
the SNS topic exists, which may be different from the boto3 default. (Thanks to
`@mark-mishyn`_ for reporting this.)
* **Postmark:** Fix two different errors when sending with a template but no merge * **Postmark:** Fix two different errors when sending with a template but no merge
data. (Thanks to `@kareemcoding`_ and `@Tobeyforce`_ for reporting them.) data. (Thanks to `@kareemcoding`_ and `@Tobeyforce`_ for reporting them.)
@@ -1230,6 +1235,7 @@ Features
.. _@kika115: https://github.com/kika115 .. _@kika115: https://github.com/kika115
.. _@Lekensteyn: https://github.com/Lekensteyn .. _@Lekensteyn: https://github.com/Lekensteyn
.. _@lewistaylor: https://github.com/lewistaylor .. _@lewistaylor: https://github.com/lewistaylor
.. _@mark-mishyn: https://github.com/mark-mishyn
.. _@mbk-ok: https://github.com/mbk-ok .. _@mbk-ok: https://github.com/mbk-ok
.. _@mwheels: https://github.com/mwheels .. _@mwheels: https://github.com/mwheels
.. _@nuschk: https://github.com/nuschk .. _@nuschk: https://github.com/nuschk

View File

@@ -130,9 +130,20 @@ class AmazonSESBaseWebhookView(AnymailBaseWebhookView):
# WEBHOOK_SECRET *is* set, so the request's basic auth has been verified by now (in run_validators). # WEBHOOK_SECRET *is* set, so the request's basic auth has been verified by now (in run_validators).
# We're good to confirm... # We're good to confirm...
sns_client = boto3.session.Session(**self.session_params).client('sns', **self.client_params) topic_arn = sns_message["TopicArn"]
token = sns_message["Token"]
# Must confirm in TopicArn's own region (which may be different from the default)
try:
(_arn_tag, _partition, _service, region, _account, _resource) = topic_arn.split(":", maxsplit=6)
except (TypeError, ValueError):
raise ValueError("Invalid ARN format '{topic_arn!s}'".format(topic_arn=topic_arn))
client_params = self.client_params.copy()
client_params["region_name"] = region
sns_client = boto3.session.Session(**self.session_params).client('sns', **client_params)
sns_client.confirm_subscription( sns_client.confirm_subscription(
TopicArn=sns_message["TopicArn"], Token=sns_message["Token"], AuthenticateOnUnsubscribe='true') TopicArn=topic_arn, Token=token, AuthenticateOnUnsubscribe='true')
class AmazonSESTrackingWebhookView(AmazonSESBaseWebhookView): class AmazonSESTrackingWebhookView(AmazonSESBaseWebhookView):

View File

@@ -441,7 +441,7 @@ class AmazonSESSubscriptionManagementTests(WebhookTestCase, AmazonSESWebhookTest
response = self.post_from_sns('/anymail/amazon_ses/tracking/', self.SNS_SUBSCRIPTION_CONFIRMATION) response = self.post_from_sns('/anymail/amazon_ses/tracking/', self.SNS_SUBSCRIPTION_CONFIRMATION)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
# auto-confirmed: # auto-confirmed:
self.mock_client.assert_called_once_with('sns', config=ANY) self.mock_client.assert_called_once_with('sns', config=ANY, region_name="us-west-2")
self.mock_client_instance.confirm_subscription.assert_called_once_with( self.mock_client_instance.confirm_subscription.assert_called_once_with(
TopicArn="arn:aws:sns:us-west-2:123456789012:SES_Notifications", TopicArn="arn:aws:sns:us-west-2:123456789012:SES_Notifications",
Token="EXAMPLE_TOKEN", AuthenticateOnUnsubscribe="true") Token="EXAMPLE_TOKEN", AuthenticateOnUnsubscribe="true")
@@ -469,6 +469,13 @@ class AmazonSESSubscriptionManagementTests(WebhookTestCase, AmazonSESWebhookTest
self.assertEqual(self.tracking_handler.call_count, 0) self.assertEqual(self.tracking_handler.call_count, 0)
self.assertEqual(self.inbound_handler.call_count, 0) self.assertEqual(self.inbound_handler.call_count, 0)
@override_settings(ANYMAIL_AMAZON_SES_CLIENT_PARAMS={"region_name": "us-east-1"})
def test_sns_subscription_confirmation_different_region(self):
"""Anymail confirms the subscription in the SNS Topic's own region, rather than any default region"""
# (The SNS_SUBSCRIPTION_CONFIRMATION above has a TopicArn in region us-west-2)
self.post_from_sns('/anymail/amazon_ses/tracking/', self.SNS_SUBSCRIPTION_CONFIRMATION)
self.mock_client.assert_called_once_with('sns', config=ANY, region_name="us-west-2")
@override_settings(ANYMAIL={}) # clear WEBHOOK_SECRET setting from base WebhookTestCase @override_settings(ANYMAIL={}) # clear WEBHOOK_SECRET setting from base WebhookTestCase
def test_sns_subscription_confirmation_auth_disabled(self): def test_sns_subscription_confirmation_auth_disabled(self):
"""Anymail *won't* auto-confirm SNS subscriptions if WEBHOOK_SECRET isn't in use""" """Anymail *won't* auto-confirm SNS subscriptions if WEBHOOK_SECRET isn't in use"""