Skip to content

Commit

Permalink
Merged 2.0 branch into trunk
Browse files Browse the repository at this point in the history
  • Loading branch information
Brodie Rao committed Jun 4, 2008
1 parent 2fac1a2 commit a8b59ad
Show file tree
Hide file tree
Showing 13 changed files with 467 additions and 300 deletions.
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include Makefile *.txt
13 changes: 13 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
PREFIX=/usr/local
PYTHON=python

all: build

build:
$(PYTHON) setup.py build
clean:
$(PYTHON) setup.py clean --all
find . -name '*.py[co]' -exec rm -f "{}" ';'
rm -rf build dist django_cas.egg-info temp
install:
$(PYTHON) setup.py install --prefix="$(PREFIX)"
89 changes: 0 additions & 89 deletions README

This file was deleted.

166 changes: 166 additions & 0 deletions README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
Django CAS
==========

`django_cas` is a CAS 1.0 and CAS 2.0 authentication backend for Django. It
allows you to use Django's built-in authentication mechanisms and `User`
model while adding support for CAS.

It also includes a middleware that intercepts calls to the original login
and logout pages and forwards them to the CASified versions, and adds
CAS support to the admin interface.


Installation
------------

Run `python setup.py install`, or place the `django_cas` directory in your
`PYTHONPATH` directly.

Now add it to the middleware and authentication backends in your settings.
Make sure you also have the authentication middleware installed. Here's what
mine looks like:

MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django_cas.middleware.CASMiddleware',
'django.middleware.doc.XViewMiddleware',
)

AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'django_cas.backends.CASBackend',
)

Set the following required setting in `settings.py`:

* `CAS_SERVER_URL`: This is the only setting you must explicitly define.
Set it to the base URL of your CAS source (e.g.
http://sso.some.edu/cas/).

Optional settings include:

* `CAS_ADMIN_PREFIX`: The URL prefix of the Django administration site.
If undefined, the CAS middleware will check the view being rendered to
see if it lives in `django.contrib.admin.views`.
* `CAS_IGNORE_REFERER`: If `True`, logging out of the application will
always send the user to the URL specified by `CAS_REDIRECT_URL`.
* `CAS_LOGOUT_COMPLETELY`: If `False`, logging out of the application
won't log the user out of CAS as well.
* `CAS_REDIRECT_URL`: Where to send a user after logging in or out if
there is no referrer and no next page set. Default is `/`.
* `CAS_VERSION`: The CAS protocol version to use. `'1'` and `'2'` are
supported, with `'2'` being the default.

Make sure your project knows how to log users in and out by adding these to
your URL mappings:

{{{
(r'^accounts/login/$', 'django_cas.views.login'),
(r'^accounts/logout/$', 'django_cas.views.logout'),
}}}

Users should now be able to log into your site (and staff into the
administration interface) using CAS.


Populating user data
--------------------

To add user data, subclass `CASBackend` and specify that as your
application's backend.

For example:

from django_cas.backend import CASBackend

class PopulatedCASBackend(CASBackend):
"""CAS authentication backend with user data populated from AD"""

def authenticate(self, ticket, service):
"""Authenticates CAS ticket and retrieves user data"""

user = super(PopulatedCASBackend, self).authenticate(
ticket, service)

# Connect to AD, modify user object, etc.

return user


Preventing Infinite Redirects
-----------------------------

Django's current implementation of its `permission_required` and
`user_passes_test` decorators (in `django.contrib.auth.decorators`) have a
known issue that can cause users to experience infinite redirects. The
decorators return the user to the login page, even if they're already logged
in, which causes a loop with SSO services like CAS.

`django_cas` provides fixed versions of these decorators in
`django_cas.decorators`. Usage is unchanged, and in the event that this issue
is fixed, the decorators should still work without issue.

For more information see http://code.djangoproject.com/ticket/4617.


Customizing the 403 Error Page
------------------------------

Django doesn't provide a simple way to customize 403 error pages, so you'll
have to make a response middleware that handles `HttpResponseForbidden`.

For example, in `views.py`:

from django.http import HttpResponseForbidden
from django.template import Context, loader

def forbidden(request, template_name='403.html'):
"""Default 403 handler"""

t = loader.get_template(template_name)
return HttpResponseForbidden(t.render(Context({})))

And in `middleware.py`:

from django.http import HttpResponseForbidden

from yourapp.views import forbidden

class Custom403Middleware(object):
"""Catches 403 responses and renders 403.html"""

def process_response(self, request, response):

if isinstance(response, HttpResponseForbidden):
return forbidden(request)
else:
return response

Now add `yourapp.middleware.Custom403Middleware` to your `MIDDLEWARE_CLASSES`
setting and create a template named `403.html`.

CAS 2.0 support
---------------

The CAS 2.0 protocol is supported in the same way that 1.0 is; no extensions
or new features from the CAS 2.0 specification are implemented. `elementtree`
is required to use this functionality. (`elementtree` is also included in
Python 2.5's standard library.)

Note: The CAS 3.x server uses the CAS 2.0 protocol. There is no CAS 3.0
protocol, though the CAS 3.x server does allow extensions to the protocol.


Differences Between Django CAS 1.0 and 2.0
------------------------------------------

Version 2.0 of `django_cas` breaks compatibility in some small ways, in order
simplify the library. The following settings have been removed:

* `CAS_LOGIN_URL` and `CAS_LOGOUT_URL`: Version 2.0 is capable of
determining these automatically.
* `CAS_POPULATE_USER`: Subclass `CASBackend` instead (see above).
* `CAS_REDIRECT_FIELD_NAME`: Django's own `REDIRECT_FIELD_NAME` is now
used unconditionally.
91 changes: 16 additions & 75 deletions django_cas/__init__.py
Original file line number Diff line number Diff line change
@@ -1,83 +1,24 @@
from urllib import urlopen, urlencode
from urlparse import urljoin
"""Django CAS 1.0/2.0 authentication backend"""

from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME

defaults = {'CAS_SERVER_URL': None,
'CAS_POPULATE_USER': None,
'CAS_ADMIN_PREFIX': None,
'CAS_LOGIN_URL': '/accounts/login/',
'CAS_LOGOUT_URL': '/accounts/logout/',
'CAS_REDIRECT_URL': '/',
'CAS_REDIRECT_FIELD_NAME': 'next',
}
__all__ = []

for key, value in defaults.iteritems():
_DEFAULTS = {
'CAS_ADMIN_PREFIX': None,
'CAS_IGNORE_REFERER': False,
'CAS_LOGOUT_COMPLETELY': True,
'CAS_REDIRECT_URL': '/',
'CAS_SERVER_URL': None,
'CAS_VERSION': '2',
}

for key, value in _DEFAULTS.iteritems():
try:
getattr(settings, key)
except AttributeError:
setattr(settings, key, value)

def get_populate_callback(callback):
if callable(callback):
return callback
try:
dot = callback.rindex('.')
except ValueError:
from django.core import exceptions
error = "Error importing CAS_POPULATE_USER callback: %s" % callback
raise exceptions.ImproperlyConfigured(error)
module_name, func_name = callback[:dot], callback[dot + 1:]
module = __import__(module_name, {}, {}, [''])
try:
func = getattr(module, func_name)
except AttributeError:
from django.core import exceptions
error = "Error importing CAS_POPULATE_USER callback: %s" % callback
raise exceptions.ImproperlyConfigured(error)
assert callable(func)
return func

def populate_user(user):
if settings.CAS_POPULATE_USER:
callback = get_populate_callback(settings.CAS_POPULATE_USER)
return callback(user)

def service_url(request, redirect_to=None):
from django.http import get_host
host = get_host(request)
protocol = request.is_secure() and 'https://' or 'http://'
service = protocol + host + request.path
if redirect_to:
if '?' in service:
service += '&'
else:
service += '?'
service += urlencode({settings.CAS_REDIRECT_FIELD_NAME: redirect_to})
return service

def redirect_url(request):
next = request.GET.get(settings.CAS_REDIRECT_FIELD_NAME)
if not next:
next = request.META.get('HTTP_REFERER', settings.CAS_REDIRECT_URL)
return next

def login_url(service):
params = {'service': service}
url = urljoin(settings.CAS_SERVER_URL, 'login') + '?' + urlencode(params)
if settings.DEBUG: print "Logging in: %s" % url
return url

def validate_url():
return urljoin(settings.CAS_SERVER_URL, 'validate')

def verify(ticket, service):
params = {'ticket': ticket, 'service': service}
url = validate_url() + '?' + urlencode(params)
if settings.DEBUG: print "Verifying ticket: %s" % url
page = urlopen(url)
verified = page.readline().strip()
username = page.readline().strip()
if verified == 'yes':
return username
return None
# Suppress errors from DJANGO_SETTINGS_MODULE not being set
except ImportError:
pass
Loading

0 comments on commit a8b59ad

Please sign in to comment.