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"""