Ticket #10816: csrf-remove-session-dep-r10369.3.diff
File csrf-remove-session-dep-r10369.3.diff, 20.5 KB (added by , 16 years ago) |
---|
-
django/contrib/csrf/middleware.py
7 7 8 8 import re 9 9 import itertools 10 import random 10 11 try: 11 12 from functools import wraps 12 13 except ImportError: … … 24 25 25 26 _HTML_TYPES = ('text/html', 'application/xhtml+xml') 26 27 27 def _make_token(session_id): 28 # Use the system (hardware-based) random number generator if it exists. 29 if hasattr(random, 'SystemRandom'): 30 randrange = random.SystemRandom().randrange 31 else: 32 randrange = random.randrange 33 _MAX_CSRF_KEY = 18446744073709551616L # 2 << 63 34 35 def _get_new_csrf_key(): 36 return md5_constructor("%s%s" 37 % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest() 38 39 def _make_legacy_session_token(session_id): 28 40 return md5_constructor(settings.SECRET_KEY + session_id).hexdigest() 29 41 42 def _make_token(csrf_cookie): 43 return md5_constructor("csrf-" + settings.SECRET_KEY + csrf_cookie).hexdigest() 44 45 def _cookie_name(): 46 # Backwards-compatibility: derive the cookie name from SESSION_COOKIE_NAME, so if the 47 # user has specified a unique session cookie, we use a unique CSRF cookie, too. 48 return "csrf_%s" % settings.SESSION_COOKIE_NAME 49 50 def get_csrf_token(request): 51 """Return the CSRF token to be inserted into forms by CsrfResponseMiddleware.""" 52 csrf_cookie = request.META.get("CSRF_COOKIE", None) 53 assert csrf_cookie is not None, 'META["CSRF_COOKIE"] is not set.' 54 55 return _make_token(csrf_cookie) 56 30 57 class CsrfViewMiddleware(object): 31 58 """ 32 59 Middleware that requires a present and correct csrfmiddlewaretoken 33 for POST requests that have a n active session.60 for POST requests that have a CSRF cookie. 34 61 """ 35 62 def process_view(self, request, callback, callback_args, callback_kwargs): 63 if getattr(callback, 'csrf_exempt', False): 64 return None 65 66 # If the user doesn't have a CSRF cookie, generate one and store it in the 67 # request, so it's available to the view. We'll store it in a cookie when 68 # we reach the response. 69 try: 70 request.META["CSRF_COOKIE"] = request.COOKIES[_cookie_name()] 71 cookie_is_new = False 72 except KeyError: 73 # No cookie, so create one. 74 request.META["CSRF_COOKIE"] = _get_new_csrf_key() 75 cookie_is_new = True 76 36 77 if request.method == 'POST': 37 if getattr(callback, 'csrf_exempt', False):38 return None39 40 78 if request.is_ajax(): 41 79 return None 42 80 43 try: 44 session_id = request.COOKIES[settings.SESSION_COOKIE_NAME] 45 except KeyError: 46 # No session, no check required 47 return None 81 # If the user didn't already have a CSRF key, then accept the session key for the middleware 82 # token, so CSRF protection isn't lost for the period between upgrading to CSRF cookies to 83 # the first time each user comes back to the site to receive one. 84 if cookie_is_new: 85 try: 86 session_id = request.COOKIES[settings.SESSION_COOKIE_NAME] 87 csrf_token = _make_legacy_session_token(session_id) 88 except KeyError: 89 # No CSRF cookie and no session cookie; no check is performed. 90 return None 91 else: 92 csrf_cookie = request.META["CSRF_COOKIE"] 93 csrf_token = _make_token(csrf_cookie) 48 94 49 csrf_token = _make_token(session_id)50 95 # check incoming token 51 96 try: 52 97 request_csrf_token = request.POST['csrfmiddlewaretoken'] … … 60 105 61 106 class CsrfResponseMiddleware(object): 62 107 """ 63 Middleware that post-processes a response to add a 64 csrfmiddlewaretoken if the response/request have an active 65 session. 108 Middleware that post-processes a response to add a csrfmiddlewaretoken. 66 109 """ 67 110 def process_response(self, request, response): 68 111 if getattr(response, 'csrf_exempt', False): 69 112 return response 70 113 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 114 # If CSRF_COOKIE is unset, then CsrfViewMiddleware.process_view was never called, probably 115 # because a request middleware returned a response (for example, contrib.auth redirecting 116 # to a login page). 117 if request.META.get("CSRF_COOKIE") is None: 118 return response 89 119 90 if csrf_token is not None and \ 91 response['Content-Type'].split(';')[0] in _HTML_TYPES: 120 # Set the CSRF cookie even if it's already set, so we renew the expiry timer. 121 response.set_cookie(_cookie_name(), 122 request.META["CSRF_COOKIE"], max_age = 60 * 60 * 24 * 7 * 52, 123 domain=settings.SESSION_COOKIE_DOMAIN, 124 path=settings.SESSION_COOKIE_PATH) 92 125 126 if response['Content-Type'].split(';')[0] in _HTML_TYPES: 127 csrf_token = get_csrf_token(request) 128 93 129 # ensure we don't add the 'id' attribute twice (HTML validity) 94 130 idattributes = itertools.chain(("id='csrfmiddlewaretoken'",), 95 131 itertools.repeat('')) … … 109 145 Request Forgeries by adding hidden form fields to POST forms and 110 146 checking requests for the correct value. 111 147 112 In the list of middlewares, SessionMiddleware is required, and 113 must come after this middleware. CsrfMiddleWare must come after 114 compression middleware. 148 CsrfMiddleWare must come after compression middleware. 115 149 116 If a session ID cookie is present, itis hashed with the150 A persistent ID cookie is created. This cookie is hashed with the 117 151 SECRET_KEY setting to create an authentication token. This token 118 152 is added to all outgoing POST forms and is expected on all 119 incoming POST requests that have a sessionID cookie.153 incoming POST requests that have a CSRF ID cookie. 120 154 121 If you are setting cookies directly, instead of using Django's122 session framework, this middleware will not work.123 124 155 CsrfMiddleWare is composed of two middleware, CsrfViewMiddleware 125 156 and CsrfResponseMiddleware which can be used independently. 126 157 """ -
django/contrib/csrf/tests.py
2 2 3 3 from django.test import TestCase 4 4 from django.http import HttpRequest, HttpResponse, HttpResponseForbidden 5 from django.contrib.csrf.middleware import CsrfMiddleware, _make_token, csrf_exempt5 from django.contrib.csrf.middleware import CsrfMiddleware, _make_token, _cookie_name, csrf_exempt 6 6 from django.conf import settings 7 7 8 8 … … 17 17 18 18 class CsrfMiddlewareTest(TestCase): 19 19 20 _csrf_id = "1" 21 22 # This is a valid session token for this ID and secret key. This was generated using 23 # the old code that we're to be backwards-compatible with. Don't use the CSRF code 24 # to generate this hash, or we're merely testing the code against itself and not 25 # checking backwards-compatibility. This is also the output of (echo -n test1 | md5sum). 26 _session_token = "5a105e8b9d40e1329780d62ea2265d8a" 20 27 _session_id = "1" 28 _secret_key_for_session_test= "test" 21 29 22 def _get_GET_no_ session_request(self):30 def _get_GET_no_csrf_cookie_request(self): 23 31 return HttpRequest() 24 32 25 def _get_GET_ session_request(self):26 req = self._get_GET_no_ session_request()27 req.COOKIES[ settings.SESSION_COOKIE_NAME] = self._session_id33 def _get_GET_csrf_cookie_request(self): 34 req = self._get_GET_no_csrf_cookie_request() 35 req.COOKIES[_cookie_name()] = self._csrf_id 28 36 return req 29 37 30 def _get_POST_ session_request(self):31 req = self._get_GET_ session_request()38 def _get_POST_no_cookie_request(self): 39 req = self._get_GET_no_csrf_cookie_request() 32 40 req.method = "POST" 33 41 return req 34 42 35 def _get_POST_ no_session_request(self):36 req = self._get_GET_ no_session_request()43 def _get_POST_csrf_cookie_request(self): 44 req = self._get_GET_csrf_cookie_request() 37 45 req.method = "POST" 38 46 return req 39 47 48 def _get_POST_request_with_token(self): 49 req = self._get_POST_csrf_cookie_request() 50 req.POST['csrfmiddlewaretoken'] = _make_token(self._csrf_id) 51 return req 52 40 53 def _get_POST_session_request_with_token(self): 41 req = self._get_POST_session_request() 42 req.POST['csrfmiddlewaretoken'] = _make_token(self._session_id) 54 req = self._get_POST_no_cookie_request() 55 req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id 56 req.POST['csrfmiddlewaretoken'] = self._session_token 43 57 return req 44 58 59 def _get_POST_session_request_no_token(self): 60 req = self._get_POST_no_cookie_request() 61 req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id 62 return req 63 45 64 def _get_post_form_response(self): 46 65 return post_form_response() 47 66 48 def _get_ new_session_response(self):49 resp = self._get_post_form_response()50 resp .cookies[settings.SESSION_COOKIE_NAME] = self._session_id67 def _get_post_form_response_non_html(self): 68 resp = post_form_response() 69 resp["Content-Type"] = "application/xml" 51 70 return resp 52 71 53 def _check_token_present(self, response ):54 self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % _make_token( self._session_id))72 def _check_token_present(self, response, csrf_id=None): 73 self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % _make_token(csrf_id or self._csrf_id)) 55 74 56 75 def get_view(self): 57 76 return test_view 58 77 59 78 # Check the post processing 60 def test_process_response_ no_session(self):79 def test_process_response_existing_cookie(self): 61 80 """ 62 Check th e the post-processor does nothing if no session active81 Check that the token is inserted when a prior CSRF cookie exists. 63 82 """ 64 req = self._get_GET_no_session_request() 83 req = self._get_GET_csrf_cookie_request() 84 CsrfMiddleware().process_view(req, self.get_view(), (), {}) 85 65 86 resp = self._get_post_form_response() 66 87 resp_content = resp.content # needed because process_response modifies resp 67 88 resp2 = CsrfMiddleware().process_response(req, resp) 68 self.assertEquals(resp_content, resp2.content) 89 self.assertNotEqual(resp_content, resp2.content) 90 self._check_token_present(resp2) 69 91 70 def test_process_response_existing_ session(self):92 def test_process_response_existing_no_cookie(self): 71 93 """ 72 Check that the token is inserted if there is an existing session 94 When no prior CSRF cookie exists, check that the cookie is created and a 95 token is inserted. 73 96 """ 74 req = self._get_GET_session_request() 97 req = self._get_GET_no_csrf_cookie_request() 98 CsrfMiddleware().process_view(req, self.get_view(), (), {}) 99 75 100 resp = self._get_post_form_response() 76 101 resp_content = resp.content # needed because process_response modifies resp 77 102 resp2 = CsrfMiddleware().process_response(req, resp) 103 104 csrf_id = resp2.cookies.get(_cookie_name(), False) 105 self.assertNotEqual(csrf_id, False) 78 106 self.assertNotEqual(resp_content, resp2.content) 79 self._check_token_present(resp2 )107 self._check_token_present(resp2, csrf_id.value) 80 108 81 def test_process_response_n ew_session(self):109 def test_process_response_no_cookie(self): 82 110 """ 83 Check th at the token is inserted if there is a new session being started111 Check the the post-processor does nothing for content-types not in _HTML_TYPES. 84 112 """ 85 req = self._get_GET_no_session_request() # no session in request 86 resp = self._get_new_session_response() # but new session started 113 req = self._get_GET_no_csrf_cookie_request() 114 CsrfMiddleware().process_view(req, self.get_view(), (), {}) 115 resp = self._get_post_form_response_non_html() 87 116 resp_content = resp.content # needed because process_response modifies resp 88 117 resp2 = CsrfMiddleware().process_response(req, resp) 89 self.assertNotEqual(resp_content, resp2.content) 90 self._check_token_present(resp2) 118 self.assertEquals(resp_content, resp2.content) 91 119 92 120 def test_process_response_exempt_view(self): 93 121 """ 94 122 Check that no post processing is done for an exempt view 95 123 """ 96 req = self._get_POST_ session_request()124 req = self._get_POST_csrf_cookie_request() 97 125 resp = csrf_exempt(self.get_view())(req) 98 126 resp_content = resp.content 99 127 resp2 = CsrfMiddleware().process_response(req, resp) 100 128 self.assertEquals(resp_content, resp2.content) 101 129 102 130 # Check the request processing 103 def test_process_request_no_ session(self):131 def test_process_request_no_cookie_no_token(self): 104 132 """ 105 Check that if n o session is present, the middleware does nothing.106 to the incoming request.133 Check that if neither a CSRF cookie nor a session cookie are present, the 134 middleware does nothing to the incoming request. 107 135 """ 108 req = self._get_POST_no_ session_request()136 req = self._get_POST_no_cookie_request() 109 137 req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {}) 110 138 self.assertEquals(None, req2) 111 139 112 def test_process_request_ session_no_token(self):140 def test_process_request_cookie_no_token(self): 113 141 """ 114 Check that if a session is present but no token, we get a 'forbidden'142 Check that if a CSRF cookie is present but no token, we get a 'forbidden'. 115 143 """ 116 req = self._get_POST_ session_request()144 req = self._get_POST_csrf_cookie_request() 117 145 req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {}) 118 146 self.assertEquals(HttpResponseForbidden, req2.__class__) 119 147 120 def test_process_request_ session_and_token(self):148 def test_process_request_cookie_and_token(self): 121 149 """ 122 Check that if a session is present and a token, the middleware lets it through150 Check that if both a cookie and a token is present, the middleware lets it through. 123 151 """ 124 req = self._get_POST_ session_request_with_token()152 req = self._get_POST_request_with_token() 125 153 req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {}) 126 154 self.assertEquals(None, req2) 127 155 128 def test_process_request_session_ no_token_exempt_view(self):156 def test_process_request_session_cookie_no_csrf_cookie_token(self): 129 157 """ 130 Check that if a session is present and no token, but the csrf_exempt 158 When no CSRF cookie exists, but the user has a session, check that a token 159 using the session cookie as a legacy CSRF cookie is accepted. 160 """ 161 settings.SECRET_KEY = self._secret_key_for_session_test 162 try: 163 req = self._get_POST_session_request_with_token() 164 req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {}) 165 self.assertEquals(None, req2) 166 finally: 167 orig_secret_key = settings.SECRET_KEY 168 169 def test_process_request_session_cookie_no_csrf_cookie_no_token(self): 170 """ 171 Check that if a session cookie is present but no token and no CSRF cookie, 172 we get a 'forbidden'. 173 """ 174 req = self._get_POST_session_request_no_token() 175 req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {}) 176 self.assertEquals(HttpResponseForbidden, req2.__class__) 177 178 def test_process_request_cookie_no_token_exempt_view(self): 179 """ 180 Check that if a cookie is present and no token, but the csrf_exempt 131 181 decorator has been applied to the view, the middleware lets it through 132 182 """ 133 req = self._get_POST_ session_request()183 req = self._get_POST_csrf_cookie_request() 134 184 req2 = CsrfMiddleware().process_view(req, csrf_exempt(self.get_view()), (), {}) 135 185 self.assertEquals(None, req2) 136 186 … … 138 188 """ 139 189 Check that AJAX requests are automatically exempted. 140 190 """ 141 req = self._get_POST_ session_request()191 req = self._get_POST_csrf_cookie_request() 142 192 req.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' 143 193 req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {}) 144 194 self.assertEquals(None, req2) -
docs/ref/contrib/csrf.txt
23 23 ============= 24 24 25 25 Add the middleware ``'django.contrib.csrf.middleware.CsrfMiddleware'`` to 26 your list of middleware classes, :setting:`MIDDLEWARE_CLASSES`. It needs to process 27 the response after the SessionMiddleware, so must come before it in the 28 list. It also must process the response before things like compression 29 happen to the response, so it must come after GZipMiddleware in the 30 list. 26 your list of middleware classes, :setting:`MIDDLEWARE_CLASSES`. It must 27 process the response before things like compression happen to the response, 28 so it must come after GZipMiddleware in the list. 31 29 32 30 The ``CsrfMiddleware`` class is actually composed of two middleware: 33 31 ``CsrfViewMiddleware`` which performs the checks on incoming requests, … … 70 68 71 69 CsrfMiddleware does two things: 72 70 73 1. It modifies outgoing requests by adding a hidden form field to all 71 1. It stores a random number in a cookie. For backwards-compatibility 72 purposes, the cookie name is derived from the SESSION_COOKIE_NAME setting. 73 74 2. It modifies outgoing requests by adding a hidden form field to all 74 75 'POST' forms, with the name 'csrfmiddlewaretoken' and a value which is 75 a hash of the session ID plus a secret. If there is no session ID set, 76 this modification of the response isn't done, so there is very little 77 performance penalty for those requests that don't have a session. 78 (This is done by ``CsrfResponseMiddleware``). 76 a hash of the cookie value plus a secret. (This is done by 77 ``CsrfResponseMiddleware``). 79 78 80 2. On all incoming POST requests that have the session cookie set, it 81 checks that the 'csrfmiddlewaretoken' is present and correct. If it 82 isn't, the user will get a 403 error. (This is done by 83 ``CsrfViewMiddleware``) 79 3. On all incoming POST requests that have the cookie set, it checks that 80 the 'csrfmiddlewaretoken' is present and correct. If it isn't, the user 81 will get a 403 error. (This is done by ``CsrfViewMiddleware``) 84 82 85 83 This ensures that only forms that have originated from your Web site 86 84 can be used to POST data back. … … 90 88 effects (see `9.1.1 Safe Methods, HTTP 1.1, RFC 2616`_), and so a 91 89 CSRF attack with a GET request ought to be harmless. 92 90 93 POST requests that are not accompanied by a session cookie are not protected,94 but they do not need to be protected, since the 'attacking' Web site95 could make these kind of requests anyway.96 97 91 The Content-Type is checked before modifying the response, and only 98 92 pages that are served as 'text/html' or 'application/xml+xhtml' 99 93 are modified. … … 112 106 Limitations 113 107 =========== 114 108 115 CsrfMiddleware requires Django's session framework to work. If you have116 a custom authentication system that manually sets cookies and the like,117 it won't help you.118 119 109 If your app creates HTML pages and forms in some unusual way, (e.g. 120 110 it sends fragments of HTML in JavaScript document.write statements) 121 111 you might bypass the filter that adds the hidden field to the form,