mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 11:51:05 -05:00
@@ -25,6 +25,22 @@ Release history
|
|||||||
^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^
|
||||||
.. This extra heading level keeps the ToC from becoming unmanageably long
|
.. This extra heading level keeps the ToC from becoming unmanageably long
|
||||||
|
|
||||||
|
vNext
|
||||||
|
-----
|
||||||
|
|
||||||
|
*UNRELEASED*
|
||||||
|
|
||||||
|
Fixes
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
* **Mailgun:** Add new `MAILGUN_WEBHOOK_SIGNING_KEY` setting for verifying tracking and
|
||||||
|
inbound webhook calls. Mailgun's webhook signing key can become different from your
|
||||||
|
`MAILGUN_API_KEY` if you have ever rotated either key.
|
||||||
|
See `docs <https://anymail.readthedocs.io/en/latest/esps/mailgun/#std:setting-ANYMAIL_MAILGUN_WEBHOOK_SIGNING_KEY>`__.
|
||||||
|
(More in `#153`_. Thanks to `@dominik-lekse`_ for reporting the problem and Mailgun's
|
||||||
|
`@mbk-ok`_ for identifying the cause.)
|
||||||
|
|
||||||
|
|
||||||
v6.0.1
|
v6.0.1
|
||||||
------
|
------
|
||||||
|
|
||||||
@@ -945,17 +961,20 @@ Features
|
|||||||
.. _#115: https://github.com/anymail/issues/115
|
.. _#115: https://github.com/anymail/issues/115
|
||||||
.. _#147: https://github.com/anymail/issues/147
|
.. _#147: https://github.com/anymail/issues/147
|
||||||
.. _#148: https://github.com/anymail/issues/148
|
.. _#148: https://github.com/anymail/issues/148
|
||||||
|
.. _#153: https://github.com/anymail/issues/153
|
||||||
|
|
||||||
.. _@ailionx: https://github.com/ailionx
|
.. _@ailionx: https://github.com/ailionx
|
||||||
.. _@calvin: https://github.com/calvin
|
.. _@calvin: https://github.com/calvin
|
||||||
.. _@costela: https://github.com/costela
|
.. _@costela: https://github.com/costela
|
||||||
.. _@decibyte: https://github.com/decibyte
|
.. _@decibyte: https://github.com/decibyte
|
||||||
|
.. _@dominik-lekse: https://github.com/dominik-lekse
|
||||||
.. _@ewingrj: https://github.com/ewingrj
|
.. _@ewingrj: https://github.com/ewingrj
|
||||||
.. _@fdemmer: https://github.com/fdemmer
|
.. _@fdemmer: https://github.com/fdemmer
|
||||||
.. _@janneThoft: https://github.com/janneThoft
|
.. _@janneThoft: https://github.com/janneThoft
|
||||||
.. _@joshkersey: https://github.com/joshkersey
|
.. _@joshkersey: https://github.com/joshkersey
|
||||||
.. _@Lekensteyn: https://github.com/Lekensteyn
|
.. _@Lekensteyn: https://github.com/Lekensteyn
|
||||||
.. _@lewistaylor: https://github.com/lewistaylor
|
.. _@lewistaylor: https://github.com/lewistaylor
|
||||||
|
.. _@mbk-ok: https://github.com/mbk-ok
|
||||||
.. _@RignonNoel: https://github.com/RignonNoel
|
.. _@RignonNoel: https://github.com/RignonNoel
|
||||||
.. _@sebbacon: https://github.com/sebbacon
|
.. _@sebbacon: https://github.com/sebbacon
|
||||||
.. _@varche1: https://github.com/varche1
|
.. _@varche1: https://github.com/varche1
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from .base import AnymailBaseWebhookView
|
|||||||
from ..exceptions import AnymailConfigurationError, AnymailWebhookValidationFailure, AnymailInvalidAddress
|
from ..exceptions import AnymailConfigurationError, AnymailWebhookValidationFailure, AnymailInvalidAddress
|
||||||
from ..inbound import AnymailInboundMessage
|
from ..inbound import AnymailInboundMessage
|
||||||
from ..signals import inbound, tracking, AnymailInboundEvent, AnymailTrackingEvent, EventType, RejectReason
|
from ..signals import inbound, tracking, AnymailInboundEvent, AnymailTrackingEvent, EventType, RejectReason
|
||||||
from ..utils import get_anymail_setting, combine, querydict_getfirst, parse_single_address
|
from ..utils import get_anymail_setting, combine, querydict_getfirst, parse_single_address, UNSET
|
||||||
|
|
||||||
|
|
||||||
class MailgunBaseWebhookView(AnymailBaseWebhookView):
|
class MailgunBaseWebhookView(AnymailBaseWebhookView):
|
||||||
@@ -19,12 +19,18 @@ class MailgunBaseWebhookView(AnymailBaseWebhookView):
|
|||||||
esp_name = "Mailgun"
|
esp_name = "Mailgun"
|
||||||
warn_if_no_basic_auth = False # because we validate against signature
|
warn_if_no_basic_auth = False # because we validate against signature
|
||||||
|
|
||||||
|
webhook_signing_key = None # (Declaring class attr allows override by kwargs in View.as_view.)
|
||||||
|
|
||||||
|
# The `api_key` attribute name is still allowed for compatibility with earlier Anymail releases.
|
||||||
api_key = None # (Declaring class attr allows override by kwargs in View.as_view.)
|
api_key = None # (Declaring class attr allows override by kwargs in View.as_view.)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
# webhook_signing_key: falls back to api_key if webhook_signing_key not provided
|
||||||
api_key = get_anymail_setting('api_key', esp_name=self.esp_name,
|
api_key = get_anymail_setting('api_key', esp_name=self.esp_name,
|
||||||
kwargs=kwargs, allow_bare=True)
|
kwargs=kwargs, allow_bare=True, default=None)
|
||||||
self.api_key = api_key.encode('ascii') # hmac.new requires bytes key in python 3
|
webhook_signing_key = get_anymail_setting('webhook_signing_key', esp_name=self.esp_name,
|
||||||
|
kwargs=kwargs, default=UNSET if api_key is None else api_key)
|
||||||
|
self.webhook_signing_key = webhook_signing_key.encode('ascii') # hmac.new requires bytes key in python 3
|
||||||
super(MailgunBaseWebhookView, self).__init__(**kwargs)
|
super(MailgunBaseWebhookView, self).__init__(**kwargs)
|
||||||
|
|
||||||
def validate_request(self, request):
|
def validate_request(self, request):
|
||||||
@@ -52,7 +58,7 @@ class MailgunBaseWebhookView(AnymailBaseWebhookView):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
raise AnymailWebhookValidationFailure("Mailgun webhook called without required security fields")
|
raise AnymailWebhookValidationFailure("Mailgun webhook called without required security fields")
|
||||||
|
|
||||||
expected_signature = hmac.new(key=self.api_key, msg='{}{}'.format(timestamp, token).encode('ascii'),
|
expected_signature = hmac.new(key=self.webhook_signing_key, msg='{}{}'.format(timestamp, token).encode('ascii'),
|
||||||
digestmod=hashlib.sha256).hexdigest()
|
digestmod=hashlib.sha256).hexdigest()
|
||||||
if not constant_time_compare(signature, expected_signature):
|
if not constant_time_compare(signature, expected_signature):
|
||||||
raise AnymailWebhookValidationFailure("Mailgun webhook called with incorrect signature")
|
raise AnymailWebhookValidationFailure("Mailgun webhook called with incorrect signature")
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ in your settings.py.
|
|||||||
|
|
||||||
.. rubric:: MAILGUN_API_KEY
|
.. rubric:: MAILGUN_API_KEY
|
||||||
|
|
||||||
Required. Your Mailgun API key:
|
Required for sending. Your Mailgun "Private API key" from the Mailgun
|
||||||
|
`API security settings`_:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
@@ -54,6 +55,27 @@ Mailgun sender domain, this setting is not needed.
|
|||||||
See :ref:`mailgun-sender-domain` below for examples.
|
See :ref:`mailgun-sender-domain` below for examples.
|
||||||
|
|
||||||
|
|
||||||
|
.. setting:: ANYMAIL_MAILGUN_WEBHOOK_SIGNING_KEY
|
||||||
|
|
||||||
|
.. rubric:: MAILGUN_WEBHOOK_SIGNING_KEY
|
||||||
|
|
||||||
|
.. versionadded:: 6.1
|
||||||
|
|
||||||
|
Required for tracking or inbound webhooks. Your "HTTP webhook signing key" from the
|
||||||
|
Mailgun `API security settings`_:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
ANYMAIL = {
|
||||||
|
...
|
||||||
|
"MAILGUN_WEBHOOK_SIGNING_KEY": "<your webhook signing key>",
|
||||||
|
}
|
||||||
|
|
||||||
|
If not provided, Anymail will attempt to validate webhooks using the
|
||||||
|
:setting:`MAILGUN_API_KEY <ANYMAIL_MAILGUN_API_KEY>` setting instead. (These two keys have
|
||||||
|
the same values for new Mailgun users, but will diverge if you ever rotate either key.)
|
||||||
|
|
||||||
|
|
||||||
.. setting:: ANYMAIL_MAILGUN_API_URL
|
.. setting:: ANYMAIL_MAILGUN_API_URL
|
||||||
|
|
||||||
.. rubric:: MAILGUN_API_URL
|
.. rubric:: MAILGUN_API_URL
|
||||||
@@ -75,6 +97,9 @@ region:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.. _API security settings: https://app.mailgun.com/app/account/security/api_keys
|
||||||
|
|
||||||
|
|
||||||
.. _mailgun-sender-domain:
|
.. _mailgun-sender-domain:
|
||||||
|
|
||||||
Email sender domain
|
Email sender domain
|
||||||
@@ -260,9 +285,14 @@ Status tracking webhooks
|
|||||||
|
|
||||||
Added support for Mailgun's June, 2018 (non-"legacy") webhook format.
|
Added support for Mailgun's June, 2018 (non-"legacy") webhook format.
|
||||||
|
|
||||||
|
.. versionchanged:: 6.1
|
||||||
|
|
||||||
|
Added support for a new :setting:`MAILGUN_WEBHOOK_SIGNING_KEY <ANYMAIL_MAILGUN_WEBHOOK_SIGNING_KEY>`
|
||||||
|
setting, separate from your MAILGUN_API_KEY.
|
||||||
|
|
||||||
If you are using Anymail's normalized :ref:`status tracking <event-tracking>`, enter
|
If you are using Anymail's normalized :ref:`status tracking <event-tracking>`, enter
|
||||||
the url in the `Mailgun webhooks dashboard`_. (Be sure to select the correct sending
|
the url in the Mailgun webhooks config for your domain. (Be sure to select the correct
|
||||||
domain---Mailgun's sandbox and production domains have separate webhook settings.)
|
sending domain---Mailgun's sandbox and production domains have separate webhook settings.)
|
||||||
|
|
||||||
Mailgun allows you to enter a different URL for each event type: just enter this same
|
Mailgun allows you to enter a different URL for each event type: just enter this same
|
||||||
Anymail tracking URL for all events you want to receive:
|
Anymail tracking URL for all events you want to receive:
|
||||||
@@ -273,8 +303,9 @@ Anymail tracking URL for all events you want to receive:
|
|||||||
* *yoursite.example.com* is your Django site
|
* *yoursite.example.com* is your Django site
|
||||||
|
|
||||||
Mailgun implements a limited form of webhook signing, and Anymail will verify
|
Mailgun implements a limited form of webhook signing, and Anymail will verify
|
||||||
these signatures (based on your :setting:`MAILGUN_API_KEY <ANYMAIL_MAILGUN_API_KEY>`
|
these signatures against your
|
||||||
Anymail setting). By default, Mailgun's webhook signature provides similar security
|
:setting:`MAILGUN_WEBHOOK_SIGNING_KEY <ANYMAIL_MAILGUN_WEBHOOK_SIGNING_KEY>`
|
||||||
|
Anymail setting. By default, Mailgun's webhook signature provides similar security
|
||||||
to Anymail's shared webhook secret, so it's acceptable to omit the
|
to Anymail's shared webhook secret, so it's acceptable to omit the
|
||||||
:setting:`ANYMAIL_WEBHOOK_SECRET` setting (and "{random}:{random}@" portion of the
|
:setting:`ANYMAIL_WEBHOOK_SECRET` setting (and "{random}:{random}@" portion of the
|
||||||
webhook url) with Mailgun webhooks.
|
webhook url) with Mailgun webhooks.
|
||||||
@@ -321,7 +352,6 @@ Mailgun's other event APIs.)
|
|||||||
newer, non-legacy webhooks.)
|
newer, non-legacy webhooks.)
|
||||||
|
|
||||||
|
|
||||||
.. _Mailgun webhooks dashboard: https://mailgun.com/app/webhooks
|
|
||||||
.. _Mailgun webhook payload: https://documentation.mailgun.com/en/latest/user_manual.html#webhooks
|
.. _Mailgun webhook payload: https://documentation.mailgun.com/en/latest/user_manual.html#webhooks
|
||||||
|
|
||||||
|
|
||||||
@@ -333,7 +363,7 @@ Inbound webhook
|
|||||||
If you want to receive email from Mailgun through Anymail's normalized :ref:`inbound <inbound>`
|
If you want to receive email from Mailgun through Anymail's normalized :ref:`inbound <inbound>`
|
||||||
handling, follow Mailgun's `Receiving, Storing and Fowarding Messages`_ guide to set up
|
handling, follow Mailgun's `Receiving, Storing and Fowarding Messages`_ guide to set up
|
||||||
an inbound route that forwards to Anymail's inbound webhook. (You can configure routes
|
an inbound route that forwards to Anymail's inbound webhook. (You can configure routes
|
||||||
using Mailgun's API, or simply using the `Mailgun routes dashboard`_.)
|
using Mailgun's API, or simply using the `Mailgun receiving config`_.)
|
||||||
|
|
||||||
The *action* for your route will be either:
|
The *action* for your route will be either:
|
||||||
|
|
||||||
@@ -352,9 +382,17 @@ received email (including complex forms like multi-message mailing list digests)
|
|||||||
If you want to use Anymail's normalized :attr:`~anymail.inbound.AnymailInboundMessage.spam_detected` and
|
If you want to use Anymail's normalized :attr:`~anymail.inbound.AnymailInboundMessage.spam_detected` and
|
||||||
:attr:`~anymail.inbound.AnymailInboundMessage.spam_score` attributes, you'll need to set your Mailgun
|
:attr:`~anymail.inbound.AnymailInboundMessage.spam_score` attributes, you'll need to set your Mailgun
|
||||||
domain's inbound spam filter to "Deliver spam, but add X-Mailgun-SFlag and X-Mailgun-SScore headers"
|
domain's inbound spam filter to "Deliver spam, but add X-Mailgun-SFlag and X-Mailgun-SScore headers"
|
||||||
(in the `Mailgun domains dashboard`_).
|
(in the `Mailgun domains config`_).
|
||||||
|
|
||||||
|
Anymail will verify Mailgun inbound message events using your
|
||||||
|
:setting:`MAILGUN_WEBHOOK_SIGNING_KEY <ANYMAIL_MAILGUN_WEBHOOK_SIGNING_KEY>`
|
||||||
|
Anymail setting. By default, Mailgun's webhook signature provides similar security
|
||||||
|
to Anymail's shared webhook secret, so it's acceptable to omit the
|
||||||
|
:setting:`ANYMAIL_WEBHOOK_SECRET` setting (and "{random}:{random}@" portion of the
|
||||||
|
action) with Mailgun inbound routing.
|
||||||
|
|
||||||
|
|
||||||
.. _Receiving, Storing and Fowarding Messages:
|
.. _Receiving, Storing and Fowarding Messages:
|
||||||
https://documentation.mailgun.com/en/latest/user_manual.html#receiving-forwarding-and-storing-messages
|
https://documentation.mailgun.com/en/latest/user_manual.html#receiving-forwarding-and-storing-messages
|
||||||
.. _Mailgun routes dashboard: https://app.mailgun.com/app/routes
|
.. _Mailgun receiving config: https://app.mailgun.com/app/receiving/routes
|
||||||
.. _Mailgun domains dashboard: https://app.mailgun.com/app/domains
|
.. _Mailgun domains config: https://app.mailgun.com/app/sending/domains
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ from anymail.signals import AnymailInboundEvent
|
|||||||
from anymail.webhooks.mailgun import MailgunInboundWebhookView
|
from anymail.webhooks.mailgun import MailgunInboundWebhookView
|
||||||
|
|
||||||
from .test_mailgun_webhooks import (
|
from .test_mailgun_webhooks import (
|
||||||
TEST_API_KEY, mailgun_sign_payload,
|
TEST_WEBHOOK_SIGNING_KEY, mailgun_sign_payload,
|
||||||
mailgun_sign_legacy_payload, querydict_to_postdict)
|
mailgun_sign_legacy_payload, querydict_to_postdict)
|
||||||
from .utils import sample_image_content, sample_email_content
|
from .utils import sample_image_content, sample_email_content
|
||||||
from .webhook_cases import WebhookTestCase
|
from .webhook_cases import WebhookTestCase
|
||||||
|
|
||||||
|
|
||||||
@tag('mailgun')
|
@tag('mailgun')
|
||||||
@override_settings(ANYMAIL_MAILGUN_API_KEY=TEST_API_KEY)
|
@override_settings(ANYMAIL_MAILGUN_WEBHOOK_SIGNING_KEY=TEST_WEBHOOK_SIGNING_KEY)
|
||||||
class MailgunInboundTestCase(WebhookTestCase):
|
class MailgunInboundTestCase(WebhookTestCase):
|
||||||
def test_inbound_basics(self):
|
def test_inbound_basics(self):
|
||||||
raw_event = mailgun_sign_legacy_payload({
|
raw_event = mailgun_sign_legacy_payload({
|
||||||
|
|||||||
@@ -14,19 +14,19 @@ from anymail.webhooks.mailgun import MailgunTrackingWebhookView
|
|||||||
|
|
||||||
from .webhook_cases import WebhookTestCase, WebhookBasicAuthTestsMixin
|
from .webhook_cases import WebhookTestCase, WebhookBasicAuthTestsMixin
|
||||||
|
|
||||||
TEST_API_KEY = 'TEST_API_KEY'
|
TEST_WEBHOOK_SIGNING_KEY = 'TEST_WEBHOOK_SIGNING_KEY'
|
||||||
|
|
||||||
|
|
||||||
def mailgun_signature(timestamp, token, api_key):
|
def mailgun_signature(timestamp, token, webhook_signing_key):
|
||||||
"""Generates a Mailgun webhook signature"""
|
"""Generates a Mailgun webhook signature"""
|
||||||
# https://documentation.mailgun.com/en/latest/user_manual.html#securing-webhooks
|
# https://documentation.mailgun.com/en/latest/user_manual.html#securing-webhooks
|
||||||
return hmac.new(
|
return hmac.new(
|
||||||
key=api_key.encode('ascii'),
|
key=webhook_signing_key.encode('ascii'),
|
||||||
msg='{timestamp}{token}'.format(timestamp=timestamp, token=token).encode('ascii'),
|
msg='{timestamp}{token}'.format(timestamp=timestamp, token=token).encode('ascii'),
|
||||||
digestmod=hashlib.sha256).hexdigest()
|
digestmod=hashlib.sha256).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def mailgun_sign_payload(data, api_key=TEST_API_KEY):
|
def mailgun_sign_payload(data, webhook_signing_key=TEST_WEBHOOK_SIGNING_KEY):
|
||||||
"""Add or complete Mailgun webhook signature block in data dict"""
|
"""Add or complete Mailgun webhook signature block in data dict"""
|
||||||
# Modifies the dict in place
|
# Modifies the dict in place
|
||||||
event_data = data.get('event-data', {})
|
event_data = data.get('event-data', {})
|
||||||
@@ -34,16 +34,16 @@ def mailgun_sign_payload(data, api_key=TEST_API_KEY):
|
|||||||
token = signature.setdefault('token', '1234567890abcdef1234567890abcdef')
|
token = signature.setdefault('token', '1234567890abcdef1234567890abcdef')
|
||||||
timestamp = signature.setdefault('timestamp',
|
timestamp = signature.setdefault('timestamp',
|
||||||
str(int(float(event_data.get('timestamp', '1234567890.123')))))
|
str(int(float(event_data.get('timestamp', '1234567890.123')))))
|
||||||
signature['signature'] = mailgun_signature(timestamp, token, api_key=api_key)
|
signature['signature'] = mailgun_signature(timestamp, token, webhook_signing_key=webhook_signing_key)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def mailgun_sign_legacy_payload(data, api_key=TEST_API_KEY):
|
def mailgun_sign_legacy_payload(data, webhook_signing_key=TEST_WEBHOOK_SIGNING_KEY):
|
||||||
"""Add a Mailgun webhook signature to data dict"""
|
"""Add a Mailgun webhook signature to data dict"""
|
||||||
# Modifies the dict in place
|
# Modifies the dict in place
|
||||||
data.setdefault('timestamp', '1234567890')
|
data.setdefault('timestamp', '1234567890')
|
||||||
data.setdefault('token', '1234567890abcdef1234567890abcdef')
|
data.setdefault('token', '1234567890abcdef1234567890abcdef')
|
||||||
data['signature'] = mailgun_signature(data['timestamp'], data['token'], api_key=api_key)
|
data['signature'] = mailgun_signature(data['timestamp'], data['token'], webhook_signing_key=webhook_signing_key)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
@@ -61,14 +61,44 @@ def querydict_to_postdict(qd):
|
|||||||
|
|
||||||
@tag('mailgun')
|
@tag('mailgun')
|
||||||
class MailgunWebhookSettingsTestCase(WebhookTestCase):
|
class MailgunWebhookSettingsTestCase(WebhookTestCase):
|
||||||
def test_requires_api_key(self):
|
def test_requires_webhook_signing_key(self):
|
||||||
with self.assertRaises(ImproperlyConfigured):
|
with self.assertRaisesMessage(ImproperlyConfigured, "MAILGUN_WEBHOOK_SIGNING_KEY"):
|
||||||
self.client.post('/anymail/mailgun/tracking/', content_type="application/json",
|
self.client.post('/anymail/mailgun/tracking/', content_type="application/json",
|
||||||
data=json.dumps(mailgun_sign_payload({'event-data': {'event': 'delivered'}})))
|
data=json.dumps(mailgun_sign_payload({'event-data': {'event': 'delivered'}})))
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
ANYMAIL_MAILGUN_API_KEY='TEST_API_KEY',
|
||||||
|
ANYMAIL_MAILGUN_WEBHOOK_SIGNING_KEY='TEST_WEBHOOK_SIGNING_KEY',
|
||||||
|
)
|
||||||
|
def test_webhook_signing_is_different_from_api_key(self):
|
||||||
|
"""Webhooks should use MAILGUN_WEBHOOK_SIGNING_KEY, not MAILGUN_API_KEY, if both provided"""
|
||||||
|
payload = json.dumps(mailgun_sign_payload({'event-data': {'event': 'delivered'}},
|
||||||
|
webhook_signing_key='TEST_WEBHOOK_SIGNING_KEY'))
|
||||||
|
response = self.client.post('/anymail/mailgun/tracking/', content_type="application/json", data=payload)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
@override_settings(ANYMAIL_MAILGUN_API_KEY='TEST_API_KEY')
|
||||||
|
def test_defaults_webhook_signing_to_api_key(self):
|
||||||
|
"""Webhooks should default to MAILGUN_API_KEY if MAILGUN_WEBHOOK_SIGNING_KEY not provided"""
|
||||||
|
payload = json.dumps(mailgun_sign_payload({'event-data': {'event': 'delivered'}},
|
||||||
|
webhook_signing_key='TEST_API_KEY'))
|
||||||
|
response = self.client.post('/anymail/mailgun/tracking/', content_type="application/json", data=payload)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_webhook_signing_key_view_params(self):
|
||||||
|
"""Webhook signing key can be provided as a view param"""
|
||||||
|
view = MailgunTrackingWebhookView.as_view(webhook_signing_key='VIEW_SIGNING_KEY')
|
||||||
|
view_instance = view.view_class(**view.view_initkwargs)
|
||||||
|
self.assertEqual(view_instance.webhook_signing_key, b'VIEW_SIGNING_KEY')
|
||||||
|
|
||||||
|
# Can also use `api_key` param for backwards compatiblity with earlier Anymail versions
|
||||||
|
view = MailgunTrackingWebhookView.as_view(api_key='VIEW_API_KEY')
|
||||||
|
view_instance = view.view_class(**view.view_initkwargs)
|
||||||
|
self.assertEqual(view_instance.webhook_signing_key, b'VIEW_API_KEY')
|
||||||
|
|
||||||
|
|
||||||
@tag('mailgun')
|
@tag('mailgun')
|
||||||
@override_settings(ANYMAIL_MAILGUN_API_KEY=TEST_API_KEY)
|
@override_settings(ANYMAIL_MAILGUN_WEBHOOK_SIGNING_KEY=TEST_WEBHOOK_SIGNING_KEY)
|
||||||
class MailgunWebhookSecurityTestCase(WebhookTestCase, WebhookBasicAuthTestsMixin):
|
class MailgunWebhookSecurityTestCase(WebhookTestCase, WebhookBasicAuthTestsMixin):
|
||||||
should_warn_if_no_auth = False # because we check webhook signature
|
should_warn_if_no_auth = False # because we check webhook signature
|
||||||
|
|
||||||
@@ -90,14 +120,14 @@ class MailgunWebhookSecurityTestCase(WebhookTestCase, WebhookBasicAuthTestsMixin
|
|||||||
|
|
||||||
def test_verifies_bad_signature(self):
|
def test_verifies_bad_signature(self):
|
||||||
data = mailgun_sign_payload({'event-data': {'event': 'delivered'}},
|
data = mailgun_sign_payload({'event-data': {'event': 'delivered'}},
|
||||||
api_key="wrong API key")
|
webhook_signing_key="wrong signing key")
|
||||||
response = self.client.post('/anymail/mailgun/tracking/', content_type="application/json",
|
response = self.client.post('/anymail/mailgun/tracking/', content_type="application/json",
|
||||||
data=json.dumps(data))
|
data=json.dumps(data))
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
|
|
||||||
@tag('mailgun')
|
@tag('mailgun')
|
||||||
@override_settings(ANYMAIL_MAILGUN_API_KEY=TEST_API_KEY)
|
@override_settings(ANYMAIL_MAILGUN_WEBHOOK_SIGNING_KEY=TEST_WEBHOOK_SIGNING_KEY)
|
||||||
class MailgunTestCase(WebhookTestCase):
|
class MailgunTestCase(WebhookTestCase):
|
||||||
# Tests for Mailgun's new webhooks (announced 2018-06-29)
|
# Tests for Mailgun's new webhooks (announced 2018-06-29)
|
||||||
|
|
||||||
@@ -449,7 +479,7 @@ class MailgunTestCase(WebhookTestCase):
|
|||||||
|
|
||||||
|
|
||||||
@tag('mailgun')
|
@tag('mailgun')
|
||||||
@override_settings(ANYMAIL_MAILGUN_API_KEY=TEST_API_KEY)
|
@override_settings(ANYMAIL_MAILGUN_WEBHOOK_SIGNING_KEY=TEST_WEBHOOK_SIGNING_KEY)
|
||||||
class MailgunLegacyTestCase(WebhookTestCase):
|
class MailgunLegacyTestCase(WebhookTestCase):
|
||||||
# Tests for Mailgun's "legacy" webhooks
|
# Tests for Mailgun's "legacy" webhooks
|
||||||
# (which were the only webhooks available prior to Anymail 4.0)
|
# (which were the only webhooks available prior to Anymail 4.0)
|
||||||
|
|||||||
Reference in New Issue
Block a user