1 """ |
1 """ |
2 Cross Site Request Forgery Middleware. |
2 Cross Site Request Forgery Middleware. |
3 |
3 |
4 This module provides a middleware that implements protection |
4 This module provides a middleware that implements protection |
5 against request forgeries from other sites. |
5 against request forgeries from other sites. |
|
6 """ |
6 |
7 |
7 """ |
8 import re |
|
9 import itertools |
|
10 |
8 from django.conf import settings |
11 from django.conf import settings |
9 from django.http import HttpResponseForbidden |
12 from django.http import HttpResponseForbidden |
|
13 from django.utils.hashcompat import md5_constructor |
10 from django.utils.safestring import mark_safe |
14 from django.utils.safestring import mark_safe |
11 import md5 |
|
12 import re |
|
13 import itertools |
|
14 |
15 |
15 _ERROR_MSG = mark_safe('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><body><h1>403 Forbidden</h1><p>Cross Site Request Forgery detected. Request aborted.</p></body></html>') |
16 _ERROR_MSG = mark_safe('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><body><h1>403 Forbidden</h1><p>Cross Site Request Forgery detected. Request aborted.</p></body></html>') |
16 |
17 |
17 _POST_FORM_RE = \ |
18 _POST_FORM_RE = \ |
18 re.compile(r'(<form\W[^>]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE) |
19 re.compile(r'(<form\W[^>]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE) |
19 |
20 |
20 _HTML_TYPES = ('text/html', 'application/xhtml+xml') |
21 _HTML_TYPES = ('text/html', 'application/xhtml+xml') |
21 |
22 |
22 def _make_token(session_id): |
23 def _make_token(session_id): |
23 return md5.new(settings.SECRET_KEY + session_id).hexdigest() |
24 return md5_constructor(settings.SECRET_KEY + session_id).hexdigest() |
24 |
25 |
25 class CsrfMiddleware(object): |
26 class CsrfMiddleware(object): |
26 """Django middleware that adds protection against Cross Site |
27 """Django middleware that adds protection against Cross Site |
27 Request Forgeries by adding hidden form fields to POST forms and |
28 Request Forgeries by adding hidden form fields to POST forms and |
28 checking requests for the correct value. |
29 checking requests for the correct value. |
29 |
30 |
30 In the list of middlewares, SessionMiddleware is required, and must come |
31 In the list of middlewares, SessionMiddleware is required, and must come |
31 after this middleware. CsrfMiddleWare must come after compression |
32 after this middleware. CsrfMiddleWare must come after compression |
32 middleware. |
33 middleware. |
33 |
34 |
34 If a session ID cookie is present, it is hashed with the SECRET_KEY |
35 If a session ID cookie is present, it is hashed with the SECRET_KEY |
35 setting to create an authentication token. This token is added to all |
36 setting to create an authentication token. This token is added to all |
36 outgoing POST forms and is expected on all incoming POST requests that |
37 outgoing POST forms and is expected on all incoming POST requests that |
37 have a session ID cookie. |
38 have a session ID cookie. |
38 |
39 |
39 If you are setting cookies directly, instead of using Django's session |
40 If you are setting cookies directly, instead of using Django's session |
40 framework, this middleware will not work. |
41 framework, this middleware will not work. |
41 """ |
42 """ |
42 |
43 |
43 def process_request(self, request): |
44 def process_request(self, request): |
44 if request.method == 'POST': |
45 if request.method == 'POST': |
45 try: |
46 try: |
46 session_id = request.COOKIES[settings.SESSION_COOKIE_NAME] |
47 session_id = request.COOKIES[settings.SESSION_COOKIE_NAME] |
47 except KeyError: |
48 except KeyError: |
52 # check incoming token |
53 # check incoming token |
53 try: |
54 try: |
54 request_csrf_token = request.POST['csrfmiddlewaretoken'] |
55 request_csrf_token = request.POST['csrfmiddlewaretoken'] |
55 except KeyError: |
56 except KeyError: |
56 return HttpResponseForbidden(_ERROR_MSG) |
57 return HttpResponseForbidden(_ERROR_MSG) |
57 |
58 |
58 if request_csrf_token != csrf_token: |
59 if request_csrf_token != csrf_token: |
59 return HttpResponseForbidden(_ERROR_MSG) |
60 return HttpResponseForbidden(_ERROR_MSG) |
60 |
61 |
61 return None |
62 return None |
62 |
63 |
63 def process_response(self, request, response): |
64 def process_response(self, request, response): |
64 csrf_token = None |
65 csrf_token = None |
65 try: |
66 try: |
66 cookie = response.cookies[settings.SESSION_COOKIE_NAME] |
67 cookie = response.cookies[settings.SESSION_COOKIE_NAME] |
67 csrf_token = _make_token(cookie.value) |
68 csrf_token = _make_token(cookie.value) |
68 except KeyError: |
69 except KeyError: |
69 # No outgoing cookie to set session, but |
70 # No outgoing cookie to set session, but |
70 # a session might already exist. |
71 # a session might already exist. |
71 try: |
72 try: |
72 session_id = request.COOKIES[settings.SESSION_COOKIE_NAME] |
73 session_id = request.COOKIES[settings.SESSION_COOKIE_NAME] |
73 csrf_token = _make_token(session_id) |
74 csrf_token = _make_token(session_id) |
74 except KeyError: |
75 except KeyError: |
75 # no incoming or outgoing cookie |
76 # no incoming or outgoing cookie |
76 pass |
77 pass |
77 |
78 |
78 if csrf_token is not None and \ |
79 if csrf_token is not None and \ |
79 response['Content-Type'].split(';')[0] in _HTML_TYPES: |
80 response['Content-Type'].split(';')[0] in _HTML_TYPES: |
80 |
81 |
81 # ensure we don't add the 'id' attribute twice (HTML validity) |
82 # ensure we don't add the 'id' attribute twice (HTML validity) |
82 idattributes = itertools.chain(("id='csrfmiddlewaretoken'",), |
83 idattributes = itertools.chain(("id='csrfmiddlewaretoken'",), |
83 itertools.repeat('')) |
84 itertools.repeat('')) |
84 def add_csrf_field(match): |
85 def add_csrf_field(match): |
85 """Returns the matched <form> tag plus the added <input> element""" |
86 """Returns the matched <form> tag plus the added <input> element""" |
86 return mark_safe(match.group() + "<div style='display:none;'>" + \ |
87 return mark_safe(match.group() + "<div style='display:none;'>" + \ |
87 "<input type='hidden' " + idattributes.next() + \ |
88 "<input type='hidden' " + idattributes.next() + \ |