Commit c052a734 by Ed Crewe

applied patch

parent 9a7ce56f
= Release Notes =
== Version X.X.X ==
* Added support for CAS proxy authentication.
== Version 2.0.3 ==
* Added `CAS_EXTRA_LOGIN_PARAMS` setting (patched contributed by frasern).
......
......@@ -43,7 +43,11 @@ Set the following required setting in `settings.py`:
http://sso.some.edu/cas/).
Optional settings include:
* `CAS_PROXY_CALLBACK`: The URL given to the CAS server in order to
initialize a proxy ticket. The ticket granting ticket will be sent
to this URL. The url must be registered in urls.py and handled
by django_cas.views.proxy_callback, e.g:
(r'^accounts/login/casProxyCallback$', 'django_cas.views.proxy_callback')
* `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`.
......@@ -125,6 +129,22 @@ is fixed, the decorators should still work without issue.
For more information see http://code.djangoproject.com/ticket/4617.
== CAS proxy tickets ==
Using CAS proxy tickets is quite a bit less trivial than ordinary tickets.
First of all the CAS server requires that the Django site can be accessed
via https, and it MUST have a properly signed certificate that the CAS
server can verify.
For the test-server this can be achieved using a tunneling application
such as stunnel. However this is not enough. The CAS proxy auhentication
requires that both the web browser and the CAS server simoultaneously can
make requests to the Django server, which the Django test server does not
support.
However, there is a Django app you can use to be able to start a threaded
test server hosted here:
http://github.com/jaylett/django_concurrent_test_server
== Customizing the 403 Error Page ==
......
......@@ -11,6 +11,7 @@ _DEFAULTS = {
'CAS_LOGOUT_COMPLETELY': True,
'CAS_REDIRECT_URL': '/',
'CAS_RETRY_LOGIN': False,
'CAS_PROXY_CALLBACK': None,
'CAS_SERVER_URL': None,
'CAS_VERSION': '2',
}
......
......@@ -4,8 +4,8 @@ from urllib import urlencode, urlopen
from urlparse import urljoin
from django.conf import settings
from django_cas.models import User
from django.core.exceptions import ObjectDoesNotExist
from django_cas.models import User, Tgt, PgtIOU
__all__ = ['CASBackend']
......@@ -40,15 +40,65 @@ def _verify_cas2(ticket, service):
except ImportError:
from elementtree import ElementTree
if settings.CAS_PROXY_CALLBACK:
params = {'ticket': ticket, 'service': service, 'pgtUrl': settings.CAS_PROXY_CALLBACK}
else:
params = {'ticket': ticket, 'service': service}
url = (urljoin(settings.CAS_SERVER_URL, 'proxyValidate') + '?' +
urlencode(params))
page = urlopen(url)
try:
response = page.read()
tree = ElementTree.fromstring(response)
if tree[0].tag.endswith('authenticationSuccess'):
username = tree[0][0].text
if len(tree[0]) >= 2 and tree[0][1].tag.endswith('proxyGrantingTicket'):
pgtIou = PgtIOU.objects.get(pgtIou = tree[0][1].text)
try:
tgt = Tgt.objects.get(username = username)
tgt.tgt = pgtIou.tgt
tgt.save()
except ObjectDoesNotExist:
Tgt.objects.create(username = username, tgt = pgtIou.tgt)
pgtIou.delete()
return username
else:
return None
finally:
page.close()
def verify_proxy_ticket(ticket, service):
"""Verifies CAS 2.0+ XML-based proxy ticket.
Returns username on success and None on failure.
"""
try:
from xml.etree import ElementTree
except ImportError:
from elementtree import ElementTree
params = {'ticket': ticket, 'service': service}
url = (urljoin(settings.CAS_SERVER_URL, 'proxyValidate') + '?' +
urlencode(params))
page = urlopen(url)
try:
response = page.read()
tree = ElementTree.fromstring(response)
if tree[0].tag.endswith('authenticationSuccess'):
return tree[0][0].text
username = tree[0][0].text
proxies = []
for element in tree[0][1]:
proxies.append(element.text)
return {"username": username, "proxies": proxies}
else:
return None
finally:
......
"""CAS authentication middleware"""
from urllib import urlencode
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth import logout as do_logout
from django.contrib.auth.views import login, logout
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django_cas.exceptions import CasTicketException
from django_cas.views import login as cas_login, logout as cas_logout
__all__ = ['CASMiddleware']
......@@ -50,3 +50,13 @@ class CASMiddleware(object):
return HttpResponseForbidden(error)
params = urlencode({REDIRECT_FIELD_NAME: request.get_full_path()})
return HttpResponseRedirect(reverse(cas_login) + '?' + params)
def process_exception(self, request, exception):
"""When we get a CasTicketException, that is probably caused by the ticket timing out.
So logout/login and get the same page again."""
if isinstance(exception, CasTicketException):
do_logout(request)
# This assumes that request.path requires authentication.
return HttpResponseRedirect(request.path)
else:
return None
from urlparse import urljoin
from urllib import urlencode, urlopen
from django.db import models
from django.conf import settings
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django_cas.exceptions import CasTicketException, CasConfigException
class Tgt(models.Model):
username = models.CharField(max_length = 255, unique = True)
tgt = models.CharField(max_length = 255)
def get_proxy_ticket_for(self, service):
"""Verifies CAS 2.0+ XML-based authentication ticket.
Returns username on success and None on failure.
"""
if not settings.CAS_PROXY_CALLBACK:
raise CasConfigException("No proxy callback set in settings")
try:
from xml.etree import ElementTree
except ImportError:
from elementtree import ElementTree
params = {'pgt': self.tgt, 'targetService': service}
url = (urljoin(settings.CAS_SERVER_URL, 'proxy') + '?' +
urlencode(params))
page = urlopen(url)
try:
response = page.read()
tree = ElementTree.fromstring(response)
if tree[0].tag.endswith('proxySuccess'):
return tree[0][0].text
else:
raise CasTicketException("Failed to get proxy ticket")
finally:
page.close()
class PgtIOU(models.Model):
pgtIou = models.CharField(max_length = 255, unique = True)
tgt = models.CharField(max_length = 255)
timestamp = models.DateTimeField(auto_now = True)
def get_tgt_for(user):
if not settings.CAS_PROXY_CALLBACK:
raise CasConfigException("No proxy callback set in settings")
try:
return Tgt.objects.get(username = user.username)
except ObjectDoesNotExist:
raise CasTicketException("no ticket found for user " + user.username)
......@@ -3,9 +3,10 @@
from urllib import urlencode
from urlparse import urljoin
from django.http import get_host, HttpResponseRedirect, HttpResponseForbidden
from django.http import get_host, HttpResponseRedirect, HttpResponseForbidden, HttpResponse
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME
from django_cas.models import PgtIOU
__all__ = ['login', 'logout']
......@@ -76,6 +77,7 @@ def login(request, next_page=None, required=False):
if ticket:
from django.contrib import auth
user = auth.authenticate(ticket=ticket, service=service)
if user is not None:
auth.login(request, user)
name = user.first_name or user.username
......@@ -102,3 +104,18 @@ def logout(request, next_page=None):
return HttpResponseRedirect(_logout_url(request, next_page))
else:
return HttpResponseRedirect(next_page)
def proxy_callback(request):
"""Handles CAS 2.0+ XML-based proxy callback call.
Stores the proxy granting ticket in the database for
future use.
"""
pgtIou = request.GET.get('pgtIou')
tgt = request.GET.get('pgtId')
if not (pgtIou and tgt):
return HttpResponse()
PgtIOU.objects.create(tgt = tgt, pgtIou = pgtIou)
return HttpResponse()
......@@ -37,5 +37,5 @@ to the admin interface.
name='django_cas',
packages=['django_cas'],
url='http://code.google.com/p/django-cas/',
version='2.0.3',
version='2.0.3-KTH-2',
)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment