Django

Code

root/django/trunk/django/contrib/csrf/middleware.py

Revision 10617, 5.8 kB (checked in by lukeplant, 2 months ago)

Fixed #10884 - more lenient regexp for matching forms in CSRF post-processing

Thanks to Ryszard Szopa for the report and fix

  • Property svn:eol-style set to native
Line 
1 """
2 Cross Site Request Forgery Middleware.
3
4 This module provides a middleware that implements protection
5 against request forgeries from other sites.
6 """
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))
Note: See TracBrowser for help on using the browser.