summaryrefslogtreecommitdiffstats
path: root/askbot/patches
diff options
context:
space:
mode:
authorEvgeny Fadeev <evgeny.fadeev@gmail.com>2011-03-28 23:03:16 -0400
committerEvgeny Fadeev <evgeny.fadeev@gmail.com>2011-03-28 23:03:16 -0400
commit96a9863db21fc84af38ee28684807849ade25b97 (patch)
tree09d752e19aab3f4deb31d77ec68c2f12b48ace57 /askbot/patches
parent8ae8fcdc858af16e8f2d5ab87ae1a833cde52356 (diff)
downloadaskbot-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__.py28
-rw-r--r--askbot/patches/coffin_patches.py34
-rw-r--r--askbot/patches/django_patches.py327
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