Skip to content

Commit af0e36a

Browse files
committed
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
1 parent 34af81a commit af0e36a

File tree

3 files changed

+19
-2
lines changed

3 files changed

+19
-2
lines changed

anymail/webhooks/base.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,14 @@ def parse_events(self, request):
104104

105105
http_method_names = ["post", "head", "options"]
106106

107+
@method_decorator(csrf_exempt)
108+
def dispatch(self, request, *args, **kwargs):
109+
return super(AnymailBaseWebhookView, self).dispatch(request, *args, **kwargs)
110+
107111
def head(self, request, *args, **kwargs):
108112
# Some ESPs verify the webhook with a HEAD request at configuration time
109113
return HttpResponse()
110114

111-
@method_decorator(csrf_exempt)
112115
def post(self, request, *args, **kwargs):
113116
# Normal Django exception handling will do the right thing:
114117
# - AnymailWebhookValidationFailure will turn into an HTTP 400 response

tests/utils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from base64 import b64decode
88
from contextlib import contextmanager
99

10+
from django.test import Client
11+
1012

1113
def decode_att(att):
1214
"""Returns the original data from base64-encoded attachment content"""
@@ -142,3 +144,13 @@ def __exit__(self, exc_type, exc_value, tb):
142144
self.expected_regex.pattern, str(first_matching)))
143145
self._raiseFailure("{} not triggered".format(exc_name))
144146

147+
148+
class ClientWithCsrfChecks(Client):
149+
"""Django test Client that enforces CSRF checks
150+
151+
https://docs.djangoproject.com/en/1.9/ref/csrf/#testing
152+
"""
153+
154+
def __init__(self, **defaults):
155+
super(ClientWithCsrfChecks, self).__init__(
156+
enforce_csrf_checks=True, **defaults)

tests/webhook_cases.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from anymail.exceptions import AnymailInsecureWebhookWarning
77
from anymail.signals import tracking, inbound
88

9-
from .utils import AnymailTestMixin
9+
from .utils import AnymailTestMixin, ClientWithCsrfChecks
1010

1111

1212
def event_handler(sender, event, esp_name, **kwargs):
@@ -22,6 +22,8 @@ class WebhookTestCase(AnymailTestMixin, SimpleTestCase):
2222
- sets up basic auth by default (since most ESP webhooks warn if it's not enabled)
2323
"""
2424

25+
client_class = ClientWithCsrfChecks
26+
2527
def setUp(self):
2628
super(WebhookTestCase, self).setUp()
2729
# Use correct basic auth by default (individual tests can override):

0 commit comments

Comments
 (0)