├── .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 | --------------------------------------------------------------------------------