You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
94 lines
3.6 KiB
94 lines
3.6 KiB
""" |
|
Cross Site Request Forgery Middleware. |
|
|
|
This module provides a middleware that implements protection |
|
against request forgeries from other sites. |
|
""" |
|
|
|
import re |
|
import itertools |
|
|
|
from django.conf import settings |
|
from django.http import HttpResponseForbidden |
|
from django.utils.hashcompat import md5_constructor |
|
from django.utils.safestring import mark_safe |
|
|
|
_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>') |
|
|
|
_POST_FORM_RE = \ |
|
re.compile(r'(<form\W[^>]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE) |
|
|
|
_HTML_TYPES = ('text/html', 'application/xhtml+xml') |
|
|
|
def _make_token(session_id): |
|
return md5_constructor(settings.SECRET_KEY + session_id).hexdigest() |
|
|
|
class CsrfMiddleware(object): |
|
"""Django middleware that adds protection against Cross Site |
|
Request Forgeries by adding hidden form fields to POST forms and |
|
checking requests for the correct value. |
|
|
|
In the list of middlewares, SessionMiddleware is required, and must come |
|
after this middleware. CsrfMiddleWare must come after compression |
|
middleware. |
|
|
|
If a session ID cookie is present, it is hashed with the SECRET_KEY |
|
setting to create an authentication token. This token is added to all |
|
outgoing POST forms and is expected on all incoming POST requests that |
|
have a session ID cookie. |
|
|
|
If you are setting cookies directly, instead of using Django's session |
|
framework, this middleware will not work. |
|
""" |
|
|
|
def process_request(self, request): |
|
if request.method == 'POST': |
|
try: |
|
session_id = request.COOKIES[settings.SESSION_COOKIE_NAME] |
|
except KeyError: |
|
# No session, no check required |
|
return None |
|
|
|
csrf_token = _make_token(session_id) |
|
# check incoming token |
|
try: |
|
request_csrf_token = request.POST['csrfmiddlewaretoken'] |
|
except KeyError: |
|
return HttpResponseForbidden(_ERROR_MSG) |
|
|
|
if request_csrf_token != csrf_token: |
|
return HttpResponseForbidden(_ERROR_MSG) |
|
|
|
return None |
|
|
|
def process_response(self, request, response): |
|
csrf_token = None |
|
try: |
|
cookie = response.cookies[settings.SESSION_COOKIE_NAME] |
|
csrf_token = _make_token(cookie.value) |
|
except KeyError: |
|
# No outgoing cookie to set session, but |
|
# a session might already exist. |
|
try: |
|
session_id = request.COOKIES[settings.SESSION_COOKIE_NAME] |
|
csrf_token = _make_token(session_id) |
|
except KeyError: |
|
# no incoming or outgoing cookie |
|
pass |
|
|
|
if csrf_token is not None and \ |
|
response['Content-Type'].split(';')[0] in _HTML_TYPES: |
|
|
|
# ensure we don't add the 'id' attribute twice (HTML validity) |
|
idattributes = itertools.chain(("id='csrfmiddlewaretoken'",), |
|
itertools.repeat('')) |
|
def add_csrf_field(match): |
|
"""Returns the matched <form> tag plus the added <input> element""" |
|
return mark_safe(match.group() + "<div style='display:none;'>" + \ |
|
"<input type='hidden' " + idattributes.next() + \ |
|
" name='csrfmiddlewaretoken' value='" + csrf_token + \ |
|
"' /></div>") |
|
|
|
# Modify any POST forms |
|
response.content = _POST_FORM_RE.sub(add_csrf_field, response.content) |
|
return response
|
|
|