Reformat code with automated tools

Apply standardized code style
This commit is contained in:
medmunds
2023-02-06 12:27:43 -08:00
committed by Mike Edmunds
parent 40891fcb4a
commit b4e22c63b3
94 changed files with 12936 additions and 7443 deletions

View File

@@ -16,50 +16,65 @@ class AmazonSESWebhookTestsMixin(SimpleTestCase):
def post_from_sns(self, path, raw_sns_message, **kwargs):
return self.client.post(
path,
content_type='text/plain; charset=UTF-8', # SNS posts JSON as text/plain
content_type="text/plain; charset=UTF-8", # SNS posts JSON as text/plain
data=json.dumps(raw_sns_message),
HTTP_X_AMZ_SNS_MESSAGE_ID=raw_sns_message["MessageId"],
HTTP_X_AMZ_SNS_MESSAGE_TYPE=raw_sns_message["Type"],
# Anymail doesn't use other x-amz-sns-* headers
**kwargs)
**kwargs,
)
@tag('amazon_ses')
class AmazonSESWebhookSecurityTests(AmazonSESWebhookTestsMixin, WebhookBasicAuthTestCase):
@tag("amazon_ses")
class AmazonSESWebhookSecurityTests(
AmazonSESWebhookTestsMixin, WebhookBasicAuthTestCase
):
def call_webhook(self):
return self.post_from_sns('/anymail/amazon_ses/tracking/',
{"Type": "Notification", "MessageId": "123", "Message": "{}"})
return self.post_from_sns(
"/anymail/amazon_ses/tracking/",
{"Type": "Notification", "MessageId": "123", "Message": "{}"},
)
# Most actual tests are in WebhookBasicAuthTestCase
def test_verifies_missing_auth(self):
# Must handle missing auth header slightly differently from Anymail default 400 SuspiciousOperation:
# SNS will only send basic auth after missing auth responds 401 WWW-Authenticate: Basic realm="..."
# Must handle missing auth header slightly differently from Anymail default 400
# SuspiciousOperation: SNS will only send basic auth after missing auth responds
# 401 WWW-Authenticate: Basic realm="..."
self.clear_basic_auth()
response = self.call_webhook()
self.assertEqual(response.status_code, 401)
self.assertEqual(response["WWW-Authenticate"], 'Basic realm="Anymail WEBHOOK_SECRET"')
self.assertEqual(
response["WWW-Authenticate"], 'Basic realm="Anymail WEBHOOK_SECRET"'
)
@tag('amazon_ses')
@tag("amazon_ses")
class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
def test_bounce_event(self):
# This test includes a complete Amazon SES example event. (Later tests omit some payload for brevity.)
# This test includes a complete Amazon SES example event.
# (Later tests omit some payload for brevity.)
# https://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-examples.html#notification-examples-bounce
raw_ses_event = {
"notificationType": "Bounce",
"bounce": {
"bounceType": "Permanent",
"reportingMTA": "dns; email.example.com",
"bouncedRecipients": [{
"emailAddress": "jane@example.com",
"status": "5.1.1",
"action": "failed",
"diagnosticCode": "smtp; 550 5.1.1 <jane@example.com>... User unknown",
}],
"bouncedRecipients": [
{
"emailAddress": "jane@example.com",
"status": "5.1.1",
"action": "failed",
"diagnosticCode": "smtp; 550 5.1.1 <jane@example.com>..."
" User unknown",
}
],
"bounceSubType": "General",
"timestamp": "2016-01-27T14:59:44.101Z", # when bounce sent (by receiving ISP)
"feedbackId": "00000138111222aa-44455566-cccc-cccc-cccc-ddddaaaa068a-000000", # unique id for bounce
# when bounce sent (by receiving ISP):
"timestamp": "2016-01-27T14:59:44.101Z",
# unique id for bounce:
"feedbackId": "00000138111222aa-44455566-cccc"
"-cccc-cccc-ddddaaaa068a-000000",
"remoteMtaIp": "127.0.2.0",
},
"mail": {
@@ -68,13 +83,22 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
"sourceArn": "arn:aws:ses:us-west-2:888888888888:identity/example.com",
"sourceIp": "127.0.3.0",
"sendingAccountId": "123456789012",
"messageId": "00000138111222aa-33322211-cccc-cccc-cccc-ddddaaaa0680-000000",
"destination": ["jane@example.com", "mary@example.com", "richard@example.com"],
"messageId": "00000138111222aa-33322211-cccc"
"-cccc-cccc-ddddaaaa0680-000000",
"destination": [
"jane@example.com",
"mary@example.com",
"richard@example.com",
],
"headersTruncated": False,
"headers": [
{"name": "From", "value": '"John Doe" <john@example.com>'},
{"name": "To", "value": '"Jane Doe" <jane@example.com>, "Mary Doe" <mary@example.com>,'
' "Richard Doe" <richard@example.com>'},
{
"name": "To",
"value": '"Jane Doe" <jane@example.com>,'
' "Mary Doe" <mary@example.com>,'
' "Richard Doe" <richard@example.com>',
},
{"name": "Message-ID", "value": "custom-message-ID"},
{"name": "Subject", "value": "Hello"},
{"name": "Content-Type", "value": 'text/plain; charset="UTF-8"'},
@@ -87,8 +111,10 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
"commonHeaders": {
"from": ["John Doe <john@example.com>"],
"date": "Wed, 27 Jan 2016 14:05:45 +0000",
"to": ["Jane Doe <jane@example.com>, Mary Doe <mary@example.com>,"
" Richard Doe <richard@example.com>"],
"to": [
"Jane Doe <jane@example.com>, Mary Doe <mary@example.com>,"
" Richard Doe <richard@example.com>"
],
"messageId": "custom-message-ID",
"subject": "Hello",
},
@@ -96,35 +122,48 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
}
raw_sns_message = {
"Type": "Notification",
"MessageId": "19ba9823-d7f2-53c1-860e-cb10e0d13dfc", # unique id for SNS event
# unique id for SNS event:
"MessageId": "19ba9823-d7f2-53c1-860e-cb10e0d13dfc",
"TopicArn": "arn:aws:sns:us-east-1:1234567890:SES_Events",
"Subject": "Amazon SES Email Event Notification",
"Message": json.dumps(raw_ses_event) + "\n",
"Timestamp": "2018-03-26T17:58:59.675Z",
"SignatureVersion": "1",
"Signature": "EXAMPLE-SIGNATURE==",
"SigningCertURL": "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-12345abcde.pem",
"UnsubscribeURL": "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn...",
"SigningCertURL": "https://sns.us-east-1.amazonaws.com"
"/SimpleNotificationService-12345abcde.pem",
"UnsubscribeURL": "https://sns.us-east-1.amazonaws.com"
"/?Action=Unsubscribe&SubscriptionArn=arn...",
}
response = self.post_from_sns('/anymail/amazon_ses/tracking/', raw_sns_message)
response = self.post_from_sns("/anymail/amazon_ses/tracking/", raw_sns_message)
self.assertEqual(response.status_code, 200)
kwargs = self.assert_handler_called_once_with(self.tracking_handler, sender=AmazonSESTrackingWebhookView,
event=ANY, esp_name='Amazon SES')
event = kwargs['event']
kwargs = self.assert_handler_called_once_with(
self.tracking_handler,
sender=AmazonSESTrackingWebhookView,
event=ANY,
esp_name="Amazon SES",
)
event = kwargs["event"]
self.assertIsInstance(event, AnymailTrackingEvent)
self.assertEqual(event.event_type, "bounced")
self.assertEqual(event.esp_event, raw_ses_event)
# timestamp from SNS:
self.assertEqual(
event.timestamp,
datetime(2018, 3, 26, 17, 58, 59, microsecond=675000, tzinfo=timezone.utc)
) # SNS
self.assertEqual(event.message_id, "00000138111222aa-33322211-cccc-cccc-cccc-ddddaaaa0680-000000")
datetime(2018, 3, 26, 17, 58, 59, microsecond=675000, tzinfo=timezone.utc),
)
self.assertEqual(
event.message_id,
"00000138111222aa-33322211-cccc-cccc-cccc-ddddaaaa0680-000000",
)
self.assertEqual(event.event_id, "19ba9823-d7f2-53c1-860e-cb10e0d13dfc")
self.assertEqual(event.recipient, "jane@example.com")
self.assertEqual(event.reject_reason, "bounced")
self.assertEqual(event.description, "Permanent: General")
self.assertEqual(event.mta_response, "smtp; 550 5.1.1 <jane@example.com>... User unknown")
self.assertEqual(
event.mta_response, "smtp; 550 5.1.1 <jane@example.com>... User unknown"
)
self.assertEqual(event.tags, ["tag 1", "tag 2"])
self.assertEqual(event.metadata, {"meta1": "string", "meta2": 2})
@@ -139,13 +178,18 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
"bounceSubType": "General",
"bouncedRecipients": [
{"emailAddress": "jane@example.com"},
{"emailAddress": "richard@example.com"}
{"emailAddress": "richard@example.com"},
],
},
"mail": {
"messageId": "00000137860315fd-34208509-5b74-41f3-95c5-22c1edc3c924-000000",
"destination": ["jane@example.com", "mary@example.com", "richard@example.com"],
}
"messageId": "00000137860315fd-34208509-5b74"
"-41f3-95c5-22c1edc3c924-000000",
"destination": [
"jane@example.com",
"mary@example.com",
"richard@example.com",
],
},
}
raw_sns_message = {
"Type": "Notification",
@@ -153,7 +197,7 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
"Message": json.dumps(raw_ses_event) + "\n",
"Timestamp": "2018-03-26T17:58:59.675Z",
}
response = self.post_from_sns('/anymail/amazon_ses/tracking/', raw_sns_message)
response = self.post_from_sns("/anymail/amazon_ses/tracking/", raw_sns_message)
self.assertEqual(response.status_code, 200)
# tracking handler should be called twice -- once for each bounced recipient
@@ -161,14 +205,14 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
self.assertEqual(self.tracking_handler.call_count, 2)
_, kwargs = self.tracking_handler.call_args_list[0]
event = kwargs['event']
event = kwargs["event"]
self.assertEqual(event.event_type, "bounced")
self.assertEqual(event.recipient, "jane@example.com")
self.assertEqual(event.description, "Permanent: General")
self.assertIsNone(event.mta_response)
_, kwargs = self.tracking_handler.call_args_list[1]
event = kwargs['event']
event = kwargs["event"]
self.assertEqual(event.esp_event, raw_ses_event)
self.assertEqual(event.recipient, "richard@example.com")
@@ -181,9 +225,14 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
"complaintFeedbackType": "abuse",
},
"mail": {
"messageId": "000001378603177f-7a5433e7-8edb-42ae-af10-f0181f34d6ee-000000",
"destination": ["jane@example.com", "mary@example.com", "richard@example.com"],
}
"messageId": "000001378603177f-7a5433e7-8edb"
"-42ae-af10-f0181f34d6ee-000000",
"destination": [
"jane@example.com",
"mary@example.com",
"richard@example.com",
],
},
}
raw_sns_message = {
"Type": "Notification",
@@ -191,11 +240,15 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
"Message": json.dumps(raw_ses_event) + "\n",
"Timestamp": "2018-03-26T17:58:59.675Z",
}
response = self.post_from_sns('/anymail/amazon_ses/tracking/', raw_sns_message)
response = self.post_from_sns("/anymail/amazon_ses/tracking/", raw_sns_message)
self.assertEqual(response.status_code, 200)
kwargs = self.assert_handler_called_once_with(self.tracking_handler, sender=AmazonSESTrackingWebhookView,
event=ANY, esp_name='Amazon SES')
event = kwargs['event']
kwargs = self.assert_handler_called_once_with(
self.tracking_handler,
sender=AmazonSESTrackingWebhookView,
event=ANY,
esp_name="Amazon SES",
)
event = kwargs["event"]
self.assertEqual(event.event_type, "complained")
self.assertEqual(event.recipient, "richard@example.com")
self.assertEqual(event.reject_reason, "spam")
@@ -207,8 +260,13 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
"notificationType": "Delivery",
"mail": {
"timestamp": "2016-01-27T14:59:38.237Z",
"messageId": "0000014644fe5ef6-9a483358-9170-4cb4-a269-f5dcdf415321-000000",
"destination": ["jane@example.com", "mary@example.com", "richard@example.com"],
"messageId": "0000014644fe5ef6-9a483358-9170"
"-4cb4-a269-f5dcdf415321-000000",
"destination": [
"jane@example.com",
"mary@example.com",
"richard@example.com",
],
},
"delivery": {
"timestamp": "2016-01-27T14:59:38.237Z",
@@ -216,8 +274,8 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
"processingTimeMillis": 546,
"reportingMTA": "a8-70.smtp-out.amazonses.com",
"smtpResponse": "250 ok: Message 64111812 accepted",
"remoteMtaIp": "127.0.2.0"
}
"remoteMtaIp": "127.0.2.0",
},
}
raw_sns_message = {
"Type": "Notification",
@@ -225,11 +283,15 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
"Message": json.dumps(raw_ses_event) + "\n",
"Timestamp": "2018-03-26T17:58:59.675Z",
}
response = self.post_from_sns('/anymail/amazon_ses/tracking/', raw_sns_message)
response = self.post_from_sns("/anymail/amazon_ses/tracking/", raw_sns_message)
self.assertEqual(response.status_code, 200)
kwargs = self.assert_handler_called_once_with(self.tracking_handler, sender=AmazonSESTrackingWebhookView,
event=ANY, esp_name='Amazon SES')
event = kwargs['event']
kwargs = self.assert_handler_called_once_with(
self.tracking_handler,
sender=AmazonSESTrackingWebhookView,
event=ANY,
esp_name="Amazon SES",
)
event = kwargs["event"]
self.assertEqual(event.event_type, "delivered")
self.assertEqual(event.recipient, "jane@example.com")
self.assertEqual(event.mta_response, "250 ok: Message 64111812 accepted")
@@ -247,10 +309,10 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
"ses:from-domain": ["example.com"],
"ses:caller-identity": ["ses_user"],
"myCustomTag1": ["myCustomTagValue1"],
"myCustomTag2": ["myCustomTagValue2"]
}
"myCustomTag2": ["myCustomTagValue2"],
},
},
"send": {}
"send": {},
}
raw_sns_message = {
"Type": "Notification",
@@ -258,22 +320,30 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
"Message": json.dumps(raw_ses_event) + "\n",
"Timestamp": "2018-03-26T17:58:59.675Z",
}
response = self.post_from_sns('/anymail/amazon_ses/tracking/', raw_sns_message)
response = self.post_from_sns("/anymail/amazon_ses/tracking/", raw_sns_message)
self.assertEqual(response.status_code, 200)
kwargs = self.assert_handler_called_once_with(self.tracking_handler, sender=AmazonSESTrackingWebhookView,
event=ANY, esp_name='Amazon SES')
event = kwargs['event']
kwargs = self.assert_handler_called_once_with(
self.tracking_handler,
sender=AmazonSESTrackingWebhookView,
event=ANY,
esp_name="Amazon SES",
)
event = kwargs["event"]
self.assertIsInstance(event, AnymailTrackingEvent)
self.assertEqual(event.event_type, "sent")
self.assertEqual(event.esp_event, raw_ses_event)
# timestamp from SNS:
self.assertEqual(
event.timestamp,
datetime(2018, 3, 26, 17, 58, 59, microsecond=675000, tzinfo=timezone.utc)
) # SNS
self.assertEqual(event.message_id, "7c191be45-e9aedb9a-02f9-4d12-a87d-dd0099a07f8a-000000")
datetime(2018, 3, 26, 17, 58, 59, microsecond=675000, tzinfo=timezone.utc),
)
self.assertEqual(
event.message_id, "7c191be45-e9aedb9a-02f9-4d12-a87d-dd0099a07f8a-000000"
)
self.assertEqual(event.event_id, "19ba9823-d7f2-53c1-860e-cb10e0d13dfc")
self.assertEqual(event.recipient, "recipient@example.com")
self.assertEqual(event.tags, []) # Anymail doesn't load Amazon SES "Message Tags"
# Anymail doesn't load Amazon SES "Message Tags":
self.assertEqual(event.tags, [])
self.assertEqual(event.metadata, {})
def test_reject_event(self):
@@ -284,9 +354,7 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
"messageId": "7c191be45-e9aedb9a-02f9-4d12-a87d-dd0099a07f8a-000000",
"destination": ["recipient@example.com"],
},
"reject": {
"reason": "Bad content"
}
"reject": {"reason": "Bad content"},
}
raw_sns_message = {
"Type": "Notification",
@@ -294,11 +362,15 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
"Message": json.dumps(raw_ses_event) + "\n",
"Timestamp": "2018-03-26T17:58:59.675Z",
}
response = self.post_from_sns('/anymail/amazon_ses/tracking/', raw_sns_message)
response = self.post_from_sns("/anymail/amazon_ses/tracking/", raw_sns_message)
self.assertEqual(response.status_code, 200)
kwargs = self.assert_handler_called_once_with(self.tracking_handler, sender=AmazonSESTrackingWebhookView,
event=ANY, esp_name='Amazon SES')
event = kwargs['event']
kwargs = self.assert_handler_called_once_with(
self.tracking_handler,
sender=AmazonSESTrackingWebhookView,
event=ANY,
esp_name="Amazon SES",
)
event = kwargs["event"]
self.assertEqual(event.event_type, "rejected")
self.assertEqual(event.reject_reason, "blocked")
self.assertEqual(event.description, "Bad content")
@@ -314,8 +386,9 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
"open": {
"ipAddress": "192.0.2.1",
"timestamp": "2017-08-09T22:00:19.652Z",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_3 like Mac OS X)..."
}
"userAgent": "Mozilla/5.0"
" (iPhone; CPU iPhone OS 10_3_3 like Mac OS X)...",
},
}
raw_sns_message = {
"Type": "Notification",
@@ -323,14 +396,21 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
"Message": json.dumps(raw_ses_event) + "\n",
"Timestamp": "2018-03-26T17:58:59.675Z",
}
response = self.post_from_sns('/anymail/amazon_ses/tracking/', raw_sns_message)
response = self.post_from_sns("/anymail/amazon_ses/tracking/", raw_sns_message)
self.assertEqual(response.status_code, 200)
kwargs = self.assert_handler_called_once_with(self.tracking_handler, sender=AmazonSESTrackingWebhookView,
event=ANY, esp_name='Amazon SES')
event = kwargs['event']
kwargs = self.assert_handler_called_once_with(
self.tracking_handler,
sender=AmazonSESTrackingWebhookView,
event=ANY,
esp_name="Amazon SES",
)
event = kwargs["event"]
self.assertEqual(event.event_type, "opened")
self.assertEqual(event.recipient, "recipient@example.com")
self.assertEqual(event.user_agent, "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_3 like Mac OS X)...")
self.assertEqual(
event.user_agent,
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_3 like Mac OS X)...",
)
def test_click_event(self):
raw_ses_event = {
@@ -343,12 +423,13 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
"samplekey1": ["samplevalue1"],
},
"timestamp": "2017-08-09T23:51:25.570Z",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36..."
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
" AppleWebKit/537.36...",
},
"mail": {
"destination": ["recipient@example.com"],
"messageId": "7c191be45-e9aedb9a-02f9-4d12-a87d-dd0099a07f8a-000000",
}
},
}
raw_sns_message = {
"Type": "Notification",
@@ -356,15 +437,24 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
"Message": json.dumps(raw_ses_event) + "\n",
"Timestamp": "2018-03-26T17:58:59.675Z",
}
response = self.post_from_sns('/anymail/amazon_ses/tracking/', raw_sns_message)
response = self.post_from_sns("/anymail/amazon_ses/tracking/", raw_sns_message)
self.assertEqual(response.status_code, 200)
kwargs = self.assert_handler_called_once_with(self.tracking_handler, sender=AmazonSESTrackingWebhookView,
event=ANY, esp_name='Amazon SES')
event = kwargs['event']
kwargs = self.assert_handler_called_once_with(
self.tracking_handler,
sender=AmazonSESTrackingWebhookView,
event=ANY,
esp_name="Amazon SES",
)
event = kwargs["event"]
self.assertEqual(event.event_type, "clicked")
self.assertEqual(event.recipient, "recipient@example.com")
self.assertEqual(event.user_agent, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...")
self.assertEqual(event.click_url, "https://docs.aws.amazon.com/ses/latest/DeveloperGuide/")
self.assertEqual(
event.user_agent,
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...",
)
self.assertEqual(
event.click_url, "https://docs.aws.amazon.com/ses/latest/DeveloperGuide/"
)
def test_rendering_failure_event(self):
raw_ses_event = {
@@ -374,9 +464,10 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
"destination": ["recipient@example.com"],
},
"failure": {
"errorMessage": "Attribute 'attributeName' is not present in the rendering data.",
"templateName": "MyTemplate"
}
"errorMessage": "Attribute 'attributeName' is not present"
" in the rendering data.",
"templateName": "MyTemplate",
},
}
raw_sns_message = {
"Type": "Notification",
@@ -384,14 +475,21 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
"Message": json.dumps(raw_ses_event) + "\n",
"Timestamp": "2018-03-26T17:58:59.675Z",
}
response = self.post_from_sns('/anymail/amazon_ses/tracking/', raw_sns_message)
response = self.post_from_sns("/anymail/amazon_ses/tracking/", raw_sns_message)
self.assertEqual(response.status_code, 200)
kwargs = self.assert_handler_called_once_with(self.tracking_handler, sender=AmazonSESTrackingWebhookView,
event=ANY, esp_name='Amazon SES')
event = kwargs['event']
kwargs = self.assert_handler_called_once_with(
self.tracking_handler,
sender=AmazonSESTrackingWebhookView,
event=ANY,
esp_name="Amazon SES",
)
event = kwargs["event"]
self.assertEqual(event.event_type, "failed")
self.assertEqual(event.recipient, "recipient@example.com")
self.assertEqual(event.description, "Attribute 'attributeName' is not present in the rendering data.")
self.assertEqual(
event.description,
"Attribute 'attributeName' is not present in the rendering data.",
)
def test_incorrect_received_event(self):
"""The tracking webhook should warn if it receives inbound events"""
@@ -403,13 +501,14 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
}
with self.assertRaisesMessage(
AnymailConfigurationError,
"You seem to have set an Amazon SES *inbound* receipt rule to publish to an SNS Topic that posts "
"to Anymail's *tracking* webhook URL. (SNS TopicArn arn:aws:sns:us-east-1:111111111111:SES_Inbound)"
"You seem to have set an Amazon SES *inbound* receipt rule to publish to an"
" SNS Topic that posts to Anymail's *tracking* webhook URL. (SNS TopicArn"
" arn:aws:sns:us-east-1:111111111111:SES_Inbound)",
):
self.post_from_sns('/anymail/amazon_ses/tracking/', raw_sns_message)
self.post_from_sns("/anymail/amazon_ses/tracking/", raw_sns_message)
@tag('amazon_ses')
@tag("amazon_ses")
class AmazonSESSubscriptionManagementTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
# Anymail will automatically respond to SNS subscription notifications
# if Anymail is configured to require basic auth via WEBHOOK_SECRET.
@@ -417,15 +516,22 @@ class AmazonSESSubscriptionManagementTests(WebhookTestCase, AmazonSESWebhookTest
def setUp(self):
super().setUp()
# Mock boto3.session.Session().client('sns').confirm_subscription (and any other client operations)
# (We could also use botocore.stub.Stubber, but mock works well with our test structure)
self.patch_boto3_session = patch('anymail.webhooks.amazon_ses.boto3.session.Session', autospec=True)
self.mock_session = self.patch_boto3_session.start() # boto3.session.Session
# Mock boto3.session.Session().client('sns').confirm_subscription (and any
# other client operations). (We could also use botocore.stub.Stubber, but mock
# works well with our test structure.)
self.patch_boto3_session = patch(
"anymail.webhooks.amazon_ses.boto3.session.Session", autospec=True
)
#: boto3.session.Session
self.mock_session = self.patch_boto3_session.start()
self.addCleanup(self.patch_boto3_session.stop)
self.mock_client = self.mock_session.return_value.client # boto3.session.Session().client
self.mock_client_instance = self.mock_client.return_value # boto3.session.Session().client('sns', ...)
#: boto3.session.Session().client
self.mock_client = self.mock_session.return_value.client
#: boto3.session.Session().client('sns', ...)
self.mock_client_instance = self.mock_client.return_value
self.mock_client_instance.confirm_subscription.return_value = {
'SubscriptionArn': 'arn:aws:sns:us-west-2:123456789012:SES_Notifications:aaaaaaa-...'
"SubscriptionArn": "arn:aws:sns:us-west-2:123456789012:SES_Notifications"
":aaaaaaa-..."
}
SNS_SUBSCRIPTION_CONFIRMATION = {
@@ -433,23 +539,32 @@ class AmazonSESSubscriptionManagementTests(WebhookTestCase, AmazonSESWebhookTest
"MessageId": "165545c9-2a5c-472c-8df2-7ff2be2b3b1b",
"Token": "EXAMPLE_TOKEN",
"TopicArn": "arn:aws:sns:us-west-2:123456789012:SES_Notifications",
"Message": "You have chosen to subscribe ...\nTo confirm..., visit the SubscribeURL included in this message.",
"SubscribeURL": "https://sns.us-west-2.amazonaws.com/?Action=ConfirmSubscription&TopicArn=...",
"Message": "You have chosen to subscribe ...\n"
"To confirm..., visit the SubscribeURL included in this message.",
"SubscribeURL": "https://sns.us-west-2.amazonaws.com/"
"?Action=ConfirmSubscription&TopicArn=...",
"Timestamp": "2012-04-26T20:45:04.751Z",
"SignatureVersion": "1",
"Signature": "EXAMPLE-SIGNATURE==",
"SigningCertURL": "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-12345abcde.pem"
"SigningCertURL": "https://sns.us-west-2.amazonaws.com/"
"SimpleNotificationService-12345abcde.pem",
}
def test_sns_subscription_auto_confirmation(self):
"""Anymail webhook will auto-confirm SNS topic subscriptions"""
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)
# auto-confirmed:
self.mock_client.assert_called_once_with('sns', config=ANY, region_name="us-west-2")
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")
Token="EXAMPLE_TOKEN",
AuthenticateOnUnsubscribe="true",
)
# didn't notify receivers:
self.assertEqual(self.tracking_handler.call_count, 0)
self.assertEqual(self.inbound_handler.call_count, 0)
@@ -457,43 +572,68 @@ class AmazonSESSubscriptionManagementTests(WebhookTestCase, AmazonSESWebhookTest
def test_sns_subscription_confirmation_failure(self):
"""Auto-confirmation allows error through if confirm call fails"""
from botocore.exceptions import ClientError
self.mock_client_instance.confirm_subscription.side_effect = ClientError({
'Error': {
'Type': 'Sender',
'Code': 'InternalError',
'Message': 'Gremlins!',
self.mock_client_instance.confirm_subscription.side_effect = ClientError(
{
"Error": {
"Type": "Sender",
"Code": "InternalError",
"Message": "Gremlins!",
},
"ResponseMetadata": {
"RequestId": "aaaaaaaa-2222-1111-8888-bbbb3333bbbb",
"HTTPStatusCode": 500,
},
},
'ResponseMetadata': {
'RequestId': 'aaaaaaaa-2222-1111-8888-bbbb3333bbbb',
'HTTPStatusCode': 500,
}
}, operation_name="confirm_subscription")
operation_name="confirm_subscription",
)
with self.assertRaisesMessage(ClientError, "Gremlins!"):
self.post_from_sns('/anymail/amazon_ses/tracking/', self.SNS_SUBSCRIPTION_CONFIRMATION)
self.post_from_sns(
"/anymail/amazon_ses/tracking/", self.SNS_SUBSCRIPTION_CONFIRMATION
)
# didn't notify receivers:
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"""
"""
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")
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
# clear WEBHOOK_SECRET setting from base WebhookTestCase
@override_settings(ANYMAIL={})
def test_sns_subscription_confirmation_auth_disabled(self):
"""Anymail *won't* auto-confirm SNS subscriptions if WEBHOOK_SECRET isn't in use"""
warnings.simplefilter("ignore", AnymailInsecureWebhookWarning) # (this gets tested elsewhere)
with self.assertLogs('django.security.AnymailWebhookValidationFailure') as cm:
response = self.post_from_sns('/anymail/amazon_ses/tracking/', self.SNS_SUBSCRIPTION_CONFIRMATION)
"""
Anymail *won't* auto-confirm SNS subscriptions if WEBHOOK_SECRET isn't in use
"""
# (warning gets tested elsewhere)
warnings.simplefilter("ignore", AnymailInsecureWebhookWarning)
with self.assertLogs("django.security.AnymailWebhookValidationFailure") as cm:
response = self.post_from_sns(
"/anymail/amazon_ses/tracking/", self.SNS_SUBSCRIPTION_CONFIRMATION
)
self.assertEqual(response.status_code, 400) # bad request
self.assertEqual(
["Anymail received an unexpected SubscriptionConfirmation request for Amazon SNS topic "
"'arn:aws:sns:us-west-2:123456789012:SES_Notifications'. (Anymail can automatically confirm "
"SNS subscriptions if you set a WEBHOOK_SECRET and use that in your SNS notification url. Or "
"you can manually confirm this subscription in the SNS dashboard with token 'EXAMPLE_TOKEN'.)"],
[record.getMessage() for record in cm.records])
[
"Anymail received an unexpected SubscriptionConfirmation request for"
" Amazon SNS topic"
" 'arn:aws:sns:us-west-2:123456789012:SES_Notifications'."
" (Anymail can automatically confirm SNS subscriptions if you set"
" a WEBHOOK_SECRET and use that in your SNS notification url."
" Or you can manually confirm this subscription in the SNS dashboard"
" with token 'EXAMPLE_TOKEN'.)"
],
[record.getMessage() for record in cm.records],
)
# *didn't* try to confirm the subscription:
self.assertEqual(self.mock_client_instance.confirm_subscription.call_count, 0)
# didn't notify receivers:
@@ -501,37 +641,55 @@ class AmazonSESSubscriptionManagementTests(WebhookTestCase, AmazonSESWebhookTest
self.assertEqual(self.inbound_handler.call_count, 0)
def test_sns_confirmation_success_notification(self):
"""Anymail ignores the 'Successfully validated' notification after confirming an SNS subscription"""
response = self.post_from_sns('/anymail/amazon_ses/tracking/', {
"Type": "Notification",
"MessageId": "7fbca0d9-eeab-5285-ae27-f3f57f2e84b0",
"TopicArn": "arn:aws:sns:us-west-2:123456789012:SES_Notifications",
"Message": "Successfully validated SNS topic for Amazon SES event publishing.",
"Timestamp": "2018-03-21T16:58:45.077Z",
"SignatureVersion": "1",
"Signature": "EXAMPLE_SIGNATURE==",
"SigningCertURL": "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-12345abcde.pem",
"UnsubscribeURL": "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe...",
})
"""
Anymail ignores the 'Successfully validated' notification
after confirming an SNS subscription
"""
response = self.post_from_sns(
"/anymail/amazon_ses/tracking/",
{
"Type": "Notification",
"MessageId": "7fbca0d9-eeab-5285-ae27-f3f57f2e84b0",
"TopicArn": "arn:aws:sns:us-west-2:123456789012:SES_Notifications",
"Message": "Successfully validated SNS topic"
" for Amazon SES event publishing.",
"Timestamp": "2018-03-21T16:58:45.077Z",
"SignatureVersion": "1",
"Signature": "EXAMPLE_SIGNATURE==",
"SigningCertURL": "https://sns.us-east-1.amazonaws.com"
"/SimpleNotificationService-12345abcde.pem",
"UnsubscribeURL": "https://sns.us-east-1.amazonaws.com"
"/?Action=Unsubscribe...",
},
)
self.assertEqual(response.status_code, 200)
# didn't notify receivers:
self.assertEqual(self.tracking_handler.call_count, 0)
self.assertEqual(self.inbound_handler.call_count, 0)
def test_sns_unsubscribe_confirmation(self):
"""Anymail ignores the UnsubscribeConfirmation SNS message after deleting a subscription"""
response = self.post_from_sns('/anymail/amazon_ses/tracking/', {
"Type": "UnsubscribeConfirmation",
"MessageId": "47138184-6831-46b8-8f7c-afc488602d7d",
"Token": "EXAMPLE_TOKEN",
"TopicArn": "arn:aws:sns:us-west-2:123456789012:SES_Notifications",
"Message": "You have chosen to deactivate subscription ...\nTo cancel ... visit the SubscribeURL...",
"SubscribeURL": "https://sns.us-west-2.amazonaws.com/?Action=ConfirmSubscription&TopicArn=...",
"Timestamp": "2012-04-26T20:06:41.581Z",
"SignatureVersion": "1",
"Signature": "EXAMPLE_SIGNATURE==",
"SigningCertURL": "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-12345abcde.pem",
})
"""
Anymail ignores the UnsubscribeConfirmation SNS message
after deleting a subscription
"""
response = self.post_from_sns(
"/anymail/amazon_ses/tracking/",
{
"Type": "UnsubscribeConfirmation",
"MessageId": "47138184-6831-46b8-8f7c-afc488602d7d",
"Token": "EXAMPLE_TOKEN",
"TopicArn": "arn:aws:sns:us-west-2:123456789012:SES_Notifications",
"Message": "You have chosen to deactivate subscription ...\n"
"To cancel ... visit the SubscribeURL...",
"SubscribeURL": "https://sns.us-west-2.amazonaws.com"
"/?Action=ConfirmSubscription&TopicArn=...",
"Timestamp": "2012-04-26T20:06:41.581Z",
"SignatureVersion": "1",
"Signature": "EXAMPLE_SIGNATURE==",
"SigningCertURL": "https://sns.us-east-1.amazonaws.com"
"/SimpleNotificationService-12345abcde.pem",
},
)
self.assertEqual(response.status_code, 200)
# *didn't* try to use the Token to re-enable the subscription:
self.assertEqual(self.mock_client_instance.confirm_subscription.call_count, 0)
@@ -541,8 +699,13 @@ class AmazonSESSubscriptionManagementTests(WebhookTestCase, AmazonSESWebhookTest
@override_settings(ANYMAIL_AMAZON_SES_AUTO_CONFIRM_SNS_SUBSCRIPTIONS=False)
def test_disable_auto_confirmation(self):
"""The ANYMAIL setting AMAZON_SES_AUTO_CONFIRM_SNS_SUBSCRIPTIONS will disable this feature"""
response = self.post_from_sns('/anymail/amazon_ses/tracking/', self.SNS_SUBSCRIPTION_CONFIRMATION)
"""
The ANYMAIL setting AMAZON_SES_AUTO_CONFIRM_SNS_SUBSCRIPTIONS
will disable this feature
"""
response = self.post_from_sns(
"/anymail/amazon_ses/tracking/", self.SNS_SUBSCRIPTION_CONFIRMATION
)
self.assertEqual(response.status_code, 200)
# *didn't* try to subscribe:
self.assertEqual(self.mock_session.call_count, 0)