Permalink
Browse files

More CSRF fixes (UGH)

  • Loading branch information...
1 parent 8a685ec commit d1f3771e41c028a4d0b2c43f5c397e5585661c72 @dcramer dcramer committed Dec 19, 2011
Showing with 96 additions and 58 deletions.
  1. +5 −0 CHANGES
  2. +5 −5 example_module/nexus_modules.py
  3. +32 −30 nexus/media/js/nexus.js
  4. +10 −9 nexus/modules.py
  5. +43 −13 nexus/sites.py
  6. +1 −1 setup.py
View
@@ -1,3 +1,8 @@
+0.2.3
+
+- Ensure on exempt views we still send .
+- Downgrade CSRF ajax to work with older versions of jQuery.
+
0.2.2
- Update AJAX CSRF set to work against correct origins.
@@ -3,24 +3,24 @@
class HelloWorldModule(nexus.NexusModule):
home_url = 'index'
name = 'hello-world'
-
+
def get_title(self):
return 'Hello World'
-
+
def get_urls(self):
from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('',
url(r'^$', self.as_view(self.index), name='index'),
)
-
+
return urlpatterns
-
+
def render_on_dashboard(self, request):
return self.render_to_string('nexus/example/dashboard.html', {
'title': 'Hello World',
})
-
+
def index(self, request):
return self.render_to_response("nexus/example/index.html", {
'title': 'Hello World',
View
@@ -1,38 +1,40 @@
// AJAX CSRF setup. Source: http://docs.djangoproject.com/en/1.2/ref/contrib/csrf/#ajax
-jQuery.ajaxSend(function(event, xhr, settings) {
- function getCookie(name) {
- var cookieValue = null;
- if (document.cookie && document.cookie !== '') {
- var cookies = document.cookie.split(';');
- for (var i = 0; i < cookies.length; i++) {
- var cookie = jQuery.trim(cookies[i]);
- // Does this cookie string begin with the name we want?
- if (cookie.substring(0, name.length + 1) == (name + '=')) {
- cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
- break;
+jQuery.ajaxSetup({
+ beforeSend: function(xhr, settings) {
+ function getCookie(name) {
+ var cookieValue = null;
+ if (document.cookie && document.cookie !== '') {
+ var cookies = document.cookie.split(';');
+ for (var i = 0; i < cookies.length; i++) {
+ var cookie = jQuery.trim(cookies[i]);
+ // Does this cookie string begin with the name we want?
+ if (cookie.substring(0, name.length + 1) == (name + '=')) {
+ cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
+ break;
+ }
}
}
+ return cookieValue;
+ }
+ function sameOrigin(url) {
+ // url could be relative or scheme relative or absolute
+ var host = document.location.host; // host + port
+ var protocol = document.location.protocol;
+ var sr_origin = '//' + host;
+ var origin = protocol + sr_origin;
+ // Allow absolute or scheme relative URLs to same origin
+ return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
+ (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
+ // or any other URL that isn't scheme relative or absolute i.e relative.
+ !(/^(\/\/|http:|https:).*/.test(url));
+ }
+ function safeMethod(method) {
+ return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
- return cookieValue;
- }
- function sameOrigin(url) {
- // url could be relative or scheme relative or absolute
- var host = document.location.host; // host + port
- var protocol = document.location.protocol;
- var sr_origin = '//' + host;
- var origin = protocol + sr_origin;
- // Allow absolute or scheme relative URLs to same origin
- return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
- (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
- // or any other URL that isn't scheme relative or absolute i.e relative.
- !(/^(\/\/|http:|https:).*/.test(url));
- }
- function safeMethod(method) {
- return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
- }
- if (!safeMethod(settings.type) && sameOrigin(settings.url)) {
- xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
+ if (!safeMethod(settings.type) && sameOrigin(settings.url)) {
+ xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
+ }
}
});
View
@@ -7,6 +7,7 @@
import os
import thread
+
class NexusModule(object):
# base url (pattern name) to show in navigation
home_url = None
@@ -20,7 +21,7 @@ class NexusModule(object):
# list of active sites within process
_globals = {}
-
+
def __init__(self, site, category=None, name=None, app_name=None):
self.category = category
self.site = site
@@ -46,11 +47,11 @@ def set_global(cls, key, value):
if ident not in cls._globals:
cls._globals[ident] = {}
cls._globals[ident][key] = value
-
+
@classmethod
def get_global(cls, key):
return cls._globals.get(thread.get_ident(), {}).get(key)
-
+
@classmethod
def get_request(cls):
"""
@@ -85,15 +86,15 @@ def as_view(self, *args, **kwargs):
if 'extra_permission' not in kwargs:
kwargs['extra_permission'] = self.permission
return self.site.as_view(*args, **kwargs)
-
+
def get_context(self, request):
title = self.get_title()
return {
'title': title,
'module_title': title,
'trail_bits': self.get_trail(request),
}
-
+
def get_namespace(self):
return hashlib.md5(self.__class__.__module__ + '.' + self.__class__.__name__).hexdigest()
@@ -120,18 +121,18 @@ def get_trail(self, request):
return [
(self.get_title(), self.get_home_url(request)),
]
-
+
def get_home_url(self, request):
if self.home_url:
if self.app_name:
home_url_name = '%s:%s' % (self.app_name, self.home_url)
else:
home_url_name = self.home_url
-
+
home_url = reverse(home_url_name, current_app=self.name)
else:
home_url = None
-
+
return home_url
-
+
View
@@ -24,6 +24,33 @@
NEXUS_ROOT = os.path.normpath(os.path.dirname(__file__))
+
+try:
+ from django.views.decorators.csrf import ensure_csrf_cookie
+except ImportError: # must be < Django 1.3
+ from django.views.decorators.csrf import CsrfViewMiddleware
+ from django.middleware.csrf import get_token
+ from django.utils.decorators import decorator_from_middleware
+
+ class _EnsureCsrfCookie(CsrfViewMiddleware):
+ def _reject(self, request, reason):
+ return None
+
+ def process_view(self, request, callback, callback_args, callback_kwargs):
+ retval = super(_EnsureCsrfCookie, self).process_view(request, callback, callback_args, callback_kwargs)
+ # Forces process_response to send the cookie
+ get_token(request)
+ return retval
+
+
+ ensure_csrf_cookie = decorator_from_middleware(_EnsureCsrfCookie)
+ ensure_csrf_cookie.__name__ = 'ensure_csrf_cookie'
+ ensure_csrf_cookie.__doc__ = """
+ Use this decorator to ensure that a view sets a CSRF cookie, whether or not it
+ uses the csrf_token template tag, or the CsrfViewMiddleware is used.
+ """
+
+
class NexusSite(object):
def __init__(self, name=None, app_name='nexus'):
self._registry = {}
@@ -48,7 +75,7 @@ def register(self, module, namespace=None, category=None):
module.app_name = module.name = namespace
self._registry[namespace] = (module, category)
return module
-
+
def unregister(self, namespace):
if namespace in self._registry:
del self._registry[namespace]
@@ -63,16 +90,17 @@ def get_urls(self):
url(r'^login/$', self.login, name='login'),
url(r'^logout/$', self.as_view(self.logout), name='logout'),
), self.app_name, self.name
-
+
urlpatterns = patterns('',
url(r'^', include(base_urls)),
)
for namespace, module in self.get_modules():
urlpatterns += patterns('',
url(r'^%s/' % namespace, include(module.urls)),
)
-
+
return urlpatterns
+
def urls(self):
return self.get_urls()
@@ -109,6 +137,8 @@ def inner(request, *args, **kwargs):
if not getattr(view, 'csrf_exempt', False):
inner = csrf_protect(inner)
+ inner = ensure_csrf_cookie(inner)
+
return update_wrapper(inner, view)
def get_context(self, request):
@@ -123,7 +153,7 @@ def get_context(self, request):
def get_modules(self):
for k, v in self._registry.iteritems():
yield k, v[0]
-
+
def get_module(self, module):
return self._registry[module][0]
@@ -139,14 +169,14 @@ def render_to_string(self, template, context, request, current_app=None):
current_app = self.name
else:
current_app = '%s:%s' % (self.name, current_app)
-
+
if request:
context_instance = RequestContext(request, current_app=current_app)
else:
context_instance = None
context.update(self.get_context(request))
-
+
return render_to_string(template, context,
context_instance=context_instance
)
@@ -157,20 +187,20 @@ def render_to_response(self, template, context, request, current_app=None):
current_app = self.name
else:
current_app = '%s:%s' % (self.name, current_app)
-
+
if request:
context_instance = RequestContext(request, current_app=current_app)
else:
context_instance = None
context.update(self.get_context(request))
-
+
return render_to_response(template, context,
context_instance=context_instance
)
## Our views
-
+
def media(self, request, module, path):
"""
Serve static files below a given point in the directory structure.
@@ -179,7 +209,7 @@ def media(self, request, module, path):
document_root = os.path.join(NEXUS_ROOT, 'media')
else:
document_root = self.get_module(module).media_root
-
+
path = posixpath.normpath(urllib.unquote(path))
path = path.lstrip('/')
newpath = ''
@@ -210,7 +240,7 @@ def media(self, request, module, path):
response = HttpResponse(contents, mimetype=mimetype)
response["Last-Modified"] = http_date(statobj[stat.ST_MTIME])
response["Content-Length"] = len(contents)
- return response
+ return response
def login(self, request):
"Login form"
@@ -233,7 +263,7 @@ def login(self, request):
'form': form,
}, request)
login = never_cache(login)
-
+
def logout(self, request):
"Logs out user and redirects them to Nexus home"
from django.contrib.auth import logout
@@ -253,7 +283,7 @@ def dashboard(self, request):
# Show by default, unless a permission is required
if not module.permission or request.user.has_perm(module.permission):
module_set.append((module.get_dashboard_title(), module.render_on_dashboard(request), home_url))
-
+
return self.render_to_response('nexus/dashboard.html', {
'module_set': module_set,
}, request)
View
@@ -17,7 +17,7 @@ def run(self, *args, **kwargs):
setup(
name='nexus',
- version='0.2.2',
+ version='0.2.3',
author='David Cramer',
author_email='dcramer@gmail.com',
url='http://github.com/dcramer/nexus',

0 comments on commit d1f3771

Please sign in to comment.