Code

Ticket #7167: 7167-safeform1.diff

File 7167-safeform1.diff, 5.7 KB (added by ElliottM, 5 years ago)

revised docstring

Line 
1Index: django/contrib/csrf/tests.py
2===================================================================
3--- django/contrib/csrf/tests.py        (revision 0)
4+++ django/contrib/csrf/tests.py        (revision 0)
5@@ -0,0 +1,69 @@
6+"""
7+
8+>>> from django.conf import settings
9+>>> from django.http import HttpRequest
10+>>> from django.contrib.csrf.forms import SafeForm
11+
12+>>> settings.SECRET_KEY='secret'
13+>>> settings.SESSION_COOKIE_NAME='session_id'
14+
15+#If SafeForm is not passed a HttpRequest object, a ValueError is raised
16+>>> form=SafeForm()
17+Traceback (most recent call last):
18+  ...
19+ValueError: SafeForm must be given a HttpRequest object
20+
21+#if the user who made the request does not have the session cookie set,
22+#a csrf token cannot be generated, so the value for the token should be empty
23+>>> request=HttpRequest()
24+>>> form=SafeForm(request=request)
25+>>> form.csrf_token_field()
26+u'<input type=\"hidden\" name=\"_csrf_token\" id=\"id__csrf_token\" />'
27+
28+#if a user that does not send any cookies tries to submit a form,
29+#SafeForm should automatically invalidate it
30+>>> request=HttpRequest()
31+>>> request.POST['_csrf_token']='2376e3ffd767c170fab368189b7e4799'
32+>>> form=SafeForm(request.POST, request=request)
33+>>> form.is_valid()
34+False
35+>>> form.non_field_errors()
36+[u'Your session has expired. Please refresh the page and submit the form again.']
37+
38+#if the request has a valid session ID cookie, generate a token for it
39+>>> request=HttpRequest()
40+>>> request.COOKIES['session_id']='abcde'
41+>>> form=SafeForm(request=request)
42+>>> form.csrf_token_field()
43+u'<input type=\"hidden\" name=\"_csrf_token\" value=\"2376e3ffd767c170fab368189b7e4799\" id=\"id__csrf_token\" />'
44+
45+#if a user submits a form that doesn't have a csrf token at all, the form is not valid
46+>>> form=SafeForm(request.POST,request=request)
47+>>> form.is_valid()
48+False
49+>>> form.non_field_errors()
50+[u'Your session has expired. Please refresh the page and submit the form again.']
51+
52+#if a user submits a form with an incorrect csrf token, the form is not valid.
53+>>> request=HttpRequest()
54+>>> request.POST['_csrf_token']='hello'
55+>>> request.COOKIES['session_id']='abcde'
56+>>> form=SafeForm(request.POST,request=request)
57+>>> form.is_valid()
58+False
59+>>> form.non_field_errors()
60+[u'Your session has expired. Please refresh the page and submit the form again.']
61+
62+#if a user submits a form with the correct token, only then should is_valid() be True
63+>>> request=HttpRequest()
64+>>> request.POST['_csrf_token']='2376e3ffd767c170fab368189b7e4799'
65+>>> request.COOKIES['session_id']='abcde'
66+>>> form=SafeForm(request.POST,request=request)
67+>>> form.is_valid()
68+True
69+
70+"""
71+
72+if __name__ == '__main__':
73+    import doctest
74+    doctest.testmod()
75Index: django/contrib/csrf/forms.py
76===================================================================
77--- django/contrib/csrf/forms.py        (revision 0)
78+++ django/contrib/csrf/forms.py        (revision 0)
79@@ -0,0 +1,59 @@
80+from django import forms
81+from django.conf import settings
82+from django.utils.hashcompat import md5_constructor
83+from django.utils.safestring import mark_safe
84+
85+class SafeForm(forms.Form):
86+    """
87+    Form that adds protection against Cross Site Request Forgeries by adding
88+    a hidden field and checking for the correct value during validation.
89+   
90+    It works virtually the same as a regular form, with the exception of an
91+    additional parameter passed to the constructor - an HttpRequest object.
92+    It uses the request object to get the user's session ID.
93+   
94+    If the developer is manually specifying each field in their forms rather
95+    than calling form.as_table or some other helper, they will also need to
96+    add {{form.csrf_token_field}} somewhere inside the form in their template
97+   
98+    This form does not rely on django.contrib.sessions to work as long as
99+    whatever backend is ultimately used honors the settings.SESSION_COOKIE_NAME
100+    setting. If that cookie is unset, safeform.is_valid() will always be false
101+    """
102+   
103+    _csrf_token = forms.CharField(widget=forms.HiddenInput, required=False)
104+    INVALID_TOKEN_MSG = "Your session has expired. Please refresh the "\
105+                    + "page and submit the form again."
106+   
107+    def __init__(self, *args, **kwargs):
108+        try:
109+            self._request=kwargs.pop('request')
110+        except KeyError:
111+            raise ValueError("SafeForm must be given a HttpRequest object")
112+       
113+        super(SafeForm,self).__init__(*args, **kwargs)
114+       
115+        #We cannot set the initial in the field declaration because the
116+        #_make_token call needs the reference to "self"
117+        self.fields['_csrf_token'].initial=self._make_token
118+   
119+    def clean(self):
120+        session_token = self._make_token()
121+       
122+        if '_csrf_token' not in self.cleaned_data or self.cleaned_data['_csrf_token'] != session_token or session_token is None:
123+            raise forms.ValidationError(self.INVALID_TOKEN_MSG)
124+       
125+        return self.cleaned_data
126+       
127+       
128+    def _make_token(self):
129+        try:
130+            session_id = self._request.COOKIES[settings.SESSION_COOKIE_NAME]
131+            return md5_constructor(settings.SECRET_KEY + session_id).hexdigest()
132+        except KeyError:
133+            #if the user does not have any cookies set,
134+            #a token cannot be generated
135+            return None
136+   
137+    def csrf_token_field(self):
138+        return mark_safe(unicode(self['_csrf_token']))
139\ No newline at end of file
140Index: AUTHORS
141===================================================================
142--- AUTHORS     (revision 9244)
143+++ AUTHORS     (working copy)
144@@ -425,6 +425,7 @@
145     ymasuda@ethercube.com
146     Jarek Zgoda <jarek.zgoda@gmail.com>
147     Cheng Zhang
148+    Elliott Mahler <join.together@gmail.com>
149 
150 A big THANK YOU goes to:
151