From af0e36ab6556b372fbf5b92b3dea29fafb1a3866 Mon Sep 17 00:00:00 2001 From: medmunds Date: Tue, 31 May 2016 11:54:18 -0700 Subject: [PATCH] Webhooks: fix 403 Forbidden errors (csrf check) * csrf_exempt must be applied to View.dispatch, not View.post. * In base WebhookTestCase, enable Django test Client enforce_csrf_checks. (Test Client by default disables CSRF protection.) Closes #19 --- anymail/webhooks/base.py | 5 ++++- tests/utils.py | 12 ++++++++++++ tests/webhook_cases.py | 4 +++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/anymail/webhooks/base.py b/anymail/webhooks/base.py index deda15a..d324239 100644 --- a/anymail/webhooks/base.py +++ b/anymail/webhooks/base.py @@ -104,11 +104,14 @@ class AnymailBaseWebhookView(AnymailBasicAuthMixin, View): http_method_names = ["post", "head", "options"] + @method_decorator(csrf_exempt) + def dispatch(self, request, *args, **kwargs): + return super(AnymailBaseWebhookView, self).dispatch(request, *args, **kwargs) + def head(self, request, *args, **kwargs): # Some ESPs verify the webhook with a HEAD request at configuration time return HttpResponse() - @method_decorator(csrf_exempt) def post(self, request, *args, **kwargs): # Normal Django exception handling will do the right thing: # - AnymailWebhookValidationFailure will turn into an HTTP 400 response diff --git a/tests/utils.py b/tests/utils.py index 59054f5..80fdb73 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -7,6 +7,8 @@ import warnings from base64 import b64decode from contextlib import contextmanager +from django.test import Client + def decode_att(att): """Returns the original data from base64-encoded attachment content""" @@ -142,3 +144,13 @@ class _AssertWarnsContext(object): self.expected_regex.pattern, str(first_matching))) self._raiseFailure("{} not triggered".format(exc_name)) + +class ClientWithCsrfChecks(Client): + """Django test Client that enforces CSRF checks + + https://docs.djangoproject.com/en/1.9/ref/csrf/#testing + """ + + def __init__(self, **defaults): + super(ClientWithCsrfChecks, self).__init__( + enforce_csrf_checks=True, **defaults) diff --git a/tests/webhook_cases.py b/tests/webhook_cases.py index c40723e..e4f8338 100644 --- a/tests/webhook_cases.py +++ b/tests/webhook_cases.py @@ -6,7 +6,7 @@ from mock import create_autospec, ANY from anymail.exceptions import AnymailInsecureWebhookWarning from anymail.signals import tracking, inbound -from .utils import AnymailTestMixin +from .utils import AnymailTestMixin, ClientWithCsrfChecks def event_handler(sender, event, esp_name, **kwargs): @@ -22,6 +22,8 @@ class WebhookTestCase(AnymailTestMixin, SimpleTestCase): - sets up basic auth by default (since most ESP webhooks warn if it's not enabled) """ + client_class = ClientWithCsrfChecks + def setUp(self): super(WebhookTestCase, self).setUp() # Use correct basic auth by default (individual tests can override):