|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 from django.test import TestCase |
|
4 from django.http import HttpRequest, HttpResponse |
|
5 from django.middleware.csrf import CsrfMiddleware, CsrfViewMiddleware |
|
6 from django.views.decorators.csrf import csrf_exempt, csrf_view_exempt, requires_csrf_token |
|
7 from django.core.context_processors import csrf |
|
8 from django.contrib.sessions.middleware import SessionMiddleware |
|
9 from django.utils.importlib import import_module |
|
10 from django.conf import settings |
|
11 from django.template import RequestContext, Template |
|
12 |
|
13 # Response/views used for CsrfResponseMiddleware and CsrfViewMiddleware tests |
|
14 def post_form_response(): |
|
15 resp = HttpResponse(content=u""" |
|
16 <html><body><h1>\u00a1Unicode!<form method="post"><input type="text" /></form></body></html> |
|
17 """, mimetype="text/html") |
|
18 return resp |
|
19 |
|
20 def post_form_response_non_html(): |
|
21 resp = post_form_response() |
|
22 resp["Content-Type"] = "application/xml" |
|
23 return resp |
|
24 |
|
25 def post_form_view(request): |
|
26 """A view that returns a POST form (without a token)""" |
|
27 return post_form_response() |
|
28 |
|
29 # Response/views used for template tag tests |
|
30 def _token_template(): |
|
31 return Template("{% csrf_token %}") |
|
32 |
|
33 def _render_csrf_token_template(req): |
|
34 context = RequestContext(req, processors=[csrf]) |
|
35 template = _token_template() |
|
36 return template.render(context) |
|
37 |
|
38 def token_view(request): |
|
39 """A view that uses {% csrf_token %}""" |
|
40 return HttpResponse(_render_csrf_token_template(request)) |
|
41 |
|
42 def non_token_view_using_request_processor(request): |
|
43 """ |
|
44 A view that doesn't use the token, but does use the csrf view processor. |
|
45 """ |
|
46 context = RequestContext(request, processors=[csrf]) |
|
47 template = Template("") |
|
48 return HttpResponse(template.render(context)) |
|
49 |
|
50 class TestingHttpRequest(HttpRequest): |
|
51 """ |
|
52 A version of HttpRequest that allows us to change some things |
|
53 more easily |
|
54 """ |
|
55 def is_secure(self): |
|
56 return getattr(self, '_is_secure', False) |
|
57 |
|
58 class CsrfMiddlewareTest(TestCase): |
|
59 # The csrf token is potentially from an untrusted source, so could have |
|
60 # characters that need dealing with. |
|
61 _csrf_id_cookie = "<1>\xc2\xa1" |
|
62 _csrf_id = "1" |
|
63 |
|
64 # This is a valid session token for this ID and secret key. This was generated using |
|
65 # the old code that we're to be backwards-compatible with. Don't use the CSRF code |
|
66 # to generate this hash, or we're merely testing the code against itself and not |
|
67 # checking backwards-compatibility. This is also the output of (echo -n test1 | md5sum). |
|
68 _session_token = "5a105e8b9d40e1329780d62ea2265d8a" |
|
69 _session_id = "1" |
|
70 _secret_key_for_session_test= "test" |
|
71 |
|
72 def _get_GET_no_csrf_cookie_request(self): |
|
73 return TestingHttpRequest() |
|
74 |
|
75 def _get_GET_csrf_cookie_request(self): |
|
76 req = TestingHttpRequest() |
|
77 req.COOKIES[settings.CSRF_COOKIE_NAME] = self._csrf_id_cookie |
|
78 return req |
|
79 |
|
80 def _get_POST_csrf_cookie_request(self): |
|
81 req = self._get_GET_csrf_cookie_request() |
|
82 req.method = "POST" |
|
83 return req |
|
84 |
|
85 def _get_POST_no_csrf_cookie_request(self): |
|
86 req = self._get_GET_no_csrf_cookie_request() |
|
87 req.method = "POST" |
|
88 return req |
|
89 |
|
90 def _get_POST_request_with_token(self): |
|
91 req = self._get_POST_csrf_cookie_request() |
|
92 req.POST['csrfmiddlewaretoken'] = self._csrf_id |
|
93 return req |
|
94 |
|
95 def _get_POST_session_request_with_token(self): |
|
96 req = self._get_POST_no_csrf_cookie_request() |
|
97 req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id |
|
98 req.POST['csrfmiddlewaretoken'] = self._session_token |
|
99 return req |
|
100 |
|
101 def _get_POST_session_request_no_token(self): |
|
102 req = self._get_POST_no_csrf_cookie_request() |
|
103 req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id |
|
104 return req |
|
105 |
|
106 def _check_token_present(self, response, csrf_id=None): |
|
107 self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % (csrf_id or self._csrf_id)) |
|
108 |
|
109 # Check the post processing and outgoing cookie |
|
110 def test_process_response_no_csrf_cookie(self): |
|
111 """ |
|
112 When no prior CSRF cookie exists, check that the cookie is created and a |
|
113 token is inserted. |
|
114 """ |
|
115 req = self._get_GET_no_csrf_cookie_request() |
|
116 CsrfMiddleware().process_view(req, post_form_view, (), {}) |
|
117 |
|
118 resp = post_form_response() |
|
119 resp_content = resp.content # needed because process_response modifies resp |
|
120 resp2 = CsrfMiddleware().process_response(req, resp) |
|
121 |
|
122 csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False) |
|
123 self.assertNotEqual(csrf_cookie, False) |
|
124 self.assertNotEqual(resp_content, resp2.content) |
|
125 self._check_token_present(resp2, csrf_cookie.value) |
|
126 # Check the Vary header got patched correctly |
|
127 self.assert_('Cookie' in resp2.get('Vary','')) |
|
128 |
|
129 def test_process_response_for_exempt_view(self): |
|
130 """ |
|
131 Check that a view decorated with 'csrf_view_exempt' is still |
|
132 post-processed to add the CSRF token. |
|
133 """ |
|
134 req = self._get_GET_no_csrf_cookie_request() |
|
135 CsrfMiddleware().process_view(req, csrf_view_exempt(post_form_view), (), {}) |
|
136 |
|
137 resp = post_form_response() |
|
138 resp_content = resp.content # needed because process_response modifies resp |
|
139 resp2 = CsrfMiddleware().process_response(req, resp) |
|
140 |
|
141 csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False) |
|
142 self.assertNotEqual(csrf_cookie, False) |
|
143 self.assertNotEqual(resp_content, resp2.content) |
|
144 self._check_token_present(resp2, csrf_cookie.value) |
|
145 |
|
146 def test_process_response_no_csrf_cookie_view_only_get_token_used(self): |
|
147 """ |
|
148 When no prior CSRF cookie exists, check that the cookie is created, even |
|
149 if only CsrfViewMiddleware is used. |
|
150 """ |
|
151 # This is checking that CsrfViewMiddleware has the cookie setting |
|
152 # code. Most of the other tests use CsrfMiddleware. |
|
153 req = self._get_GET_no_csrf_cookie_request() |
|
154 # token_view calls get_token() indirectly |
|
155 CsrfViewMiddleware().process_view(req, token_view, (), {}) |
|
156 resp = token_view(req) |
|
157 resp2 = CsrfViewMiddleware().process_response(req, resp) |
|
158 |
|
159 csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False) |
|
160 self.assertNotEqual(csrf_cookie, False) |
|
161 |
|
162 def test_process_response_get_token_not_used(self): |
|
163 """ |
|
164 Check that if get_token() is not called, the view middleware does not |
|
165 add a cookie. |
|
166 """ |
|
167 # This is important to make pages cacheable. Pages which do call |
|
168 # get_token(), assuming they use the token, are not cacheable because |
|
169 # the token is specific to the user |
|
170 req = self._get_GET_no_csrf_cookie_request() |
|
171 # non_token_view_using_request_processor does not call get_token(), but |
|
172 # does use the csrf request processor. By using this, we are testing |
|
173 # that the view processor is properly lazy and doesn't call get_token() |
|
174 # until needed. |
|
175 CsrfViewMiddleware().process_view(req, non_token_view_using_request_processor, (), {}) |
|
176 resp = non_token_view_using_request_processor(req) |
|
177 resp2 = CsrfViewMiddleware().process_response(req, resp) |
|
178 |
|
179 csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False) |
|
180 self.assertEqual(csrf_cookie, False) |
|
181 |
|
182 def test_process_response_existing_csrf_cookie(self): |
|
183 """ |
|
184 Check that the token is inserted when a prior CSRF cookie exists |
|
185 """ |
|
186 req = self._get_GET_csrf_cookie_request() |
|
187 CsrfMiddleware().process_view(req, post_form_view, (), {}) |
|
188 |
|
189 resp = post_form_response() |
|
190 resp_content = resp.content # needed because process_response modifies resp |
|
191 resp2 = CsrfMiddleware().process_response(req, resp) |
|
192 self.assertNotEqual(resp_content, resp2.content) |
|
193 self._check_token_present(resp2) |
|
194 |
|
195 def test_process_response_non_html(self): |
|
196 """ |
|
197 Check the the post-processor does nothing for content-types not in _HTML_TYPES. |
|
198 """ |
|
199 req = self._get_GET_no_csrf_cookie_request() |
|
200 CsrfMiddleware().process_view(req, post_form_view, (), {}) |
|
201 resp = post_form_response_non_html() |
|
202 resp_content = resp.content # needed because process_response modifies resp |
|
203 resp2 = CsrfMiddleware().process_response(req, resp) |
|
204 self.assertEquals(resp_content, resp2.content) |
|
205 |
|
206 def test_process_response_exempt_view(self): |
|
207 """ |
|
208 Check that no post processing is done for an exempt view |
|
209 """ |
|
210 req = self._get_GET_csrf_cookie_request() |
|
211 view = csrf_exempt(post_form_view) |
|
212 CsrfMiddleware().process_view(req, view, (), {}) |
|
213 |
|
214 resp = view(req) |
|
215 resp_content = resp.content |
|
216 resp2 = CsrfMiddleware().process_response(req, resp) |
|
217 self.assertEquals(resp_content, resp2.content) |
|
218 |
|
219 # Check the request processing |
|
220 def test_process_request_no_session_no_csrf_cookie(self): |
|
221 """ |
|
222 Check that if neither a CSRF cookie nor a session cookie are present, |
|
223 the middleware rejects the incoming request. This will stop login CSRF. |
|
224 """ |
|
225 req = self._get_POST_no_csrf_cookie_request() |
|
226 req2 = CsrfMiddleware().process_view(req, post_form_view, (), {}) |
|
227 self.assertEquals(403, req2.status_code) |
|
228 |
|
229 def test_process_request_csrf_cookie_no_token(self): |
|
230 """ |
|
231 Check that if a CSRF cookie is present but no token, the middleware |
|
232 rejects the incoming request. |
|
233 """ |
|
234 req = self._get_POST_csrf_cookie_request() |
|
235 req2 = CsrfMiddleware().process_view(req, post_form_view, (), {}) |
|
236 self.assertEquals(403, req2.status_code) |
|
237 |
|
238 def test_process_request_csrf_cookie_and_token(self): |
|
239 """ |
|
240 Check that if both a cookie and a token is present, the middleware lets it through. |
|
241 """ |
|
242 req = self._get_POST_request_with_token() |
|
243 req2 = CsrfMiddleware().process_view(req, post_form_view, (), {}) |
|
244 self.assertEquals(None, req2) |
|
245 |
|
246 def test_process_request_session_cookie_no_csrf_cookie_token(self): |
|
247 """ |
|
248 When no CSRF cookie exists, but the user has a session, check that a token |
|
249 using the session cookie as a legacy CSRF cookie is accepted. |
|
250 """ |
|
251 orig_secret_key = settings.SECRET_KEY |
|
252 settings.SECRET_KEY = self._secret_key_for_session_test |
|
253 try: |
|
254 req = self._get_POST_session_request_with_token() |
|
255 req2 = CsrfMiddleware().process_view(req, post_form_view, (), {}) |
|
256 self.assertEquals(None, req2) |
|
257 finally: |
|
258 settings.SECRET_KEY = orig_secret_key |
|
259 |
|
260 def test_process_request_session_cookie_no_csrf_cookie_no_token(self): |
|
261 """ |
|
262 Check that if a session cookie is present but no token and no CSRF cookie, |
|
263 the request is rejected. |
|
264 """ |
|
265 req = self._get_POST_session_request_no_token() |
|
266 req2 = CsrfMiddleware().process_view(req, post_form_view, (), {}) |
|
267 self.assertEquals(403, req2.status_code) |
|
268 |
|
269 def test_process_request_csrf_cookie_no_token_exempt_view(self): |
|
270 """ |
|
271 Check that if a CSRF cookie is present and no token, but the csrf_exempt |
|
272 decorator has been applied to the view, the middleware lets it through |
|
273 """ |
|
274 req = self._get_POST_csrf_cookie_request() |
|
275 req2 = CsrfMiddleware().process_view(req, csrf_exempt(post_form_view), (), {}) |
|
276 self.assertEquals(None, req2) |
|
277 |
|
278 def test_ajax_exemption(self): |
|
279 """ |
|
280 Check that AJAX requests are automatically exempted. |
|
281 """ |
|
282 req = self._get_POST_csrf_cookie_request() |
|
283 req.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' |
|
284 req2 = CsrfMiddleware().process_view(req, post_form_view, (), {}) |
|
285 self.assertEquals(None, req2) |
|
286 |
|
287 # Tests for the template tag method |
|
288 def test_token_node_no_csrf_cookie(self): |
|
289 """ |
|
290 Check that CsrfTokenNode works when no CSRF cookie is set |
|
291 """ |
|
292 req = self._get_GET_no_csrf_cookie_request() |
|
293 resp = token_view(req) |
|
294 self.assertEquals(u"", resp.content) |
|
295 |
|
296 def test_token_node_empty_csrf_cookie(self): |
|
297 """ |
|
298 Check that we get a new token if the csrf_cookie is the empty string |
|
299 """ |
|
300 req = self._get_GET_no_csrf_cookie_request() |
|
301 req.COOKIES[settings.CSRF_COOKIE_NAME] = "" |
|
302 CsrfViewMiddleware().process_view(req, token_view, (), {}) |
|
303 resp = token_view(req) |
|
304 |
|
305 self.assertNotEqual(u"", resp.content) |
|
306 |
|
307 def test_token_node_with_csrf_cookie(self): |
|
308 """ |
|
309 Check that CsrfTokenNode works when a CSRF cookie is set |
|
310 """ |
|
311 req = self._get_GET_csrf_cookie_request() |
|
312 CsrfViewMiddleware().process_view(req, token_view, (), {}) |
|
313 resp = token_view(req) |
|
314 self._check_token_present(resp) |
|
315 |
|
316 def test_get_token_for_exempt_view(self): |
|
317 """ |
|
318 Check that get_token still works for a view decorated with 'csrf_view_exempt'. |
|
319 """ |
|
320 req = self._get_GET_csrf_cookie_request() |
|
321 CsrfViewMiddleware().process_view(req, csrf_view_exempt(token_view), (), {}) |
|
322 resp = token_view(req) |
|
323 self._check_token_present(resp) |
|
324 |
|
325 def test_get_token_for_requires_csrf_token_view(self): |
|
326 """ |
|
327 Check that get_token works for a view decorated solely with requires_csrf_token |
|
328 """ |
|
329 req = self._get_GET_csrf_cookie_request() |
|
330 resp = requires_csrf_token(token_view)(req) |
|
331 self._check_token_present(resp) |
|
332 |
|
333 def test_token_node_with_new_csrf_cookie(self): |
|
334 """ |
|
335 Check that CsrfTokenNode works when a CSRF cookie is created by |
|
336 the middleware (when one was not already present) |
|
337 """ |
|
338 req = self._get_GET_no_csrf_cookie_request() |
|
339 CsrfViewMiddleware().process_view(req, token_view, (), {}) |
|
340 resp = token_view(req) |
|
341 resp2 = CsrfViewMiddleware().process_response(req, resp) |
|
342 csrf_cookie = resp2.cookies[settings.CSRF_COOKIE_NAME] |
|
343 self._check_token_present(resp, csrf_id=csrf_cookie.value) |
|
344 |
|
345 def test_response_middleware_without_view_middleware(self): |
|
346 """ |
|
347 Check that CsrfResponseMiddleware finishes without error if the view middleware |
|
348 has not been called, as is the case if a request middleware returns a response. |
|
349 """ |
|
350 req = self._get_GET_no_csrf_cookie_request() |
|
351 resp = post_form_view(req) |
|
352 CsrfMiddleware().process_response(req, resp) |
|
353 |
|
354 def test_https_bad_referer(self): |
|
355 """ |
|
356 Test that a POST HTTPS request with a bad referer is rejected |
|
357 """ |
|
358 req = self._get_POST_request_with_token() |
|
359 req._is_secure = True |
|
360 req.META['HTTP_HOST'] = 'www.example.com' |
|
361 req.META['HTTP_REFERER'] = 'https://www.evil.org/somepage' |
|
362 req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) |
|
363 self.assertNotEqual(None, req2) |
|
364 self.assertEquals(403, req2.status_code) |
|
365 |
|
366 def test_https_good_referer(self): |
|
367 """ |
|
368 Test that a POST HTTPS request with a good referer is accepted |
|
369 """ |
|
370 req = self._get_POST_request_with_token() |
|
371 req._is_secure = True |
|
372 req.META['HTTP_HOST'] = 'www.example.com' |
|
373 req.META['HTTP_REFERER'] = 'https://www.example.com/somepage' |
|
374 req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) |
|
375 self.assertEquals(None, req2) |