|
2 | 2 |
|
3 | 3 | import six
|
4 | 4 | from django.http import HttpResponse
|
| 5 | +from django.utils.crypto import constant_time_compare |
5 | 6 | from django.utils.decorators import method_decorator
|
6 | 7 | from django.views.decorators.csrf import csrf_exempt
|
7 | 8 | from django.views.generic import View
|
@@ -40,8 +41,13 @@ def __init__(self, **kwargs):
|
40 | 41 | def validate_request(self, request):
|
41 | 42 | """If configured for webhook basic auth, validate request has correct auth."""
|
42 | 43 | if self.basic_auth:
|
43 |
| - basic_auth = get_request_basic_auth(request) |
44 |
| - if basic_auth is None or basic_auth not in self.basic_auth: |
| 44 | + request_auth = get_request_basic_auth(request) |
| 45 | + # Use constant_time_compare to avoid timing attack on basic auth. (It's OK that any() |
| 46 | + # can terminate early: we're not trying to protect how many auth strings are allowed, |
| 47 | + # just the contents of each individual auth string.) |
| 48 | + auth_ok = any(constant_time_compare(request_auth, allowed_auth) |
| 49 | + for allowed_auth in self.basic_auth) |
| 50 | + if not auth_ok: |
45 | 51 | # noinspection PyUnresolvedReferences
|
46 | 52 | raise AnymailWebhookValidationFailure(
|
47 | 53 | "Missing or invalid basic auth in Anymail %s webhook" % self.esp_name)
|
@@ -77,8 +83,11 @@ def validate_request(self, request):
|
77 | 83 | *All* definitions of this method in the class chain (including mixins)
|
78 | 84 | will be called. There is no need to chain to the superclass.
|
79 | 85 | (See self.run_validators and collect_all_methods.)
|
| 86 | +
|
| 87 | + Security note: use django.utils.crypto.constant_time_compare for string |
| 88 | + comparisons, to avoid exposing your validation to a timing attack. |
80 | 89 | """
|
81 |
| - # if request.POST['signature'] != expected_signature: |
| 90 | + # if not constant_time_compare(request.POST['signature'], expected_signature): |
82 | 91 | # raise AnymailWebhookValidationFailure("...message...")
|
83 | 92 | # (else just do nothing)
|
84 | 93 | pass
|
|
0 commit comments