web/lib/django/middleware/csrf.py
changeset 29 cc9b7e14412b
equal deleted inserted replaced
28:b758351d191f 29:cc9b7e14412b
       
     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 itertools
       
     9 import re
       
    10 import random
       
    11 
       
    12 from django.conf import settings
       
    13 from django.core.urlresolvers import get_callable
       
    14 from django.utils.cache import patch_vary_headers
       
    15 from django.utils.hashcompat import md5_constructor
       
    16 from django.utils.safestring import mark_safe
       
    17 
       
    18 _POST_FORM_RE = \
       
    19     re.compile(r'(<form\W[^>]*\bmethod\s*=\s*(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE)
       
    20 
       
    21 _HTML_TYPES = ('text/html', 'application/xhtml+xml')
       
    22 
       
    23 # Use the system (hardware-based) random number generator if it exists.
       
    24 if hasattr(random, 'SystemRandom'):
       
    25     randrange = random.SystemRandom().randrange
       
    26 else:
       
    27     randrange = random.randrange
       
    28 _MAX_CSRF_KEY = 18446744073709551616L     # 2 << 63
       
    29 
       
    30 def _get_failure_view():
       
    31     """
       
    32     Returns the view to be used for CSRF rejections
       
    33     """
       
    34     return get_callable(settings.CSRF_FAILURE_VIEW)
       
    35 
       
    36 def _get_new_csrf_key():
       
    37     return md5_constructor("%s%s"
       
    38                 % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest()
       
    39 
       
    40 def _make_legacy_session_token(session_id):
       
    41     return md5_constructor(settings.SECRET_KEY + session_id).hexdigest()
       
    42 
       
    43 def get_token(request):
       
    44     """
       
    45     Returns the the CSRF token required for a POST form.
       
    46 
       
    47     A side effect of calling this function is to make the the csrf_protect
       
    48     decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie'
       
    49     header to the outgoing response.  For this reason, you may need to use this
       
    50     function lazily, as is done by the csrf context processor.
       
    51     """
       
    52     request.META["CSRF_COOKIE_USED"] = True
       
    53     return request.META.get("CSRF_COOKIE", None)
       
    54 
       
    55 class CsrfViewMiddleware(object):
       
    56     """
       
    57     Middleware that requires a present and correct csrfmiddlewaretoken
       
    58     for POST requests that have a CSRF cookie, and sets an outgoing
       
    59     CSRF cookie.
       
    60 
       
    61     This middleware should be used in conjunction with the csrf_token template
       
    62     tag.
       
    63     """
       
    64     def process_view(self, request, callback, callback_args, callback_kwargs):
       
    65         if getattr(callback, 'csrf_exempt', False):
       
    66             return None
       
    67 
       
    68         if getattr(request, 'csrf_processing_done', False):
       
    69             return None
       
    70 
       
    71         reject = lambda s: _get_failure_view()(request, reason=s)
       
    72         def accept():
       
    73             # Avoid checking the request twice by adding a custom attribute to
       
    74             # request.  This will be relevant when both decorator and middleware
       
    75             # are used.
       
    76             request.csrf_processing_done = True
       
    77             return None
       
    78 
       
    79         # If the user doesn't have a CSRF cookie, generate one and store it in the
       
    80         # request, so it's available to the view.  We'll store it in a cookie when
       
    81         # we reach the response.
       
    82         try:
       
    83             request.META["CSRF_COOKIE"] = request.COOKIES[settings.CSRF_COOKIE_NAME]
       
    84             cookie_is_new = False
       
    85         except KeyError:
       
    86             # No cookie, so create one.  This will be sent with the next
       
    87             # response.
       
    88             request.META["CSRF_COOKIE"] = _get_new_csrf_key()
       
    89             # Set a flag to allow us to fall back and allow the session id in
       
    90             # place of a CSRF cookie for this request only.
       
    91             cookie_is_new = True
       
    92 
       
    93         if request.method == 'POST':
       
    94             if getattr(request, '_dont_enforce_csrf_checks', False):
       
    95                 # Mechanism to turn off CSRF checks for test suite.  It comes after
       
    96                 # the creation of CSRF cookies, so that everything else continues to
       
    97                 # work exactly the same (e.g. cookies are sent etc), but before the
       
    98                 # any branches that call reject()
       
    99                 return accept()
       
   100 
       
   101             if request.is_ajax():
       
   102                 # .is_ajax() is based on the presence of X-Requested-With.  In
       
   103                 # the context of a browser, this can only be sent if using
       
   104                 # XmlHttpRequest.  Browsers implement careful policies for
       
   105                 # XmlHttpRequest:
       
   106                 #
       
   107                 #  * Normally, only same-domain requests are allowed.
       
   108                 #
       
   109                 #  * Some browsers (e.g. Firefox 3.5 and later) relax this
       
   110                 #    carefully:
       
   111                 #
       
   112                 #    * if it is a 'simple' GET or POST request (which can
       
   113                 #      include no custom headers), it is allowed to be cross
       
   114                 #      domain.  These requests will not be recognized as AJAX.
       
   115                 #
       
   116                 #    * if a 'preflight' check with the server confirms that the
       
   117                 #      server is expecting and allows the request, cross domain
       
   118                 #      requests even with custom headers are allowed. These
       
   119                 #      requests will be recognized as AJAX, but can only get
       
   120                 #      through when the developer has specifically opted in to
       
   121                 #      allowing the cross-domain POST request.
       
   122                 #
       
   123                 # So in all cases, it is safe to allow these requests through.
       
   124                 return accept()
       
   125 
       
   126             if request.is_secure():
       
   127                 # Strict referer checking for HTTPS
       
   128                 referer = request.META.get('HTTP_REFERER')
       
   129                 if referer is None:
       
   130                     return reject("Referer checking failed - no Referer.")
       
   131 
       
   132                 # The following check ensures that the referer is HTTPS,
       
   133                 # the domains match and the ports match.  This might be too strict.
       
   134                 good_referer = 'https://%s/' % request.get_host()
       
   135                 if not referer.startswith(good_referer):
       
   136                     return reject("Referer checking failed - %s does not match %s." %
       
   137                                   (referer, good_referer))
       
   138 
       
   139             # If the user didn't already have a CSRF cookie, then fall back to
       
   140             # the Django 1.1 method (hash of session ID), so a request is not
       
   141             # rejected if the form was sent to the user before upgrading to the
       
   142             # Django 1.2 method (session independent nonce)
       
   143             if cookie_is_new:
       
   144                 try:
       
   145                     session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
       
   146                     csrf_token = _make_legacy_session_token(session_id)
       
   147                 except KeyError:
       
   148                     # No CSRF cookie and no session cookie. For POST requests,
       
   149                     # we insist on a CSRF cookie, and in this way we can avoid
       
   150                     # all CSRF attacks, including login CSRF.
       
   151                     return reject("No CSRF or session cookie.")
       
   152             else:
       
   153                 csrf_token = request.META["CSRF_COOKIE"]
       
   154 
       
   155             # check incoming token
       
   156             request_csrf_token = request.POST.get('csrfmiddlewaretoken', None)
       
   157             if request_csrf_token != csrf_token:
       
   158                 if cookie_is_new:
       
   159                     # probably a problem setting the CSRF cookie
       
   160                     return reject("CSRF cookie not set.")
       
   161                 else:
       
   162                     return reject("CSRF token missing or incorrect.")
       
   163 
       
   164         return accept()
       
   165 
       
   166     def process_response(self, request, response):
       
   167         if getattr(response, 'csrf_processing_done', False):
       
   168             return response
       
   169 
       
   170         # If CSRF_COOKIE is unset, then CsrfViewMiddleware.process_view was
       
   171         # never called, probaby because a request middleware returned a response
       
   172         # (for example, contrib.auth redirecting to a login page).
       
   173         if request.META.get("CSRF_COOKIE") is None:
       
   174             return response
       
   175 
       
   176         if not request.META.get("CSRF_COOKIE_USED", False):
       
   177             return response
       
   178 
       
   179         # Set the CSRF cookie even if it's already set, so we renew the expiry timer.
       
   180         response.set_cookie(settings.CSRF_COOKIE_NAME,
       
   181                 request.META["CSRF_COOKIE"], max_age = 60 * 60 * 24 * 7 * 52,
       
   182                 domain=settings.CSRF_COOKIE_DOMAIN)
       
   183         # Content varies with the CSRF cookie, so set the Vary header.
       
   184         patch_vary_headers(response, ('Cookie',))
       
   185         response.csrf_processing_done = True
       
   186         return response
       
   187 
       
   188 class CsrfResponseMiddleware(object):
       
   189     """
       
   190     DEPRECATED
       
   191     Middleware that post-processes a response to add a csrfmiddlewaretoken.
       
   192 
       
   193     This exists for backwards compatibility and as an interim measure until
       
   194     applications are converted to using use the csrf_token template tag
       
   195     instead. It will be removed in Django 1.4.
       
   196     """
       
   197     def __init__(self):
       
   198         import warnings
       
   199         warnings.warn(
       
   200             "CsrfResponseMiddleware and CsrfMiddleware are deprecated; use CsrfViewMiddleware and the template tag instead (see CSRF documentation).",
       
   201             PendingDeprecationWarning
       
   202         )
       
   203 
       
   204     def process_response(self, request, response):
       
   205         if getattr(response, 'csrf_exempt', False):
       
   206             return response
       
   207 
       
   208         if response['Content-Type'].split(';')[0] in _HTML_TYPES:
       
   209             csrf_token = get_token(request)
       
   210             # If csrf_token is None, we have no token for this request, which probably
       
   211             # means that this is a response from a request middleware.
       
   212             if csrf_token is None:
       
   213                 return response
       
   214 
       
   215             # ensure we don't add the 'id' attribute twice (HTML validity)
       
   216             idattributes = itertools.chain(("id='csrfmiddlewaretoken'",),
       
   217                                            itertools.repeat(''))
       
   218             def add_csrf_field(match):
       
   219                 """Returns the matched <form> tag plus the added <input> element"""
       
   220                 return mark_safe(match.group() + "<div style='display:none;'>" + \
       
   221                 "<input type='hidden' " + idattributes.next() + \
       
   222                 " name='csrfmiddlewaretoken' value='" + csrf_token + \
       
   223                 "' /></div>")
       
   224 
       
   225             # Modify any POST forms
       
   226             response.content, n = _POST_FORM_RE.subn(add_csrf_field, response.content)
       
   227             if n > 0:
       
   228                 # Content varies with the CSRF cookie, so set the Vary header.
       
   229                 patch_vary_headers(response, ('Cookie',))
       
   230 
       
   231                 # Since the content has been modified, any Etag will now be
       
   232                 # incorrect.  We could recalculate, but only if we assume that
       
   233                 # the Etag was set by CommonMiddleware. The safest thing is just
       
   234                 # to delete. See bug #9163
       
   235                 del response['ETag']
       
   236         return response
       
   237 
       
   238 class CsrfMiddleware(object):
       
   239     """
       
   240     Django middleware that adds protection against Cross Site
       
   241     Request Forgeries by adding hidden form fields to POST forms and
       
   242     checking requests for the correct value.
       
   243 
       
   244     CsrfMiddleware uses two middleware, CsrfViewMiddleware and
       
   245     CsrfResponseMiddleware, which can be used independently.  It is recommended
       
   246     to use only CsrfViewMiddleware and use the csrf_token template tag in
       
   247     templates for inserting the token.
       
   248     """
       
   249     # We can't just inherit from CsrfViewMiddleware and CsrfResponseMiddleware
       
   250     # because both have process_response methods.
       
   251     def __init__(self):
       
   252         self.response_middleware = CsrfResponseMiddleware()
       
   253         self.view_middleware = CsrfViewMiddleware()
       
   254 
       
   255     def process_response(self, request, resp):
       
   256         # We must do the response post-processing first, because that calls
       
   257         # get_token(), which triggers a flag saying that the CSRF cookie needs
       
   258         # to be sent (done in CsrfViewMiddleware.process_response)
       
   259         resp2 = self.response_middleware.process_response(request, resp)
       
   260         return self.view_middleware.process_response(request, resp2)
       
   261 
       
   262     def process_view(self, request, callback, callback_args, callback_kwargs):
       
   263         return self.view_middleware.process_view(request, callback, callback_args,
       
   264                                                  callback_kwargs)
       
   265