Ticket #10816: csrf-remove-session-dep-r10369.diff
File csrf-remove-session-dep-r10369.diff, 19.4 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 return md5_constructor(settings.SECRET_KEY + session_id).hexdigest() 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 29 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_token(csrf_cookie): 40 return md5_constructor(settings.SECRET_KEY + csrf_cookie).hexdigest() 41 42 def _cookie_name(): 43 # Backwards-compatibility: derive the cookie name from SESSION_COOKIE_NAME, so if the 44 # user has specified a unique session cookie, we use a unique CSRF cookie, too. 45 return "csrf_%s" % settings.SESSION_COOKIE_NAME 46 47 def get_csrf_token(request): 48 """Return the CSRF token to be inserted into forms by CsrfResponseMiddleware.""" 49 csrf_cookie = request.META.get("CSRF_COOKIE", None) 50 assert csrf_cookie is not None, 'META["CSRF_COOKIE"] is not set.' 51 52 return _make_token(csrf_cookie) 53 30 54 class CsrfViewMiddleware(object): 31 55 """ 32 56 Middleware that requires a present and correct csrfmiddlewaretoken 33 for POST requests that have a n active session.57 for POST requests that have a CSRF cookie. 34 58 """ 35 59 def process_view(self, request, callback, callback_args, callback_kwargs): 60 if getattr(callback, 'csrf_exempt', False): 61 return None 62 63 # If the user doesn't have a CSRF cookie, generate one and store it in the 64 # request, so it's available to the view. We'll store it in a cookie when 65 # we reach the response. 66 try: 67 request.META["CSRF_COOKIE"] = request.COOKIES[_cookie_name()] 68 cookie_is_new = False 69 except KeyError: 70 # No cookie, so create one. 71 request.META["CSRF_COOKIE"] = _get_new_csrf_key() 72 cookie_is_new = True 73 36 74 if request.method == 'POST': 37 if getattr(callback, 'csrf_exempt', False):38 return None39 40 75 if request.is_ajax(): 41 76 return None 42 77 43 try: 44 session_id = request.COOKIES[settings.SESSION_COOKIE_NAME] 45 except KeyError: 46 # No session, no check required 47 return None 78 # If the user didn't already have a CSRF key, then accept the session key for the middleware 79 # token, so CSRF protection isn't lost for the period between upgrading to CSRF cookies to 80 # the first time each user comes back to the site to receive one. 81 if cookie_is_new: 82 try: 83 csrf_cookie = request.COOKIES[settings.SESSION_COOKIE_NAME] 84 except KeyError: 85 # No CSRF cookie and no session cookie; no check is performed. 86 return None 87 else: 88 csrf_cookie = request.META["CSRF_COOKIE"] 48 89 49 csrf_token = _make_token( session_id)90 csrf_token = _make_token(csrf_cookie) 50 91 # check incoming token 51 92 try: 52 93 request_csrf_token = request.POST['csrfmiddlewaretoken'] … … 60 101 61 102 class CsrfResponseMiddleware(object): 62 103 """ 63 Middleware that post-processes a response to add a 64 csrfmiddlewaretoken if the response/request have an active 65 session. 104 Middleware that post-processes a response to add a csrfmiddlewaretoken. 66 105 """ 67 106 def process_response(self, request, response): 68 107 if getattr(response, 'csrf_exempt', False): 69 108 return response 70 109 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 110 # Set the CSRF cookie even if it's already set, so we renew the expiry timer. 111 response.set_cookie(_cookie_name(), 112 request.META["CSRF_COOKIE"], max_age = 60 * 60 * 24 * 7 * 52, 113 domain=settings.SESSION_COOKIE_DOMAIN, 114 path=settings.SESSION_COOKIE_PATH) 89 115 90 if csrf_token is not None and \91 response['Content-Type'].split(';')[0] in _HTML_TYPES:116 if response['Content-Type'].split(';')[0] in _HTML_TYPES: 117 csrf_token = get_csrf_token(request) 92 118 93 119 # ensure we don't add the 'id' attribute twice (HTML validity) 94 120 idattributes = itertools.chain(("id='csrfmiddlewaretoken'",), … … 109 135 Request Forgeries by adding hidden form fields to POST forms and 110 136 checking requests for the correct value. 111 137 112 In the list of middlewares, SessionMiddleware is required, and 113 must come after this middleware. CsrfMiddleWare must come after 114 compression middleware. 138 CsrfMiddleWare must come after compression middleware. 115 139 116 If a session ID cookie is present, itis hashed with the140 A persistent ID cookie is created. This cookie is hashed with the 117 141 SECRET_KEY setting to create an authentication token. This token 118 142 is added to all outgoing POST forms and is expected on all 119 incoming POST requests that have a sessionID cookie.143 incoming POST requests that have a CSRF ID cookie. 120 144 121 If you are setting cookies directly, instead of using Django's122 session framework, this middleware will not work.123 124 145 CsrfMiddleWare is composed of two middleware, CsrfViewMiddleware 125 146 and CsrfResponseMiddleware which can be used independently. 126 147 """ -
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 _ session_id = "1"20 _csrf_id = "1" 21 21 22 def _get_GET_no_ session_request(self):22 def _get_GET_no_csrf_cookie_request(self): 23 23 return HttpRequest() 24 24 25 def _get_GET_ session_request(self):26 req = self._get_GET_no_ session_request()27 req.COOKIES[ settings.SESSION_COOKIE_NAME] = self._session_id25 def _get_GET_csrf_cookie_request(self): 26 req = self._get_GET_no_csrf_cookie_request() 27 req.COOKIES[_cookie_name()] = self._csrf_id 28 28 return req 29 29 30 def _get_POST_ session_request(self):31 req = self._get_GET_ session_request()30 def _get_POST_no_cookie_request(self): 31 req = self._get_GET_no_csrf_cookie_request() 32 32 req.method = "POST" 33 33 return req 34 34 35 def _get_POST_ no_session_request(self):36 req = self._get_GET_ no_session_request()35 def _get_POST_csrf_cookie_request(self): 36 req = self._get_GET_csrf_cookie_request() 37 37 req.method = "POST" 38 38 return req 39 39 40 def _get_POST_request_with_token(self): 41 req = self._get_POST_csrf_cookie_request() 42 req.POST['csrfmiddlewaretoken'] = _make_token(self._csrf_id) 43 return req 44 40 45 def _get_POST_session_request_with_token(self): 41 req = self._get_POST_session_request() 42 req.POST['csrfmiddlewaretoken'] = _make_token(self._session_id) 46 req = self._get_POST_no_cookie_request() 47 req.COOKIES[settings.SESSION_COOKIE_NAME] = self._csrf_id 48 req.POST['csrfmiddlewaretoken'] = _make_token(self._csrf_id) 43 49 return req 44 50 51 def _get_POST_session_request_no_token(self): 52 req = self._get_POST_no_cookie_request() 53 req.COOKIES[settings.SESSION_COOKIE_NAME] = self._csrf_id 54 return req 55 45 56 def _get_post_form_response(self): 46 57 return post_form_response() 47 58 48 def _get_ new_session_response(self):49 resp = self._get_post_form_response()50 resp .cookies[settings.SESSION_COOKIE_NAME] = self._session_id59 def _get_post_form_response_non_html(self): 60 resp = post_form_response() 61 resp["Content-Type"] = "application/xml" 51 62 return resp 52 63 53 def _check_token_present(self, response ):54 self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % _make_token( self._session_id))64 def _check_token_present(self, response, csrf_id=None): 65 self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % _make_token(csrf_id or self._csrf_id)) 55 66 56 67 def get_view(self): 57 68 return test_view 58 69 59 70 # Check the post processing 60 def test_process_response_ no_session(self):71 def test_process_response_existing_cookie(self): 61 72 """ 62 Check th e the post-processor does nothing if no session active73 Check that the token is inserted when a prior CSRF cookie exists. 63 74 """ 64 req = self._get_GET_no_session_request() 75 req = self._get_GET_csrf_cookie_request() 76 CsrfMiddleware().process_view(req, self.get_view(), (), {}) 77 65 78 resp = self._get_post_form_response() 66 79 resp_content = resp.content # needed because process_response modifies resp 67 80 resp2 = CsrfMiddleware().process_response(req, resp) 68 self.assertEquals(resp_content, resp2.content) 81 self.assertNotEqual(resp_content, resp2.content) 82 self._check_token_present(resp2) 69 83 70 def test_process_response_existing_ session(self):84 def test_process_response_existing_no_cookie(self): 71 85 """ 72 Check that the token is inserted if there is an existing session 86 When no prior CSRF cookie exists, check that the cookie is created and a 87 token is inserted. 73 88 """ 74 req = self._get_GET_session_request() 89 req = self._get_GET_no_csrf_cookie_request() 90 CsrfMiddleware().process_view(req, self.get_view(), (), {}) 91 75 92 resp = self._get_post_form_response() 76 93 resp_content = resp.content # needed because process_response modifies resp 77 94 resp2 = CsrfMiddleware().process_response(req, resp) 95 96 csrf_id = resp2.cookies.get(_cookie_name(), False) 97 self.assertNotEqual(csrf_id, False) 78 98 self.assertNotEqual(resp_content, resp2.content) 79 self._check_token_present(resp2 )99 self._check_token_present(resp2, csrf_id.value) 80 100 81 def test_process_response_n ew_session(self):101 def test_process_response_no_cookie(self): 82 102 """ 83 Check th at the token is inserted if there is a new session being started103 Check the the post-processor does nothing for content-types not in _HTML_TYPES. 84 104 """ 85 req = self._get_GET_no_session_request() # no session in request 86 resp = self._get_new_session_response() # but new session started 105 req = self._get_GET_no_csrf_cookie_request() 106 CsrfMiddleware().process_view(req, self.get_view(), (), {}) 107 resp = self._get_post_form_response_non_html() 87 108 resp_content = resp.content # needed because process_response modifies resp 88 109 resp2 = CsrfMiddleware().process_response(req, resp) 89 self.assertNotEqual(resp_content, resp2.content) 90 self._check_token_present(resp2) 110 self.assertEquals(resp_content, resp2.content) 91 111 92 112 def test_process_response_exempt_view(self): 93 113 """ 94 114 Check that no post processing is done for an exempt view 95 115 """ 96 req = self._get_POST_ session_request()116 req = self._get_POST_csrf_cookie_request() 97 117 resp = csrf_exempt(self.get_view())(req) 98 118 resp_content = resp.content 99 119 resp2 = CsrfMiddleware().process_response(req, resp) 100 120 self.assertEquals(resp_content, resp2.content) 101 121 102 122 # Check the request processing 103 def test_process_request_ no_session(self):123 def test_process_request_session_cookie_no_csrf_cookie_token(self): 104 124 """ 105 Check that if no session is present, the middleware does nothing.106 to the incoming request.125 When no CSRF cookie exists, but the user has a session, check that a token 126 using the session cookie as the CSRF cookie is accepted. 107 127 """ 108 req = self._get_POST_ no_session_request()128 req = self._get_POST_session_request_with_token() 109 129 req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {}) 110 130 self.assertEquals(None, req2) 131 def test_process_request_session_cookie_no_csrf_cookie_no_token(self): 132 """ 133 When no CSRF cookie exists, but the user has a session, check that a token 134 using the session cookie as the CSRF cookie is accepted. 135 """ 136 req = self._get_POST_session_request_no_token() 137 req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {}) 138 self.assertEquals(HttpResponseForbidden, req2.__class__) 111 139 112 def test_process_request_ session_no_token(self):140 def test_process_request_no_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 no CSRF cookie is present, the middleware does nothing to 143 the incoming request. 115 144 """ 116 req = self._get_POST_ session_request()145 req = self._get_POST_no_cookie_request() 117 146 req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {}) 147 self.assertEquals(None, req2) 148 149 def test_process_request_cookie_no_token(self): 150 """ 151 Check that if a cookie is present but no token, we get a 'forbidden' 152 """ 153 req = self._get_POST_csrf_cookie_request() 154 req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {}) 118 155 self.assertEquals(HttpResponseForbidden, req2.__class__) 119 156 120 def test_process_request_ session_and_token(self):157 def test_process_request_cookie_and_token(self): 121 158 """ 122 Check that if a session is present and a token, the middleware lets it through159 Check that if both a cookie and a token is present, the middleware lets it through. 123 160 """ 124 req = self._get_POST_ session_request_with_token()161 req = self._get_POST_request_with_token() 125 162 req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {}) 126 163 self.assertEquals(None, req2) 127 164 128 def test_process_request_ session_no_token_exempt_view(self):165 def test_process_request_cookie_no_token_exempt_view(self): 129 166 """ 130 Check that if a sessionis present and no token, but the csrf_exempt167 Check that if a cookie is present and no token, but the csrf_exempt 131 168 decorator has been applied to the view, the middleware lets it through 132 169 """ 133 req = self._get_POST_ session_request()170 req = self._get_POST_csrf_cookie_request() 134 171 req2 = CsrfMiddleware().process_view(req, csrf_exempt(self.get_view()), (), {}) 135 172 self.assertEquals(None, req2) 136 173 … … 138 175 """ 139 176 Check that AJAX requests are automatically exempted. 140 177 """ 141 req = self._get_POST_ session_request()178 req = self._get_POST_csrf_cookie_request() 142 179 req.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' 143 180 req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {}) 144 181 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,