diff options
author | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2011-03-28 23:03:16 -0400 |
---|---|---|
committer | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2011-03-28 23:03:16 -0400 |
commit | 96a9863db21fc84af38ee28684807849ade25b97 (patch) | |
tree | 09d752e19aab3f4deb31d77ec68c2f12b48ace57 /askbot/patches | |
parent | 8ae8fcdc858af16e8f2d5ab87ae1a833cde52356 (diff) | |
download | askbot-96a9863db21fc84af38ee28684807849ade25b97.tar.gz askbot-96a9863db21fc84af38ee28684807849ade25b97.tar.bz2 askbot-96a9863db21fc84af38ee28684807849ade25b97.zip |
expanded range of supported versions of django and added patches for csrf_token
Diffstat (limited to 'askbot/patches')
-rw-r--r-- | askbot/patches/__init__.py | 28 | ||||
-rw-r--r-- | askbot/patches/coffin_patches.py | 34 | ||||
-rw-r--r-- | askbot/patches/django_patches.py | 327 |
3 files changed, 389 insertions, 0 deletions
diff --git a/askbot/patches/__init__.py b/askbot/patches/__init__.py new file mode 100644 index 00000000..3c5e0d28 --- /dev/null +++ b/askbot/patches/__init__.py @@ -0,0 +1,28 @@ +"""module for monkey patching that is +necessary for interoperability of different +versions of various components used in askbot +""" +import django +from askbot.patches import django_patches +from askbot.deployment import package_utils + +def patch_django(): + """earlier versions of Django do not have + csrf token and function called import_library + (the latter is needed by coffin) + """ + (major, minor, micro) = package_utils.get_django_version() + if major == 1 and minor < 2: + django_patches.add_import_library_function() + django_patches.add_csrf_protection() + +def patch_coffin(): + """coffin before version 0.3.4 + does not have csrf_token template tag. + This patch must be applied after the django patches + """ + from askbot.patches import coffin_patches + + (major, minor, micro) = package_utils.get_coffin_version() + if major == 0 and minor == 3 and micro < 4: + coffin_patches.add_csrf_token_tag() diff --git a/askbot/patches/coffin_patches.py b/askbot/patches/coffin_patches.py new file mode 100644 index 00000000..92e3bc09 --- /dev/null +++ b/askbot/patches/coffin_patches.py @@ -0,0 +1,34 @@ +"""patches for the coffin module""" +from jinja2 import nodes +from jinja2 import Markup +from jinja2.ext import Extension + +class CsrfTokenExtension(Extension): + """Jinja2-version of the ``csrf_token`` tag. + + Adapted from a snippet by Jason Green: + http://www.djangosnippets.org/snippets/1847/ + + This tag is a bit stricter than the Django tag in that it doesn't + simply ignore any invalid arguments passed in. + """ + + tags = set(['csrf_token']) + + def parse(self, parser): + lineno = parser.stream.next().lineno + return nodes.Output([ + self.call_method('_render', [nodes.Name('csrf_token', 'load')]) + ]).set_lineno(lineno) + + def _render(self, csrf_token): + from django.template.defaulttags import CsrfTokenNode + return Markup(CsrfTokenNode().render({'csrf_token': csrf_token})) + +def add_csrf_token_tag(): + """adds csrf token tag to the default library""" + import coffin.template.defaulttags + coffin.template.defaulttags.CsrfTokenExtension = CsrfTokenExtension + csrf_token = CsrfTokenExtension + coffin.template.defaulttags.csrf_token = csrf_token + coffin.template.defaulttags.register.tag(csrf_token) diff --git a/askbot/patches/django_patches.py b/askbot/patches/django_patches.py new file mode 100644 index 00000000..baab64af --- /dev/null +++ b/askbot/patches/django_patches.py @@ -0,0 +1,327 @@ +"""a module for patching django""" +import imp +import os +import sys +from django.utils.safestring import mark_safe +from django.utils.functional import lazy +from django.template import Node + +def module_has_submodule(package, module_name): + """See if 'module' is in 'package'.""" + name = ".".join([package.__name__, module_name]) + if name in sys.modules: + return True + for finder in sys.meta_path: + if finder.find_module(name): + return True + for entry in package.__path__: # No __path__, then not a package. + try: + # Try the cached finder. + finder = sys.path_importer_cache[entry] + if finder is None: + # Implicit import machinery should be used. + try: + file_, _, _ = imp.find_module(module_name, [entry]) + if file_: + file_.close() + return True + except ImportError: + continue + # Else see if the finder knows of a loader. + elif finder.find_module(name): + return True + else: + continue + except KeyError: + # No cached finder, so try and make one. + for hook in sys.path_hooks: + try: + finder = hook(entry) + # XXX Could cache in sys.path_importer_cache + if finder.find_module(name): + return True + else: + # Once a finder is found, stop the search. + break + except ImportError: + # Continue the search for a finder. + continue + else: + # No finder found. + # Try the implicit import machinery if searching a directory. + if os.path.isdir(entry): + try: + file_, _, _ = imp.find_module(module_name, [entry]) + if file_: + file_.close() + return True + except ImportError: + pass + # XXX Could insert None or NullImporter + else: + # Exhausted the search, so the module cannot be found. + return False + +class CsrfTokenNode(Node): + def render(self, context): + csrf_token = context.get('csrf_token', None) + if csrf_token: + if csrf_token == 'NOTPROVIDED': + return mark_safe(u"") + else: + return mark_safe(u"<div style='display:none'><input type='hidden' name='csrfmiddlewaretoken' value='%s' /></div>" % csrf_token) + else: + # It's very probable that the token is missing because of + # misconfiguration, so we raise a warning + from django.conf import settings + if settings.DEBUG: + import warnings + warnings.warn("A {% csrf_token %} was used in a template, but the context did not provide the value. This is usually caused by not using RequestContext.") + return u'' + +def get_token(request): + """ + Returns the the CSRF token required for a POST form. + A side effect of calling this function is to make the the csrf_protect + decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie' + header to the outgoing response. For this reason, you may need to use this + function lazily, as is done by the csrf context processor. + """ + request.META["CSRF_COOKIE_USED"] = True + return request.META.get("CSRF_COOKIE", None) + +def csrf(request): + """ + Context processor that provides a CSRF token, or the string 'NOTPROVIDED' if + it has not been provided by either a view decorator or the middleware + """ + def _get_val(): + token = get_token(request) + if token is None: + # In order to be able to provide debugging info in the + # case of misconfiguration, we use a sentinel value + # instead of returning an empty dict. + return 'NOTPROVIDED' + else: + return token + _get_val = lazy(_get_val, str) + return {'csrf_token': _get_val() } + +""" +Cross Site Request Forgery Middleware. +This module provides a middleware that implements protection +against request forgeries from other sites. +""" +import itertools +import re +import random +from django.conf import settings +from django.core.urlresolvers import get_callable +from django.utils.hashcompat import md5_constructor +from django.utils.safestring import mark_safe +_POST_FORM_RE = \ + re.compile(r'(<form\W[^>]*\bmethod\s*=\s*(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE) +_HTML_TYPES = ('text/html', 'application/xhtml+xml') +# Use the system (hardware-based) random number generator if it exists. +if hasattr(random, 'SystemRandom'): + randrange = random.SystemRandom().randrange +else: + randrange = random.randrange +_MAX_CSRF_KEY = 18446744073709551616L # 2 << 63 +def _get_failure_view(): + """ + Returns the view to be used for CSRF rejections + """ + return get_callable(settings.CSRF_FAILURE_VIEW) + +def _get_new_csrf_key(): + return md5_constructor("%s%s" + % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest() + +def _make_legacy_session_token(session_id): + return md5_constructor(settings.SECRET_KEY + session_id).hexdigest() + +class CsrfViewMiddleware(object): + """ + Middleware that requires a present and correct csrfmiddlewaretoken + for POST requests that have a CSRF cookie, and sets an outgoing + CSRF cookie. + This middleware should be used in conjunction with the csrf_token template + tag. + """ + def process_view(self, request, callback, callback_args, callback_kwargs): + if getattr(callback, 'csrf_exempt', False): + return None + if getattr(request, 'csrf_processing_done', False): + return None + reject = lambda s: _get_failure_view()(request, reason=s) + def accept(): + # Avoid checking the request twice by adding a custom attribute to + # request. This will be relevant when both decorator and middleware + # are used. + request.csrf_processing_done = True + return None + # If the user doesn't have a CSRF cookie, generate one and store it in the + # request, so it's available to the view. We'll store it in a cookie when + # we reach the response. + try: + request.META["CSRF_COOKIE"] = request.COOKIES[settings.CSRF_COOKIE_NAME] + cookie_is_new = False + except KeyError: + # No cookie, so create one. This will be sent with the next + # response. + request.META["CSRF_COOKIE"] = _get_new_csrf_key() + # Set a flag to allow us to fall back and allow the session id in + # place of a CSRF cookie for this request only. + cookie_is_new = True + if request.method == 'POST': + if getattr(request, '_dont_enforce_csrf_checks', False): + # Mechanism to turn off CSRF checks for test suite. It comes after + # the creation of CSRF cookies, so that everything else continues to + # work exactly the same (e.g. cookies are sent etc), but before the + # any branches that call reject() + return accept() + if request.is_ajax(): + # .is_ajax() is based on the presence of X-Requested-With. In + # the context of a browser, this can only be sent if using + # XmlHttpRequest. Browsers implement careful policies for + # XmlHttpRequest: + # + # * Normally, only same-domain requests are allowed. + # + # * Some browsers (e.g. Firefox 3.5 and later) relax this + # carefully: + # + # * if it is a 'simple' GET or POST request (which can + # include no custom headers), it is allowed to be cross + # domain. These requests will not be recognized as AJAX. + # + # * if a 'preflight' check with the server confirms that the + # server is expecting and allows the request, cross domain + # requests even with custom headers are allowed. These + # requests will be recognized as AJAX, but can only get + # through when the developer has specifically opted in to + # allowing the cross-domain POST request. + # + # So in all cases, it is safe to allow these requests through. + return accept() + if request.is_secure(): + # Strict referer checking for HTTPS + referer = request.META.get('HTTP_REFERER') + if referer is None: + return reject("Referer checking failed - no Referer.") + # The following check ensures that the referer is HTTPS, + # the domains match and the ports match. This might be too strict. + good_referer = 'https://%s/' % request.get_host() + if not referer.startswith(good_referer): + return reject("Referer checking failed - %s does not match %s." % + (referer, good_referer)) + # If the user didn't already have a CSRF cookie, then fall back to + # the Django 1.1 method (hash of session ID), so a request is not + # rejected if the form was sent to the user before upgrading to the + # Django 1.2 method (session independent nonce) + if cookie_is_new: + try: + session_id = request.COOKIES[settings.SESSION_COOKIE_NAME] + csrf_token = _make_legacy_session_token(session_id) + except KeyError: + # No CSRF cookie and no session cookie. For POST requests, + # we insist on a CSRF cookie, and in this way we can avoid + # all CSRF attacks, including login CSRF. + return reject("No CSRF or session cookie.") + else: + csrf_token = request.META["CSRF_COOKIE"] + # check incoming token + request_csrf_token = request.POST.get('csrfmiddlewaretoken', None) + if request_csrf_token != csrf_token: + if cookie_is_new: + # probably a problem setting the CSRF cookie + return reject("CSRF cookie not set.") + else: + return reject("CSRF token missing or incorrect.") + return accept() + def process_response(self, request, response): + if getattr(response, 'csrf_processing_done', False): + return response + # If CSRF_COOKIE is unset, then CsrfViewMiddleware.process_view was + # never called, probaby because a request middleware returned a response + # (for example, contrib.auth redirecting to a login page). + if request.META.get("CSRF_COOKIE") is None: + return response + if not request.META.get("CSRF_COOKIE_USED", False): + return response + # Set the CSRF cookie even if it's already set, so we renew the expiry timer. + response.set_cookie(settings.CSRF_COOKIE_NAME, + request.META["CSRF_COOKIE"], max_age = 60 * 60 * 24 * 7 * 52, + domain=settings.CSRF_COOKIE_DOMAIN) + # Content varies with the CSRF cookie, so set the Vary header. + from django.utils.cache import patch_vary_headers + patch_vary_headers(response, ('Cookie',)) + response.csrf_processing_done = True + return response + +from django.utils.decorators import decorator_from_middleware +from functools import wraps + +csrf_protect = decorator_from_middleware(CsrfViewMiddleware) +csrf_protect.__name__ = "csrf_protect" +csrf_protect.__doc__ = """ +This decorator adds CSRF protection in exactly the same way as +CsrfViewMiddleware, but it can be used on a per view basis. Using both, or +using the decorator multiple times, is harmless and efficient. +""" + +def add_import_library_function(): + + #this definition is copy/pasted from django 1.2 source code + #it is necessary to make Coffin library happy + from django.utils.importlib import import_module + class InvalidTemplateLibrary(Exception): + pass + + def import_library(taglib_module): + """Load a template tag library module. + Verifies that the library contains a 'register' attribute, and + returns that attribute as the representation of the library + """ + app_path, taglib = taglib_module.rsplit('.',1) + app_module = import_module(app_path) + try: + mod = import_module(taglib_module) + except ImportError, e: + # If the ImportError is because the taglib submodule does not exist, that's not + # an error that should be raised. If the submodule exists and raised an ImportError + # on the attempt to load it, that we want to raise. + if not module_has_submodule(app_module, taglib): + return None + else: + raise InvalidTemplateLibrary("ImportError raised loading %s: %s" % (taglib_module, e)) + try: + return mod.register + except AttributeError: + raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % taglib_module) + + import django.template + django.template.import_library = import_library + +def add_csrf_protection(): + """adds csrf_token template tag to django + Must be used if version of django is < 1.2 + + Also adds csrf function to the context processor + and the csrf_protect decorator for the views + """ + import django.template.defaulttags + def csrf_token(parser, token): + return CsrfTokenNode() + django.template.defaulttags.CsrfTokenNode = CsrfTokenNode + django.template.defaulttags.register.tag(csrf_token) + + #add csrf context processor + import django.core.context_processors + django.core.context_processors.csrf = csrf + + #add csrf_protect decorator + import django.views.decorators + django.views.decorators.csrf = imp.new_module('csrf') + django.views.decorators.csrf.csrf_protect = csrf_protect |