├── .gitignore
├── LICENSE
├── MANIFEST.in
├── README.md
├── django_xsession
├── __init__.py
├── middleware.py
├── templates
│ └── django_xsession
│ │ └── loader.html
└── templatetags
│ ├── __init__.py
│ └── django_xsession.py
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | django_xsession.egg-info
2 | build
3 | dist
4 | *.pyc
5 | *.swp
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2011 Factor AG (http://factor.ch/)
2 |
3 | This program is free software: you can redistribute it and/or modify it under
4 | the terms of the GNU Lesser General Public License as published by the Free
5 | Software Foundation, either version 3 of the License, or any later version.
6 |
7 | This program is distributed in the hope that it will be useful, but WITHOUT ANY
8 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
9 | PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
10 |
11 | You should have received a copy of the GNU Lesser General Public License along
12 | with this program. If not, see http://www.gnu.org/licenses/lgpl.html.
13 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 | include LICENSE
3 | recursive-include django_xsession/templates *.html
4 |
5 | recursive-exclude * *.pyc
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | django_xsession
2 | ===============
3 |
4 | django_xsession is a middleware that offers session sharing across multiple
5 | domains (using the same session backend obviously). Can be used to allow
6 | single sign-on across multiple websites.
7 |
8 |
9 |
10 | Install
11 | -------
12 |
13 | python setup.py install
14 |
15 |
16 | Usage
17 | -----
18 |
19 | Add django_xsession to your INSTALLED_APPS and load the XSessionMiddleware
20 | class. Then set the domain names you want to share the session cookie.
21 |
22 | settings.py snippet:
23 |
24 | ```python
25 | MIDDLEWARE_CLASSES = (
26 | 'django.middleware.common.CommonMiddleware',
27 | 'django.contrib.sessions.middleware.SessionMiddleware',
28 | 'django_xsession.middleware.XSessionMiddleware',
29 | 'django.middleware.csrf.CsrfViewMiddleware',
30 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
31 | 'django.contrib.messages.middleware.MessageMiddleware',
32 | )
33 |
34 | INSTALLED_APPS = (
35 | 'django.contrib.auth',
36 | 'django.contrib.contenttypes',
37 | 'django.contrib.sessions',
38 | 'django.contrib.sites',
39 | 'django.contrib.messages',
40 | 'django.contrib.staticfiles',
41 | 'django_xsession',
42 | )
43 |
44 | XSESSION_DOMAINS = ['www.domain1.org', 'www.domain2.org', 'www.domain3.org']
45 | ```
46 |
47 | You also need to add the xsession_loader to the head section of your base
48 | template.
49 |
50 | base.html (or whatever filename you use):
51 |
52 | ```html
53 | {% load django_xsession %}
54 |
55 |
56 | {% xsession_loader %}
57 |
58 |
59 | hello world
60 |
61 |
62 | ```
63 |
64 | You'll need to make sure that you're rendering the template with `RequestContext`, as well.
65 |
--------------------------------------------------------------------------------
/django_xsession/__init__.py:
--------------------------------------------------------------------------------
1 | __all__ = ['middleware', 'templatetags']
2 |
--------------------------------------------------------------------------------
/django_xsession/middleware.py:
--------------------------------------------------------------------------------
1 | # coding=utf-8
2 | from django.conf import settings
3 | from django.http import HttpResponse
4 | import time, datetime
5 |
6 | class XSessionMiddleware(object):
7 |
8 | def process_request(self, request):
9 |
10 | if 'REQUEST_URI' in request.META:
11 | uri_key = 'REQUEST_URI'
12 | elif 'RAW_URI' in request.META:
13 | uri_key = 'RAW_URI'
14 | elif 'PATH_INFO' in request.META:
15 | uri_key = 'PATH_INFO'
16 |
17 | path = request.META[uri_key]
18 |
19 | if path[0] == '/':
20 | path = path[1:]
21 |
22 | # Set XSession
23 | request.xsession = True
24 |
25 | # Skip regular requests
26 | loader_path = getattr(settings, 'XSESSION_FILENAME', 'xsession_loader.js')
27 | if path != loader_path:
28 | return
29 |
30 | if not hasattr(request, 'session') and not hasattr(request, 'user'):
31 | # Not far enough along in the django request cycle, so this is likely
32 | # a middleware response. Let's just return and do nothing here.
33 | return HttpResponse('', content_type="text/javascript")
34 |
35 | if not (hasattr(request, 'session') and request.session.keys()) and not (hasattr(request, 'user') and request.user.is_authenticated()):
36 | return HttpResponse('', content_type="text/javascript")
37 |
38 | # Get session cookie
39 | cookie = getattr(settings, 'SESSION_COOKIE_NAME', 'sessionid')
40 | sessionid = request.COOKIES[cookie]
41 |
42 | # Default age (see Django docs)
43 | age = getattr(settings, 'SESSION_COOKIE_AGE', 1209600)
44 | expire = int(time.time()) + age
45 | utc = datetime.datetime.utcfromtimestamp(expire)
46 |
47 | # Got session. Set sessionid and reload
48 | if settings.SESSION_COOKIE_HTTPONLY:
49 | javascript = "document.cookie='%s=%s; expires=%s'; document.cookie='set_httponly=1; expires=%s'; window.location.reload();" % (cookie, sessionid, utc, utc)
50 | else:
51 | javascript = "document.cookie='%s=%s; expires=%s'; window.location.reload();" % (cookie, sessionid, utc)
52 |
53 | return HttpResponse(javascript, content_type="text/javascript")
54 |
55 | def process_response(self, request, response):
56 | # Clear out expired session cookies. We need to do this because, by default, our Django session
57 | # cookies are set with httpOnly, meaning we can't clear them using our JS shim here.
58 | cookie = getattr(settings, 'SESSION_COOKIE_NAME', 'sessionid')
59 | if not hasattr(request, 'session') and not hasattr(request, 'user'):
60 | # Not far enough along in the django request cycle, so this is likely
61 | # a middleware response. Let's just return and do nothing here.
62 | return response
63 |
64 | has_session_or_auth = (
65 | (hasattr(request, 'session') and request.session.keys()) or
66 | (hasattr(request, 'user') and request.user.is_authenticated())
67 | )
68 | if request.COOKIES.get(cookie) and not has_session_or_auth:
69 | hostname = request.META.get('HTTP_HOST', '').split(':')[0]
70 | # Default value in Django settings is None for SESSION_COOKIE_DOMAIN, and None has no 'endswith' attribute
71 | session_domain = getattr(settings, 'SESSION_COOKIE_DOMAIN', '') or ''
72 | if session_domain.endswith(hostname):
73 | response.delete_cookie(cookie, domain=session_domain)
74 | else:
75 | response.delete_cookie(cookie, domain='')
76 | else:
77 | # If we just got a session cookie via our JS shim, we should re-add the cookie as httpOnly
78 | if request.COOKIES.get('set_httponly'):
79 | response.delete_cookie('set_httponly')
80 | age = getattr(settings, 'SESSION_COOKIE_AGE', 1209600)
81 | response.set_cookie(
82 | cookie,
83 | value=request.COOKIES[cookie],
84 | expires=age,
85 | domain=getattr(settings, 'SESSION_COOKIE_DOMAIN'),
86 | secure=getattr(settings, 'SESSION_COOKIE_SECURE'),
87 | httponly=True,
88 | )
89 |
90 | return response
91 |
--------------------------------------------------------------------------------
/django_xsession/templates/django_xsession/loader.html:
--------------------------------------------------------------------------------
1 | {% for domain in domains %}
2 |
3 | {% endfor %}
4 |
--------------------------------------------------------------------------------
/django_xsession/templatetags/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/badzong/django-xsession/44defa92fa6a816eea6be3299faf869240633a84/django_xsession/templatetags/__init__.py
--------------------------------------------------------------------------------
/django_xsession/templatetags/django_xsession.py:
--------------------------------------------------------------------------------
1 | import copy
2 | from django.conf import settings
3 | from django import template
4 |
5 | register = template.Library()
6 |
7 | @register.inclusion_tag('django_xsession/loader.html', takes_context=True)
8 | def xsession_loader(context):
9 |
10 | try:
11 | request = context['request']
12 | except KeyError:
13 | return {}
14 |
15 | # Check if XSessionMiddleware was loaded
16 | if not hasattr(request, 'xsession'):
17 | return {}
18 |
19 | if request.session.keys() or request.user.is_authenticated():
20 | return {}
21 |
22 | cookie = getattr(settings, 'SESSION_COOKIE_NAME', 'sessionid')
23 | if request.COOKIES.get(cookie, None):
24 | return {}
25 |
26 | # No session found
27 | host = request.META.get('HTTP_HOST', '').split(':')[0]
28 | if not host:
29 | return {}
30 |
31 | # protocol
32 | if request.is_secure():
33 | proto = 'https'
34 | else:
35 | proto = 'http'
36 |
37 | # Port
38 | port = request.META.get('SERVER_PORT', None)
39 | if port == '80' and proto == 'http':
40 | port = None
41 | elif port == '443' and proto == 'https':
42 | port = None
43 | else:
44 | port = str(port)
45 |
46 | # Build domain list, with support for subdomains
47 | domains = copy.copy(settings.XSESSION_DOMAINS)
48 | for domain in settings.XSESSION_DOMAINS:
49 | if host.endswith(domain):
50 | domains.remove(domain)
51 |
52 | render_context = {
53 | 'path': getattr(settings, 'XSESSION_FILENAME', 'xsession_loader.js'),
54 | 'domains': domains,
55 | 'proto': proto,
56 | 'port': port, # if port 8080, 8000 and etc
57 | }
58 |
59 | return render_context
60 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | setup(name='django_xsession',
4 | version='0.1',
5 | description='Django middleware that allows cookie sharing across multiple domains.',
6 | author='Factor AG',
7 | author_email='webmaster@factor.ch',
8 | url='https://github.com/FactorAG/django-xsession',
9 | license='LGPLv3',
10 | keywords='django single sign on cookie sharing',
11 | packages=find_packages(),
12 | include_package_data=True,
13 | platforms=['any'],
14 | classifiers=[
15 | 'Development Status :: 4 - Beta',
16 | 'Environment :: Web Environment',
17 | 'Framework :: Django',
18 | 'Intended Audience :: Developers',
19 | 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
20 | 'Operating System :: OS Independent',
21 | 'Programming Language :: Python :: 2',
22 | 'Topic :: Internet :: WWW/HTTP',
23 | 'Topic :: Software Development :: Libraries :: Python Modules',
24 | ],
25 | )
26 |
--------------------------------------------------------------------------------