From b1a4f9809a52295527ff188b7e8f73720f487337 Mon Sep 17 00:00:00 2001 From: Mike Edmunds Date: Sun, 11 Apr 2021 13:02:56 -0700 Subject: [PATCH] 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 --- CHANGELOG.rst | 6 ++++++ anymail/webhooks/amazon_ses.py | 15 +++++++++++++-- tests/test_amazon_ses_webhooks.py | 9 ++++++++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f771ee6..e679b1a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -33,6 +33,11 @@ vNext 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 data. (Thanks to `@kareemcoding`_ and `@Tobeyforce`_ for reporting them.) @@ -1230,6 +1235,7 @@ Features .. _@kika115: https://github.com/kika115 .. _@Lekensteyn: https://github.com/Lekensteyn .. _@lewistaylor: https://github.com/lewistaylor +.. _@mark-mishyn: https://github.com/mark-mishyn .. _@mbk-ok: https://github.com/mbk-ok .. _@mwheels: https://github.com/mwheels .. _@nuschk: https://github.com/nuschk diff --git a/anymail/webhooks/amazon_ses.py b/anymail/webhooks/amazon_ses.py index d284493..6bd4300 100644 --- a/anymail/webhooks/amazon_ses.py +++ b/anymail/webhooks/amazon_ses.py @@ -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). # 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( - TopicArn=sns_message["TopicArn"], Token=sns_message["Token"], AuthenticateOnUnsubscribe='true') + TopicArn=topic_arn, Token=token, AuthenticateOnUnsubscribe='true') class AmazonSESTrackingWebhookView(AmazonSESBaseWebhookView): diff --git a/tests/test_amazon_ses_webhooks.py b/tests/test_amazon_ses_webhooks.py index 74d029a..d0eb5f9 100644 --- a/tests/test_amazon_ses_webhooks.py +++ b/tests/test_amazon_ses_webhooks.py @@ -441,7 +441,7 @@ class AmazonSESSubscriptionManagementTests(WebhookTestCase, AmazonSESWebhookTest response = self.post_from_sns('/anymail/amazon_ses/tracking/', self.SNS_SUBSCRIPTION_CONFIRMATION) self.assertEqual(response.status_code, 200) # 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( TopicArn="arn:aws:sns:us-west-2:123456789012:SES_Notifications", Token="EXAMPLE_TOKEN", AuthenticateOnUnsubscribe="true") @@ -469,6 +469,13 @@ class AmazonSESSubscriptionManagementTests(WebhookTestCase, AmazonSESWebhookTest self.assertEqual(self.tracking_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 def test_sns_subscription_confirmation_auth_disabled(self): """Anymail *won't* auto-confirm SNS subscriptions if WEBHOOK_SECRET isn't in use"""