PageRenderTime 180ms CodeModel.GetById 100ms app.highlight 20ms RepoModel.GetById 55ms app.codeStats 0ms

/django/middleware/csrf.py

https://code.google.com/p/mango-py/
Python | 326 lines | 228 code | 28 blank | 70 comment | 16 complexity | aab9305924a206418844938b6c53a6c4 MD5 | raw file
  1"""
  2Cross Site Request Forgery Middleware.
  3
  4This module provides a middleware that implements protection
  5against request forgeries from other sites.
  6"""
  7
  8import itertools
  9import re
 10import random
 11
 12from django.conf import settings
 13from django.core.urlresolvers import get_callable
 14from django.utils.cache import patch_vary_headers
 15from django.utils.hashcompat import md5_constructor
 16from django.utils.http import same_origin
 17from django.utils.log import getLogger
 18from django.utils.safestring import mark_safe
 19from django.utils.crypto import constant_time_compare
 20
 21_POST_FORM_RE = \
 22    re.compile(r'(<form\W[^>]*\bmethod\s*=\s*(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE)
 23
 24_HTML_TYPES = ('text/html', 'application/xhtml+xml')
 25
 26logger = getLogger('django.request')
 27
 28# Use the system (hardware-based) random number generator if it exists.
 29if hasattr(random, 'SystemRandom'):
 30    randrange = random.SystemRandom().randrange
 31else:
 32    randrange = random.randrange
 33_MAX_CSRF_KEY = 18446744073709551616L     # 2 << 63
 34
 35REASON_NO_REFERER = "Referer checking failed - no Referer."
 36REASON_BAD_REFERER = "Referer checking failed - %s does not match %s."
 37REASON_NO_COOKIE = "No CSRF or session cookie."
 38REASON_NO_CSRF_COOKIE = "CSRF cookie not set."
 39REASON_BAD_TOKEN = "CSRF token missing or incorrect."
 40
 41
 42def _get_failure_view():
 43    """
 44    Returns the view to be used for CSRF rejections
 45    """
 46    return get_callable(settings.CSRF_FAILURE_VIEW)
 47
 48
 49def _get_new_csrf_key():
 50    return md5_constructor("%s%s"
 51                % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest()
 52
 53
 54def _make_legacy_session_token(session_id):
 55    return md5_constructor(settings.SECRET_KEY + session_id).hexdigest()
 56
 57
 58def get_token(request):
 59    """
 60    Returns the the CSRF token required for a POST form. The token is an
 61    alphanumeric value.
 62
 63    A side effect of calling this function is to make the the csrf_protect
 64    decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie'
 65    header to the outgoing response.  For this reason, you may need to use this
 66    function lazily, as is done by the csrf context processor.
 67    """
 68    request.META["CSRF_COOKIE_USED"] = True
 69    return request.META.get("CSRF_COOKIE", None)
 70
 71
 72def _sanitize_token(token):
 73    # Allow only alphanum, and ensure we return a 'str' for the sake of the post
 74    # processing middleware.
 75    token = re.sub('[^a-zA-Z0-9]', '', str(token.decode('ascii', 'ignore')))
 76    if token == "":
 77        # In case the cookie has been truncated to nothing at some point.
 78        return _get_new_csrf_key()
 79    else:
 80        return token
 81
 82
 83class CsrfViewMiddleware(object):
 84    """
 85    Middleware that requires a present and correct csrfmiddlewaretoken
 86    for POST requests that have a CSRF cookie, and sets an outgoing
 87    CSRF cookie.
 88
 89    This middleware should be used in conjunction with the csrf_token template
 90    tag.
 91    """
 92    # The _accept and _reject methods currently only exist for the sake of the
 93    # requires_csrf_token decorator.
 94    def _accept(self, request):
 95        # Avoid checking the request twice by adding a custom attribute to
 96        # request.  This will be relevant when both decorator and middleware
 97        # are used.
 98        request.csrf_processing_done = True
 99        return None
100
101    def _reject(self, request, reason):
102        return _get_failure_view()(request, reason=reason)
103
104    def process_view(self, request, callback, callback_args, callback_kwargs):
105
106        if getattr(request, 'csrf_processing_done', False):
107            return None
108
109        # If the user doesn't have a CSRF cookie, generate one and store it in the
110        # request, so it's available to the view.  We'll store it in a cookie when
111        # we reach the response.
112        try:
113            # In case of cookies from untrusted sources, we strip anything
114            # dangerous at this point, so that the cookie + token will have the
115            # same, sanitized value.
116            request.META["CSRF_COOKIE"] = _sanitize_token(request.COOKIES[settings.CSRF_COOKIE_NAME])
117            cookie_is_new = False
118        except KeyError:
119            # No cookie, so create one.  This will be sent with the next
120            # response.
121            request.META["CSRF_COOKIE"] = _get_new_csrf_key()
122            # Set a flag to allow us to fall back and allow the session id in
123            # place of a CSRF cookie for this request only.
124            cookie_is_new = True
125
126        # Wait until request.META["CSRF_COOKIE"] has been manipulated before
127        # bailing out, so that get_token still works
128        if getattr(callback, 'csrf_exempt', False):
129            return None
130
131        if request.method == 'POST':
132            if getattr(request, '_dont_enforce_csrf_checks', False):
133                # Mechanism to turn off CSRF checks for test suite.  It comes after
134                # the creation of CSRF cookies, so that everything else continues to
135                # work exactly the same (e.g. cookies are sent etc), but before the
136                # any branches that call reject()
137                return self._accept(request)
138
139            if request.is_secure():
140                # Suppose user visits http://example.com/
141                # An active network attacker,(man-in-the-middle, MITM) sends a
142                # POST form which targets https://example.com/detonate-bomb/ and
143                # submits it via javascript.
144                #
145                # The attacker will need to provide a CSRF cookie and token, but
146                # that is no problem for a MITM and the session independent
147                # nonce we are using. So the MITM can circumvent the CSRF
148                # protection. This is true for any HTTP connection, but anyone
149                # using HTTPS expects better!  For this reason, for
150                # https://example.com/ we need additional protection that treats
151                # http://example.com/ as completely untrusted.  Under HTTPS,
152                # Barth et al. found that the Referer header is missing for
153                # same-domain requests in only about 0.2% of cases or less, so
154                # we can use strict Referer checking.
155                referer = request.META.get('HTTP_REFERER')
156                if referer is None:
157                    logger.warning('Forbidden (%s): %s' % (REASON_NO_REFERER, request.path),
158                        extra={
159                            'status_code': 403,
160                            'request': request,
161                        }
162                    )
163                    return self._reject(request, REASON_NO_REFERER)
164
165                # Note that request.get_host() includes the port
166                good_referer = 'https://%s/' % request.get_host()
167                if not same_origin(referer, good_referer):
168                    reason = REASON_BAD_REFERER % (referer, good_referer)
169                    logger.warning('Forbidden (%s): %s' % (reason, request.path),
170                        extra={
171                            'status_code': 403,
172                            'request': request,
173                        }
174                    )
175                    return self._reject(request, reason)
176
177            # If the user didn't already have a CSRF cookie, then fall back to
178            # the Django 1.1 method (hash of session ID), so a request is not
179            # rejected if the form was sent to the user before upgrading to the
180            # Django 1.2 method (session independent nonce)
181            if cookie_is_new:
182                try:
183                    session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
184                    csrf_token = _make_legacy_session_token(session_id)
185                except KeyError:
186                    # No CSRF cookie and no session cookie. For POST requests,
187                    # we insist on a CSRF cookie, and in this way we can avoid
188                    # all CSRF attacks, including login CSRF.
189                    logger.warning('Forbidden (%s): %s' % (REASON_NO_COOKIE, request.path),
190                        extra={
191                            'status_code': 403,
192                            'request': request,
193                        }
194                    )
195                    return self._reject(request, REASON_NO_COOKIE)
196            else:
197                csrf_token = request.META["CSRF_COOKIE"]
198
199            # check incoming token
200            request_csrf_token = request.POST.get('csrfmiddlewaretoken', '')
201            if request_csrf_token == "":
202                # Fall back to X-CSRFToken, to make things easier for AJAX
203                request_csrf_token = request.META.get('HTTP_X_CSRFTOKEN', '')
204
205            if not constant_time_compare(request_csrf_token, csrf_token):
206                if cookie_is_new:
207                    # probably a problem setting the CSRF cookie
208                    logger.warning('Forbidden (%s): %s' % (REASON_NO_CSRF_COOKIE, request.path),
209                        extra={
210                            'status_code': 403,
211                            'request': request,
212                        }
213                    )
214                    return self._reject(request, REASON_NO_CSRF_COOKIE)
215                else:
216                    logger.warning('Forbidden (%s): %s' % (REASON_BAD_TOKEN, request.path),
217                        extra={
218                            'status_code': 403,
219                            'request': request,
220                        }
221                    )
222                    return self._reject(request, REASON_BAD_TOKEN)
223
224        return self._accept(request)
225
226    def process_response(self, request, response):
227        if getattr(response, 'csrf_processing_done', False):
228            return response
229
230        # If CSRF_COOKIE is unset, then CsrfViewMiddleware.process_view was
231        # never called, probaby because a request middleware returned a response
232        # (for example, contrib.auth redirecting to a login page).
233        if request.META.get("CSRF_COOKIE") is None:
234            return response
235
236        if not request.META.get("CSRF_COOKIE_USED", False):
237            return response
238
239        # Set the CSRF cookie even if it's already set, so we renew the expiry timer.
240        response.set_cookie(settings.CSRF_COOKIE_NAME,
241                request.META["CSRF_COOKIE"], max_age = 60 * 60 * 24 * 7 * 52,
242                domain=settings.CSRF_COOKIE_DOMAIN)
243        # Content varies with the CSRF cookie, so set the Vary header.
244        patch_vary_headers(response, ('Cookie',))
245        response.csrf_processing_done = True
246        return response
247
248
249class CsrfResponseMiddleware(object):
250    """
251    DEPRECATED
252    Middleware that post-processes a response to add a csrfmiddlewaretoken.
253
254    This exists for backwards compatibility and as an interim measure until
255    applications are converted to using use the csrf_token template tag
256    instead. It will be removed in Django 1.4.
257    """
258    def __init__(self):
259        import warnings
260        warnings.warn(
261            "CsrfResponseMiddleware and CsrfMiddleware are deprecated; use CsrfViewMiddleware and the template tag instead (see CSRF documentation).",
262            DeprecationWarning
263        )
264
265    def process_response(self, request, response):
266        if getattr(response, 'csrf_exempt', False):
267            return response
268
269        if response['Content-Type'].split(';')[0] in _HTML_TYPES:
270            csrf_token = get_token(request)
271            # If csrf_token is None, we have no token for this request, which probably
272            # means that this is a response from a request middleware.
273            if csrf_token is None:
274                return response
275
276            # ensure we don't add the 'id' attribute twice (HTML validity)
277            idattributes = itertools.chain(("id='csrfmiddlewaretoken'",),
278                                           itertools.repeat(''))
279            def add_csrf_field(match):
280                """Returns the matched <form> tag plus the added <input> element"""
281                return mark_safe(match.group() + "<div style='display:none;'>" + \
282                "<input type='hidden' " + idattributes.next() + \
283                " name='csrfmiddlewaretoken' value='" + csrf_token + \
284                "' /></div>")
285
286            # Modify any POST forms
287            response.content, n = _POST_FORM_RE.subn(add_csrf_field, response.content)
288            if n > 0:
289                # Content varies with the CSRF cookie, so set the Vary header.
290                patch_vary_headers(response, ('Cookie',))
291
292                # Since the content has been modified, any Etag will now be
293                # incorrect.  We could recalculate, but only if we assume that
294                # the Etag was set by CommonMiddleware. The safest thing is just
295                # to delete. See bug #9163
296                del response['ETag']
297        return response
298
299
300class CsrfMiddleware(object):
301    """
302    Django middleware that adds protection against Cross Site
303    Request Forgeries by adding hidden form fields to POST forms and
304    checking requests for the correct value.
305
306    CsrfMiddleware uses two middleware, CsrfViewMiddleware and
307    CsrfResponseMiddleware, which can be used independently.  It is recommended
308    to use only CsrfViewMiddleware and use the csrf_token template tag in
309    templates for inserting the token.
310    """
311    # We can't just inherit from CsrfViewMiddleware and CsrfResponseMiddleware
312    # because both have process_response methods.
313    def __init__(self):
314        self.response_middleware = CsrfResponseMiddleware()
315        self.view_middleware = CsrfViewMiddleware()
316
317    def process_response(self, request, resp):
318        # We must do the response post-processing first, because that calls
319        # get_token(), which triggers a flag saying that the CSRF cookie needs
320        # to be sent (done in CsrfViewMiddleware.process_response)
321        resp2 = self.response_middleware.process_response(request, resp)
322        return self.view_middleware.process_response(request, resp2)
323
324    def process_view(self, request, callback, callback_args, callback_kwargs):
325        return self.view_middleware.process_view(request, callback, callback_args,
326                                                 callback_kwargs)