1from wtforms.fields import HiddenField
2from wtforms.validators import ValidationError
3
4__all__ = ("CSRFTokenField", "CSRF")
5
6
7class CSRFTokenField(HiddenField):
8 """
9 A subclass of HiddenField designed for sending the CSRF token that is used
10 for most CSRF protection schemes.
11
12 Notably different from a normal field, this field always renders the
13 current token regardless of the submitted value, and also will not be
14 populated over to object data via populate_obj
15 """
16
17 current_token = None
18
19 def __init__(self, *args, **kw):
20 self.csrf_impl = kw.pop("csrf_impl")
21 super().__init__(*args, **kw)
22
23 def _value(self):
24 """
25 We want to always return the current token on render, regardless of
26 whether a good or bad token was passed.
27 """
28 return self.current_token
29
30 def populate_obj(self, *args):
31 """
32 Don't populate objects with the CSRF token
33 """
34 pass
35
36 def pre_validate(self, form):
37 """
38 Handle validation of this token field.
39 """
40 self.csrf_impl.validate_csrf_token(form, self)
41
42 def process(self, *args, **kwargs):
43 super().process(*args, **kwargs)
44 self.current_token = self.csrf_impl.generate_csrf_token(self)
45
46
47class CSRF:
48 field_class = CSRFTokenField
49
50 def setup_form(self, form):
51 """
52 Receive the form we're attached to and set up fields.
53
54 The default implementation creates a single field of
55 type :attr:`field_class` with name taken from the
56 ``csrf_field_name`` of the class meta.
57
58 :param form:
59 The form instance we're attaching to.
60 :return:
61 A sequence of `(field_name, unbound_field)` 2-tuples which
62 are unbound fields to be added to the form.
63 """
64 meta = form.meta
65 field_name = meta.csrf_field_name
66 unbound_field = self.field_class(label="CSRF Token", csrf_impl=self)
67 return [(field_name, unbound_field)]
68
69 def generate_csrf_token(self, csrf_token_field):
70 """
71 Implementations must override this to provide a method with which one
72 can get a CSRF token for this form.
73
74 A CSRF token is usually a string that is generated deterministically
75 based on some sort of user data, though it can be anything which you
76 can validate on a subsequent request.
77
78 :param csrf_token_field:
79 The field which is being used for CSRF.
80 :return:
81 A generated CSRF string.
82 """
83 raise NotImplementedError()
84
85 def validate_csrf_token(self, form, field):
86 """
87 Override this method to provide custom CSRF validation logic.
88
89 The default CSRF validation logic simply checks if the recently
90 generated token equals the one we received as formdata.
91
92 :param form: The form which has this CSRF token.
93 :param field: The CSRF token field.
94 """
95 if field.current_token != field.data:
96 raise ValidationError(field.gettext("Invalid CSRF Token."))