mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 03:41:05 -05:00
Simplify Mandrill webhook validation handshake.
Anymail was requiring Mandrill's webhook authentication key for the initial webhook url validation request from Mandrill, but Mandrill doesn't issue the key until that validation request succeeds. * Defer complaining about missing Mandrill webhook key until actual event post. * Document the double-deploy process required to set up Mandrill webhooks. Fixes #46.
This commit is contained in:
@@ -23,15 +23,23 @@ class MandrillSignatureMixin(object):
|
||||
def __init__(self, **kwargs):
|
||||
# noinspection PyUnresolvedReferences
|
||||
esp_name = self.esp_name
|
||||
webhook_key = get_anymail_setting('webhook_key', esp_name=esp_name,
|
||||
# webhook_key is required for POST, but not for HEAD when Mandrill validates webhook url.
|
||||
# Defer "missing setting" error until we actually try to use it in the POST...
|
||||
webhook_key = get_anymail_setting('webhook_key', esp_name=esp_name, default=None,
|
||||
kwargs=kwargs, allow_bare=True)
|
||||
self.webhook_key = webhook_key.encode('ascii') # hmac.new requires bytes key in python 3
|
||||
if webhook_key is not None:
|
||||
self.webhook_key = webhook_key.encode('ascii') # hmac.new requires bytes key in python 3
|
||||
self.webhook_url = get_anymail_setting('webhook_url', esp_name=esp_name, default=None,
|
||||
kwargs=kwargs, allow_bare=True)
|
||||
# noinspection PyArgumentList
|
||||
super(MandrillSignatureMixin, self).__init__(**kwargs)
|
||||
|
||||
def validate_request(self, request):
|
||||
if self.webhook_key is None:
|
||||
# issue deferred "missing setting" error (re-call get-setting without a default)
|
||||
# noinspection PyUnresolvedReferences
|
||||
get_anymail_setting('webhook_key', esp_name=self.esp_name, allow_bare=True)
|
||||
|
||||
try:
|
||||
signature = request.META["HTTP_X_MANDRILL_SIGNATURE"]
|
||||
except KeyError:
|
||||
|
||||
@@ -191,20 +191,32 @@ Status tracking webhooks
|
||||
------------------------
|
||||
|
||||
If you are using Anymail's normalized :ref:`status tracking <event-tracking>`,
|
||||
follow `Mandrill's instructions`_ to add Anymail's webhook URL:
|
||||
setting up Anymail's webhook URL requires deploying your Django project twice:
|
||||
|
||||
:samp:`https://{random}:{random}@{yoursite.example.com}/anymail/mandrill/tracking/`
|
||||
1. First, follow the instructions to
|
||||
:ref:`configure Anymail's webhooks <webhooks-configuration>`. You *must*
|
||||
deploy before adding the webhook URL to Mandrill, because it will attempt
|
||||
to verify the URL against your production server.
|
||||
|
||||
* *random:random* is an :setting:`ANYMAIL_WEBHOOK_AUTHORIZATION` shared secret
|
||||
* *yoursite.example.com* is your Django site
|
||||
Follow `Mandrill's instructions`_ to add Anymail's webhook URL in their settings:
|
||||
|
||||
Be sure to check the boxes in the Mandrill settings for the event types you want to receive.
|
||||
The same Anymail tracking URL can handle all Mandrill "message" and "sync" events.
|
||||
:samp:`https://{random}:{random}@{yoursite.example.com}/anymail/mandrill/tracking/`
|
||||
|
||||
* *random:random* is an :setting:`ANYMAIL_WEBHOOK_AUTHORIZATION` shared secret
|
||||
* *yoursite.example.com* is your Django site
|
||||
|
||||
Be sure to check the boxes in the Mandrill settings for the event types you want to receive.
|
||||
The same Anymail tracking URL can handle all Mandrill "message" and "sync" events.
|
||||
|
||||
2. Mandrill will provide you a "webhook authentication key" once it verifies the URL
|
||||
is working. Add this to your Django project's Anymail settings under
|
||||
:setting:`MANDRILL_WEBHOOK_KEY <ANYMAIL_MANDRILL_WEBHOOK_KEY>`.
|
||||
(You may also need to set :setting:`MANDRILL_WEBHOOK_URL <ANYMAIL_MANDRILL_WEBHOOK_URL>`
|
||||
depending on your server config.) Then deploy your project again.
|
||||
|
||||
Mandrill implements webhook signing on the entire event payload, and Anymail will
|
||||
verify the signature. You must set :setting:`ANYMAIL_MANDRILL_WEBHOOK_KEY` to the
|
||||
webhook key authentication key issued by Mandrill. You may also need to set
|
||||
:setting:`ANYMAIL_MANDRILL_WEBHOOK_URL` depending on your server config.
|
||||
verify the signature. Until the correct webhook key is set, Anymail will raise
|
||||
an exception for any webhook calls from Mandrill (other than the initial validation request).
|
||||
|
||||
Mandrill will report these Anymail :attr:`~anymail.signals.AnymailTrackingEvent.event_type`\s:
|
||||
sent, rejected, deferred, bounced, opened, clicked, complained, unsubscribed. Mandrill does
|
||||
|
||||
@@ -41,10 +41,17 @@ def mandrill_args(events=None, url='/anymail/mandrill/tracking/', key=TEST_WEBHO
|
||||
|
||||
class MandrillWebhookSettingsTestCase(WebhookTestCase):
|
||||
def test_requires_webhook_key(self):
|
||||
with self.assertRaises(ImproperlyConfigured):
|
||||
with self.assertRaisesRegex(ImproperlyConfigured, r'MANDRILL_WEBHOOK_KEY'):
|
||||
self.client.post('/anymail/mandrill/tracking/',
|
||||
data={'mandrill_events': '[]'})
|
||||
|
||||
def test_head_does_not_require_webhook_key(self):
|
||||
# Mandrill issues an unsigned HEAD request to verify the wehbook url.
|
||||
# Only *after* that succeeds will Mandrill will tell you the webhook key.
|
||||
# So make sure that HEAD request will go through without any key set:
|
||||
response = self.client.head('/anymail/mandrill/tracking/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
@override_settings(ANYMAIL_MANDRILL_WEBHOOK_KEY=TEST_WEBHOOK_KEY)
|
||||
class MandrillWebhookSecurityTestCase(WebhookTestCase, WebhookBasicAuthTestsMixin):
|
||||
|
||||
Reference in New Issue
Block a user