Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add template tags and key_tuple for manual usage
  • Loading branch information
edcrewe committed Nov 4, 2012
1 parent a44beb7 commit a2c8156
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 60 deletions.
61 changes: 7 additions & 54 deletions cookieless/middleware.py
@@ -1,15 +1,14 @@
#-*- coding:utf-8 -*-import time #-*- coding:utf-8 -*-import time
import re, pdb, time import re, pdb, time


from urlparse import urlparse
from django.conf import settings from django.conf import settings
from django.utils.cache import patch_vary_headers from django.utils.cache import patch_vary_headers
from django.utils.http import cookie_date from django.utils.http import cookie_date
from django.utils.importlib import import_module from django.utils.importlib import import_module
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.contrib.sessions.middleware import SessionMiddleware from django.contrib.sessions.middleware import SessionMiddleware
# Obscure the session id when passing it around in HTML # Obscure the session id when passing it around in HTML
from cookieless.xteacrypt import crypt from cookieless.utils import CryptSession


LINKS_RE = r'<a(?P<pre_href>[^>]*?)href=["\'](?P<in_href>[^"\']*?)(?P<anchor>#\S+)?["\'](?P<post_href>[^>]*?)>' LINKS_RE = r'<a(?P<pre_href>[^>]*?)href=["\'](?P<in_href>[^"\']*?)(?P<anchor>#\S+)?["\'](?P<post_href>[^>]*?)>'


Expand All @@ -32,9 +31,8 @@ def __init__(self):
""" """
self._re_links = re.compile(LINKS_RE, re.I) self._re_links = re.compile(LINKS_RE, re.I)
self._re_forms = re.compile('</form>', re.I) self._re_forms = re.compile('</form>', re.I)

self._sesh = CryptSession()
self.standard_session = SessionMiddleware() self.standard_session = SessionMiddleware()
self.secret = settings.SECRET_KEY[:16]


def process_request(self, request): def process_request(self, request):
""" Check if we have the session key from a cookie, """ Check if we have the session key from a cookie,
Expand All @@ -43,10 +41,10 @@ def process_request(self, request):
name = settings.SESSION_COOKIE_NAME name = settings.SESSION_COOKIE_NAME
session_key = request.COOKIES.get(name, '') session_key = request.COOKIES.get(name, '')
if not session_key: if not session_key:
session_key = self._decrypt(request, session_key = self._sesh.decrypt(request,
request.POST.get(name, None)) request.POST.get(name, None))
if not session_key and getattr(settings, 'COOKIELESS_USE_GET', False): if not session_key and getattr(settings, 'COOKIELESS_USE_GET', False):
session_key = self._decrypt(request, session_key = self._sesh.decrypt(request,
request.GET.get(name, '')) request.GET.get(name, ''))
if session_key: if session_key:
request.COOKIES[name] = session_key request.COOKIES[name] = session_key
Expand Down Expand Up @@ -85,63 +83,18 @@ def process_response(self, request, response):
else: else:
return self.standard_session.process_response(request, response) return self.standard_session.process_response(request, response)


def _prepare_url(self, url):
patt = None
if url.find('?') == -1:
patt = '%s?'
else:
patt = '%s&amp;'
return patt % (url,)

def _encrypt(self, request, sessionid):
""" Avoid showing plain sessionids """
if not sessionid:
return ''
secret = self._secret(request)
return crypt(secret, sessionid).encode('hex')

def _decrypt(self, request, sessionid):
""" Avoid showing plain sessionids
Optionally require that a referer exists and matches the
whitelist, or reset the session
"""
if not sessionid:
return ''
secret = self._secret(request)
if getattr(settings, 'COOKIELESS_HOSTS', []):
referer = request.META.get('HTTP_REFERER', 'None')
if referer == 'None':
# End session unless a referer is passed
return ''
url = urlparse(referer)
if url.hostname not in settings.COOKIELESS_HOSTS:
err = '%s is unauthorised' % url.hostname
raise Exception(err)
return crypt(secret, sessionid.decode('hex'))

def _secret(self, request):
""" optionally make secret client dependent
"""
if getattr(settings, 'COOKIELESS_CLIENT_ID', False):
ip = request.META['REMOTE_ADDR']
agent = request.META['HTTP_USER_AGENT']
secret = crypt(self.secret, agent + ip)[:16]
return secret
else:
return self.secret

def nocookies_response(self, request, response): def nocookies_response(self, request, response):
""" Option to rewrite forms and urls to add session automatically """ """ Option to rewrite forms and urls to add session automatically """
name = settings.SESSION_COOKIE_NAME name = settings.SESSION_COOKIE_NAME
session_key = '' session_key = ''
if request.session.session_key and not request.path.startswith("/admin"): if request.session.session_key and not request.path.startswith("/admin"):
session_key = self._encrypt(request, request.session.session_key) session_key = self._sesh.encrypt(request, request.session.session_key)


if type(response) is HttpResponseRedirect: if type(response) is HttpResponseRedirect:
if not session_key: if not session_key:
session_key = "" session_key = ""
redirect_url = [x[1] for x in response.items() if x[0] == "Location"][0] redirect_url = [x[1] for x in response.items() if x[0] == "Location"][0]
redirect_url = self._prepare_url(redirect_url) redirect_url = self._sesh.prepare_url(redirect_url)
return HttpResponseRedirect('%s%s=%s' % (redirect_url, name, return HttpResponseRedirect('%s%s=%s' % (redirect_url, name,
session_key)) session_key))


Expand All @@ -152,7 +105,7 @@ def new_url(m):
anchor_value = m.groupdict().get("anchor") anchor_value = m.groupdict().get("anchor")
return_str = '<a%shref="%s%s=%s%s"%s>' % ( return_str = '<a%shref="%s%s=%s%s"%s>' % (
m.groupdict()['pre_href'], m.groupdict()['pre_href'],
self._prepare_url(m.groupdict()['in_href']), self._sesh.prepare_url(m.groupdict()['in_href']),
name, name,
session_key, session_key,
anchor_value, anchor_value,
Expand Down
Empty file.
76 changes: 76 additions & 0 deletions cookieless/templatetags/cookieless_tags.py
@@ -0,0 +1,76 @@
"""Add {% load cookieless %} after base to use these in a template
NB: settings: Need to add django.core.context_processors.request
if using manual tags so its available for templatetags/cookieless
import django.conf.global_settings as DEFAULT_SETTINGS
TEMPLATE_CONTEXT_PROCESSORS = DEFAULT_SETTINGS.TEMPLATE_CONTEXT_PROCESSORS + ('django.core.context_processors.request', )
"""

from django.conf import settings
from django import template
from django.template.defaultfilters import stringfilter, striptags
from django.utils.safestring import mark_safe
from django.utils.html import escape

from cookieless.utils import CryptSession

register = template.Library()

class BaseSessionNode(template.Node):

def __init__(self,):
self.request_var = template.Variable('request')
self._sesh = CryptSession()

def get_key(self, context):
request = self.request_var.resolve(context)
if request.session.session_key:
return self._sesh.encrypt(request,
request.session.session_key)
else:
return ''

class FormSessionNode(BaseSessionNode):

def render(self, context):
session_key = self.get_key(context)
if session_key:
html = '<input type="hidden" name="%s" value="%s" />'
return mark_safe(html % (settings.SESSION_COOKIE_NAME,
session_key))
else:
return ''

def session_form(parser, token):
return FormSessionNode()

register.tag('session_token', session_form)

class URLSessionNode(BaseSessionNode):

def __init__(self, url):
super(URLSessionNode, self).__init__()
self.url = self._sesh.prepare_url(url.replace('"',''))

def render(self, context):
session_key = self.get_key(context)
if session_key:
html = '"%s%s=%s"'
return mark_safe(html % (self.url,
settings.SESSION_COOKIE_NAME,
session_key))
else:
return ''

def session_filter(parser, token):
try:
taglist = token.split_contents() # Not really useful
except ValueError:
raise template.TemplateSyntaxError("%r error" % token.contents.split()[0])
if len(taglist) > 1:
url = taglist[1]
return URLSessionNode(url)

register.tag('session_url', session_filter)


12 changes: 9 additions & 3 deletions cookieless/tests/settings.py
@@ -1,18 +1,23 @@


# Django settings for cookieless_test project. # Django settings for cookieless_test project.


##### django-cookieless #####
# Rewrite URLs to add session id for no_cookies decorated views # Rewrite URLs to add session id for no_cookies decorated views
# (if False then all page navigation must be via form posts) # (if False then all page navigation must be via form posts)
COOKIELESS_USE_GET = True COOKIELESS_USE_GET = True
# Rewriting the response automatically rather than use manual <% session_token %> <% session_url %> # Rewriting the response automatically rather than use manual <% session_token %> <% session_url %>
COOKIELESS_REWRITE = True COOKIELESS_REWRITE = False
# NB: Need to add django.core.context_processors.request if using manual tags
# so its available for templatetags/cookieless
if not COOKIELESS_REWRITE:
import django.conf.global_settings as DEFAULT_SETTINGS
TEMPLATE_CONTEXT_PROCESSORS = DEFAULT_SETTINGS.TEMPLATE_CONTEXT_PROCESSORS + ('django.core.context_processors.request', )
# Use client ip and browser to encode session key, to add some CSRF protection without being able to use cookies. # Use client ip and browser to encode session key, to add some CSRF protection without being able to use cookies.
COOKIELESS_CLIENT_ID = True COOKIELESS_CLIENT_ID = True

# If this list is populated then only hosts that are specifically whitelisted# are allowed to post to the server. So any domains that the site is served # over should be added to the list. This helps protect against XSS attacks. # If this list is populated then only hosts that are specifically whitelisted# are allowed to post to the server. So any domains that the site is served # over should be added to the list. This helps protect against XSS attacks.

COOKIELESS_HOSTS = ['localhost', ] COOKIELESS_HOSTS = ['localhost', ]



DEBUG = True DEBUG = True
TEMPLATE_DEBUG = DEBUG TEMPLATE_DEBUG = DEBUG


Expand Down Expand Up @@ -130,6 +135,7 @@
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'cookieless.tests', 'cookieless.tests',
'cookieless',
# Uncomment the next line to enable the admin: # Uncomment the next line to enable the admin:
'django.contrib.admin', 'django.contrib.admin',
# Uncomment the next line to enable admin documentation: # Uncomment the next line to enable admin documentation:
Expand Down
4 changes: 3 additions & 1 deletion cookieless/tests/templates/classview.html
@@ -1,3 +1,4 @@
{% load cookieless_tags %}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html> <html>
<head> <head>
Expand All @@ -8,8 +9,9 @@
<h1>Class view</h1> <h1>Class view</h1>




<p><a href="/function-view.html">Function view</a></p> <p><a href={% session_url "/function-view.html" %}>Function view</a></p>
<form method="post" action="/function-view.html">{% csrf_token %} <form method="post" action="/function-view.html">{% csrf_token %}
{% session_token %}
<input type="text" name="test" value="test form" /> <input type="text" name="test" value="test form" />
<input type="submit" /> <input type="submit" />
</form> </form>
Expand Down
6 changes: 4 additions & 2 deletions cookieless/tests/views.py
Expand Up @@ -4,12 +4,14 @@
from django.http import HttpResponse from django.http import HttpResponse
import datetime import datetime
from django.utils.html import mark_safe from django.utils.html import mark_safe
from cookieless.utils import CryptSession


def my_function_view(request): def my_function_view(request):
""" Test function view """ """ Test function view with manually constructed sesh url """
request.session['funcview'] = 'my_function_view' request.session['funcview'] = 'my_function_view'
html = "<html><body><h1>Function view</h1>" html = "<html><body><h1>Function view</h1>"
html += '<p><a href="/">Class view</a></p><hr />' html += '<p><a href="/?%s=%s">Class view</a></p><hr />'
html = html % CryptSession().key_tuple(request)
html += session_data(request) + '</body></html>' html += session_data(request) + '</body></html>'
return HttpResponse(html) return HttpResponse(html)


Expand Down
61 changes: 61 additions & 0 deletions cookieless/utils.py
@@ -0,0 +1,61 @@
""" Obscure the session id when passing it around in HTML """
from django.conf import settings
from urlparse import urlparse
from cookieless.xteacrypt import crypt

class CryptSession(object):
""" Tool to generate encrypted session id for
middleware or templatetags
"""
def __init__(self):
self.secret = settings.SECRET_KEY[:16]

def prepare_url(self, url):
patt = None
if url.find('?') == -1:
patt = '%s?'
else:
patt = '%s&amp;'
return patt % (url,)

def encrypt(self, request, sessionid):
""" Avoid showing plain sessionids """
if not sessionid:
return ''
secret = self._secret(request)
return crypt(secret, sessionid).encode('hex')

def decrypt(self, request, sessionid):
""" Avoid showing plain sessionids
Optionally require that a referer exists and matches the
whitelist, or reset the session
"""
if not sessionid:
return ''
secret = self._secret(request)
if getattr(settings, 'COOKIELESS_HOSTS', []):
referer = request.META.get('HTTP_REFERER', 'None')
if referer == 'None':
# End session unless a referer is passed
return ''
url = urlparse(referer)
if url.hostname not in settings.COOKIELESS_HOSTS:
err = '%s is unauthorised' % url.hostname
raise Exception(err)
return crypt(secret, sessionid.decode('hex'))

def key_tuple(self, request):
""" For use in generated html """
return (settings.SESSION_COOKIE_NAME,
self.encrypt(request, request.session.session_key))

def _secret(self, request):
""" optionally make secret client dependent
"""
if getattr(settings, 'COOKIELESS_CLIENT_ID', False):
ip = request.META['REMOTE_ADDR']
agent = request.META['HTTP_USER_AGENT']
secret = crypt(self.secret, agent + ip)[:16]
return secret
else:
return self.secret

0 comments on commit a2c8156

Please sign in to comment.