1 """ |
1 from django.middleware.csrf import CsrfMiddleware, CsrfViewMiddleware, CsrfResponseMiddleware |
2 Cross Site Request Forgery Middleware. |
2 from django.views.decorators.csrf import csrf_exempt, csrf_view_exempt, csrf_response_exempt |
3 |
3 |
4 This module provides a middleware that implements protection |
4 import warnings |
5 against request forgeries from other sites. |
5 warnings.warn("This import for CSRF functionality is deprecated. Please use django.middleware.csrf for the middleware and django.views.decorators.csrf for decorators.", |
6 """ |
6 PendingDeprecationWarning |
7 |
7 ) |
8 import re |
|
9 import itertools |
|
10 try: |
|
11 from functools import wraps |
|
12 except ImportError: |
|
13 from django.utils.functional import wraps # Python 2.3, 2.4 fallback. |
|
14 |
|
15 from django.conf import settings |
|
16 from django.http import HttpResponseForbidden |
|
17 from django.utils.hashcompat import md5_constructor |
|
18 from django.utils.safestring import mark_safe |
|
19 |
|
20 _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>') |
|
21 |
|
22 _POST_FORM_RE = \ |
|
23 re.compile(r'(<form\W[^>]*\bmethod\s*=\s*(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE) |
|
24 |
|
25 _HTML_TYPES = ('text/html', 'application/xhtml+xml') |
|
26 |
|
27 def _make_token(session_id): |
|
28 return md5_constructor(settings.SECRET_KEY + session_id).hexdigest() |
|
29 |
|
30 class CsrfViewMiddleware(object): |
|
31 """ |
|
32 Middleware that requires a present and correct csrfmiddlewaretoken |
|
33 for POST requests that have an active session. |
|
34 """ |
|
35 def process_view(self, request, callback, callback_args, callback_kwargs): |
|
36 if request.method == 'POST': |
|
37 if getattr(callback, 'csrf_exempt', False): |
|
38 return None |
|
39 |
|
40 if request.is_ajax(): |
|
41 return None |
|
42 |
|
43 try: |
|
44 session_id = request.COOKIES[settings.SESSION_COOKIE_NAME] |
|
45 except KeyError: |
|
46 # No session, no check required |
|
47 return None |
|
48 |
|
49 csrf_token = _make_token(session_id) |
|
50 # check incoming token |
|
51 try: |
|
52 request_csrf_token = request.POST['csrfmiddlewaretoken'] |
|
53 except KeyError: |
|
54 return HttpResponseForbidden(_ERROR_MSG) |
|
55 |
|
56 if request_csrf_token != csrf_token: |
|
57 return HttpResponseForbidden(_ERROR_MSG) |
|
58 |
|
59 return None |
|
60 |
|
61 class CsrfResponseMiddleware(object): |
|
62 """ |
|
63 Middleware that post-processes a response to add a |
|
64 csrfmiddlewaretoken if the response/request have an active |
|
65 session. |
|
66 """ |
|
67 def process_response(self, request, response): |
|
68 if getattr(response, 'csrf_exempt', False): |
|
69 return response |
|
70 |
|
71 csrf_token = None |
|
72 try: |
|
73 # This covers a corner case in which the outgoing response |
|
74 # both contains a form and sets a session cookie. This |
|
75 # really should not be needed, since it is best if views |
|
76 # that create a new session (login pages) also do a |
|
77 # redirect, as is done by all such view functions in |
|
78 # Django. |
|
79 cookie = response.cookies[settings.SESSION_COOKIE_NAME] |
|
80 csrf_token = _make_token(cookie.value) |
|
81 except KeyError: |
|
82 # Normal case - look for existing session cookie |
|
83 try: |
|
84 session_id = request.COOKIES[settings.SESSION_COOKIE_NAME] |
|
85 csrf_token = _make_token(session_id) |
|
86 except KeyError: |
|
87 # no incoming or outgoing cookie |
|
88 pass |
|
89 |
|
90 if csrf_token is not None and \ |
|
91 response['Content-Type'].split(';')[0] in _HTML_TYPES: |
|
92 |
|
93 # ensure we don't add the 'id' attribute twice (HTML validity) |
|
94 idattributes = itertools.chain(("id='csrfmiddlewaretoken'",), |
|
95 itertools.repeat('')) |
|
96 def add_csrf_field(match): |
|
97 """Returns the matched <form> tag plus the added <input> element""" |
|
98 return mark_safe(match.group() + "<div style='display:none;'>" + \ |
|
99 "<input type='hidden' " + idattributes.next() + \ |
|
100 " name='csrfmiddlewaretoken' value='" + csrf_token + \ |
|
101 "' /></div>") |
|
102 |
|
103 # Modify any POST forms |
|
104 response.content = _POST_FORM_RE.sub(add_csrf_field, response.content) |
|
105 return response |
|
106 |
|
107 class CsrfMiddleware(CsrfViewMiddleware, CsrfResponseMiddleware): |
|
108 """Django middleware that adds protection against Cross Site |
|
109 Request Forgeries by adding hidden form fields to POST forms and |
|
110 checking requests for the correct value. |
|
111 |
|
112 In the list of middlewares, SessionMiddleware is required, and |
|
113 must come after this middleware. CsrfMiddleWare must come after |
|
114 compression middleware. |
|
115 |
|
116 If a session ID cookie is present, it is hashed with the |
|
117 SECRET_KEY setting to create an authentication token. This token |
|
118 is added to all outgoing POST forms and is expected on all |
|
119 incoming POST requests that have a session ID cookie. |
|
120 |
|
121 If you are setting cookies directly, instead of using Django's |
|
122 session framework, this middleware will not work. |
|
123 |
|
124 CsrfMiddleWare is composed of two middleware, CsrfViewMiddleware |
|
125 and CsrfResponseMiddleware which can be used independently. |
|
126 """ |
|
127 pass |
|
128 |
|
129 def csrf_response_exempt(view_func): |
|
130 """ |
|
131 Modifies a view function so that its response is exempt |
|
132 from the post-processing of the CSRF middleware. |
|
133 """ |
|
134 def wrapped_view(*args, **kwargs): |
|
135 resp = view_func(*args, **kwargs) |
|
136 resp.csrf_exempt = True |
|
137 return resp |
|
138 return wraps(view_func)(wrapped_view) |
|
139 |
|
140 def csrf_view_exempt(view_func): |
|
141 """ |
|
142 Marks a view function as being exempt from CSRF view protection. |
|
143 """ |
|
144 # We could just do view_func.csrf_exempt = True, but decorators |
|
145 # are nicer if they don't have side-effects, so we return a new |
|
146 # function. |
|
147 def wrapped_view(*args, **kwargs): |
|
148 return view_func(*args, **kwargs) |
|
149 wrapped_view.csrf_exempt = True |
|
150 return wraps(view_func)(wrapped_view) |
|
151 |
|
152 def csrf_exempt(view_func): |
|
153 """ |
|
154 Marks a view function as being exempt from the CSRF checks |
|
155 and post processing. |
|
156 |
|
157 This is the same as using both the csrf_view_exempt and |
|
158 csrf_response_exempt decorators. |
|
159 """ |
|
160 return csrf_response_exempt(csrf_view_exempt(view_func)) |
|