From cc61c5969227c2d49544997ebbf074541029400b Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Tue, 17 Mar 2009 19:05:57 -0400 Subject: [PATCH 1/1] First commit --- README | 5 + cas_provider/__init__.py | 15 +++ cas_provider/admin.py | 11 +++ cas_provider/forms.py | 16 ++++ cas_provider/management/.svn/all-wcprops | 11 +++ cas_provider/management/.svn/entries | 65 +++++++++++++ cas_provider/management/.svn/format | 1 + .../.svn/text-base/__init__.py.svn-base | 0 cas_provider/management/__init__.py | 0 .../management/commands/.svn/all-wcprops | 17 ++++ cas_provider/management/commands/.svn/entries | 96 +++++++++++++++++++ cas_provider/management/commands/.svn/format | 1 + .../.svn/text-base/__init__.py.svn-base | 0 .../text-base/cleanupregistration.py.svn-base | 20 ++++ cas_provider/management/commands/__init__.py | 0 .../management/commands/cleanuptickets.py | 29 ++++++ cas_provider/models.py | 18 ++++ cas_provider/urls.py | 9 ++ cas_provider/utils.py | 19 ++++ cas_provider/views.py | 72 ++++++++++++++ setup.py | 14 +++ 21 files changed, 419 insertions(+) create mode 100644 README create mode 100644 cas_provider/__init__.py create mode 100644 cas_provider/admin.py create mode 100644 cas_provider/forms.py create mode 100644 cas_provider/management/.svn/all-wcprops create mode 100644 cas_provider/management/.svn/entries create mode 100644 cas_provider/management/.svn/format create mode 100644 cas_provider/management/.svn/text-base/__init__.py.svn-base create mode 100644 cas_provider/management/__init__.py create mode 100644 cas_provider/management/commands/.svn/all-wcprops create mode 100644 cas_provider/management/commands/.svn/entries create mode 100644 cas_provider/management/commands/.svn/format create mode 100644 cas_provider/management/commands/.svn/text-base/__init__.py.svn-base create mode 100644 cas_provider/management/commands/.svn/text-base/cleanupregistration.py.svn-base create mode 100644 cas_provider/management/commands/__init__.py create mode 100644 cas_provider/management/commands/cleanuptickets.py create mode 100644 cas_provider/models.py create mode 100644 cas_provider/urls.py create mode 100644 cas_provider/utils.py create mode 100644 cas_provider/views.py create mode 100644 setup.py diff --git a/README b/README new file mode 100644 index 0000000..4cc70b3 --- /dev/null +++ b/README @@ -0,0 +1,5 @@ +django-cas-provider + +Chris Williams + +TODO: Write documentation diff --git a/cas_provider/__init__.py b/cas_provider/__init__.py new file mode 100644 index 0000000..1a719b4 --- /dev/null +++ b/cas_provider/__init__.py @@ -0,0 +1,15 @@ +from django.conf import settings + +__all__ = [] + +_DEFAULTS = { + 'CAS_TICKET_EXPIRATION': 5, # In minutes +} + +for key, value in _DEFAULTS.iteritems(): + try: + getattr(settings, key) + except AttributeError: + setattr(settings, key, value) + except ImportError: + pass \ No newline at end of file diff --git a/cas_provider/admin.py b/cas_provider/admin.py new file mode 100644 index 0000000..5091f8e --- /dev/null +++ b/cas_provider/admin.py @@ -0,0 +1,11 @@ +from django.contrib import admin + +from models import ServiceTicket, LoginTicket + +class ServiceTicketAdmin(admin.ModelAdmin): + pass +admin.site.register(ServiceTicket, ServiceTicketAdmin) + +class LoginTicketAdmin(admin.ModelAdmin): + pass +admin.site.register(LoginTicket, LoginTicketAdmin) \ No newline at end of file diff --git a/cas_provider/forms.py b/cas_provider/forms.py new file mode 100644 index 0000000..f270c37 --- /dev/null +++ b/cas_provider/forms.py @@ -0,0 +1,16 @@ +from django import forms +from django.contrib.auth.forms import AuthenticationForm +from django.contrib.auth import authenticate + +from utils import create_login_ticket + +class LoginForm(forms.Form): + username = forms.CharField(max_length=30) + password = forms.CharField(widget=forms.PasswordInput) + #warn = forms.BooleanField(required=False) # TODO: Implement + lt = forms.CharField(widget=forms.HiddenInput, initial=create_login_ticket) + def __init__(self, service=None, renew=None, gateway=None, request=None, *args, **kwargs): + super(LoginForm, self).__init__(*args, **kwargs) + self.request = request + if service is not None: + self.fields['service'] = forms.CharField(widget=forms.HiddenInput, initial=service) \ No newline at end of file diff --git a/cas_provider/management/.svn/all-wcprops b/cas_provider/management/.svn/all-wcprops new file mode 100644 index 0000000..1ea1964 --- /dev/null +++ b/cas_provider/management/.svn/all-wcprops @@ -0,0 +1,11 @@ +K 25 +svn:wc:ra_dav:version-url +V 47 +/svn/!svn/ver/170/trunk/registration/management +END +__init__.py +K 25 +svn:wc:ra_dav:version-url +V 59 +/svn/!svn/ver/170/trunk/registration/management/__init__.py +END diff --git a/cas_provider/management/.svn/entries b/cas_provider/management/.svn/entries new file mode 100644 index 0000000..30ca011 --- /dev/null +++ b/cas_provider/management/.svn/entries @@ -0,0 +1,65 @@ +9 + +dir +170 +http://django-registration.googlecode.com/svn/trunk/registration/management +http://django-registration.googlecode.com/svn + + + +2008-09-22T10:00:14.397881Z +170 +ubernostrum + + +svn:special svn:externals svn:needs-lock + + + + + + + + + + + +b970a8a1-db28-0410-9b79-8b7e580363d3 + +commands +dir + +__init__.py +file + + + + +2009-02-18T02:12:14.000000Z +d41d8cd98f00b204e9800998ecf8427e +2008-09-22T10:00:14.397881Z +170 +ubernostrum + + + + + + + + + + + + + + + + + + + + + +0 + diff --git a/cas_provider/management/.svn/format b/cas_provider/management/.svn/format new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/cas_provider/management/.svn/format @@ -0,0 +1 @@ +9 diff --git a/cas_provider/management/.svn/text-base/__init__.py.svn-base b/cas_provider/management/.svn/text-base/__init__.py.svn-base new file mode 100644 index 0000000..e69de29 diff --git a/cas_provider/management/__init__.py b/cas_provider/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cas_provider/management/commands/.svn/all-wcprops b/cas_provider/management/commands/.svn/all-wcprops new file mode 100644 index 0000000..90c9ca7 --- /dev/null +++ b/cas_provider/management/commands/.svn/all-wcprops @@ -0,0 +1,17 @@ +K 25 +svn:wc:ra_dav:version-url +V 56 +/svn/!svn/ver/170/trunk/registration/management/commands +END +__init__.py +K 25 +svn:wc:ra_dav:version-url +V 68 +/svn/!svn/ver/170/trunk/registration/management/commands/__init__.py +END +cleanupregistration.py +K 25 +svn:wc:ra_dav:version-url +V 79 +/svn/!svn/ver/170/trunk/registration/management/commands/cleanupregistration.py +END diff --git a/cas_provider/management/commands/.svn/entries b/cas_provider/management/commands/.svn/entries new file mode 100644 index 0000000..58adc69 --- /dev/null +++ b/cas_provider/management/commands/.svn/entries @@ -0,0 +1,96 @@ +9 + +dir +170 +http://django-registration.googlecode.com/svn/trunk/registration/management/commands +http://django-registration.googlecode.com/svn + + + +2008-09-22T10:00:14.397881Z +170 +ubernostrum + + +svn:special svn:externals svn:needs-lock + + + + + + + + + + + +b970a8a1-db28-0410-9b79-8b7e580363d3 + +__init__.py +file + + + + +2009-02-18T02:12:14.000000Z +d41d8cd98f00b204e9800998ecf8427e +2008-09-22T10:00:14.397881Z +170 +ubernostrum + + + + + + + + + + + + + + + + + + + + + +0 + +cleanupregistration.py +file + + + + +2009-02-18T02:12:14.000000Z +2fc6dce3e47cc12aafd11130c6c8b45e +2008-09-22T10:00:14.397881Z +170 +ubernostrum + + + + + + + + + + + + + + + + + + + + + +630 + diff --git a/cas_provider/management/commands/.svn/format b/cas_provider/management/commands/.svn/format new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/cas_provider/management/commands/.svn/format @@ -0,0 +1 @@ +9 diff --git a/cas_provider/management/commands/.svn/text-base/__init__.py.svn-base b/cas_provider/management/commands/.svn/text-base/__init__.py.svn-base new file mode 100644 index 0000000..e69de29 diff --git a/cas_provider/management/commands/.svn/text-base/cleanupregistration.py.svn-base b/cas_provider/management/commands/.svn/text-base/cleanupregistration.py.svn-base new file mode 100644 index 0000000..cfaee32 --- /dev/null +++ b/cas_provider/management/commands/.svn/text-base/cleanupregistration.py.svn-base @@ -0,0 +1,20 @@ +""" +A management command which deletes expired accounts (e.g., +accounts which signed up but never activated) from the database. + +Calls ``RegistrationProfile.objects.delete_expired_users()``, which +contains the actual logic for determining which accounts are deleted. + +""" + +from django.core.management.base import NoArgsCommand +from django.core.management.base import CommandError + +from registration.models import RegistrationProfile + + +class Command(NoArgsCommand): + help = "Delete expired user registrations from the database" + + def handle_noargs(self, **options): + RegistrationProfile.objects.delete_expired_users() diff --git a/cas_provider/management/commands/__init__.py b/cas_provider/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cas_provider/management/commands/cleanuptickets.py b/cas_provider/management/commands/cleanuptickets.py new file mode 100644 index 0000000..751e220 --- /dev/null +++ b/cas_provider/management/commands/cleanuptickets.py @@ -0,0 +1,29 @@ +""" +A management command which deletes expired service tickets (e.g., +from the database. + +Calls ``ServiceTickets.objects.delete_expired_users()``, which +contains the actual logic for determining which accounts are deleted. + +""" + +from django.core.management.base import NoArgsCommand +from django.core.management.base import CommandError +from django.conf import settings + +import datetime + +from cas_provider.models import ServiceTicket + +class Command(NoArgsCommand): + help = "Delete expired service tickets from the database" + + def handle_noargs(self, **options): + tickets = ServiceTicket.objects.all() + for ticket in tickets: + expiration = datetime.timedelta(minutes=settings.CAS_TICKET_EXPIRATION) + if datetime.datetime.now() > ticket.created + expiration: + print "Deleting %s..." % ticket.ticket + ticket.delete() + else: + print "%s not expired..." % ticket.ticket \ No newline at end of file diff --git a/cas_provider/models.py b/cas_provider/models.py new file mode 100644 index 0000000..94023b7 --- /dev/null +++ b/cas_provider/models.py @@ -0,0 +1,18 @@ +from django.db import models +from django.contrib.auth.models import User + +class ServiceTicket(models.Model): + user = models.ForeignKey(User) + service = models.URLField(verify_exists=False) + ticket = models.CharField(max_length=256) + created = models.DateTimeField(auto_now=True) + + def __unicode__(self): + return "%s (%s) - %s" % (self.user.username, self.service, self.created) + +class LoginTicket(models.Model): + ticket = models.CharField(max_length=32) + created = models.DateTimeField(auto_now=True) + + def __unicode__(self): + return "%s - %s" % (self.ticket, self.created) \ No newline at end of file diff --git a/cas_provider/urls.py b/cas_provider/urls.py new file mode 100644 index 0000000..8edc91a --- /dev/null +++ b/cas_provider/urls.py @@ -0,0 +1,9 @@ +from django.conf.urls.defaults import * + +from views import * + +urlpatterns = patterns('', + url(r'^login/', login), + url(r'^validate/', validate), + url(r'^logout/', logout), +) \ No newline at end of file diff --git a/cas_provider/utils.py b/cas_provider/utils.py new file mode 100644 index 0000000..501f63a --- /dev/null +++ b/cas_provider/utils.py @@ -0,0 +1,19 @@ +from random import Random +import string + +from models import ServiceTicket, LoginTicket + +def _generate_string(length=8, chars=string.letters + string.digits): + return ''.join(Random().sample(string.letters+string.digits, length)) + +def create_service_ticket(user, service): + ticket_string = 'ST-' + _generate_string(29) # Total ticket length = 29 + 3 = 32 + ticket = ServiceTicket(service=service, user=user, ticket=ticket_string) + ticket.save() + return ticket + +def create_login_ticket(): + ticket_string = 'LT-' + _generate_string(29) + ticket = LoginTicket(ticket=ticket_string) + ticket.save() + return ticket_string \ No newline at end of file diff --git a/cas_provider/views.py b/cas_provider/views.py new file mode 100644 index 0000000..6fc19f6 --- /dev/null +++ b/cas_provider/views.py @@ -0,0 +1,72 @@ +from django.http import HttpResponse, HttpResponseForbidden, HttpResponseRedirect +from django.shortcuts import get_object_or_404, render_to_response, get_list_or_404 +from django.core.urlresolvers import reverse +from django.core.exceptions import SuspiciousOperation +from django.template import RequestContext +from django.contrib.auth.models import User +from django.contrib.auth import authenticate +from django.contrib.auth import login as auth_login, logout as auth_logout + +from forms import LoginForm +from models import ServiceTicket, LoginTicket +from utils import create_service_ticket + +__all__ = ['login', 'validate', 'logout'] + +def login(request, template_name='cas/login.html', success_redirect='/accounts/'): + service = request.GET.get('service', None) + if request.user.is_authenticated(): + if service is not None: + ticket = create_service_ticket(request.user, service) + if service.find('?') == -1: + return HttpResponseRedirect(service + '?ticket=' + ticket.ticket) + else: + return HttpResponseRedirect(service + '&ticket=' + ticket.ticket) + else: + return HttpResponseRedirect(success_redirect) + errors = [] + if request.method == 'POST': + username = request.POST.get('username', None) + password = request.POST.get('password', None) + service = request.POST.get('service', None) + lt = request.POST.get('lt', None) + + try: + login_ticket = LoginTicket.objects.get(ticket=lt) + except: + errors.append('Login ticket expired. Please try again.') + else: + login_ticket.delete() + user = authenticate(username=username, password=password) + if user is not None: + if user.is_active: + auth_login(request, user) + if service is not None: + ticket = create_service_ticket(user, service) + return HttpResponseRedirect(service + '?ticket=' + ticket.ticket) + else: + return HttpResponseRedirect(success_redirect) + else: + errors.append('This account is disabled.') + else: + errors.append('Incorrect username and/or password.') + form = LoginForm(service) + return render_to_response(template_name, {'form': form, 'errors': errors}, context_instance=RequestContext(request)) + +def validate(request): + service = request.GET.get('service', None) + ticket_string = request.GET.get('ticket', None) + if service is not None and ticket_string is not None: + try: + ticket = ServiceTicket.objects.get(ticket=ticket_string) + username = ticket.user.username + ticket.delete() + return HttpResponse("yes\n\r%s\n\r" % username) + except: + pass + return HttpResponse("no\n\r\n\r") + +def logout(request, template_name='cas/logout.html'): + url = request.GET.get('url', None) + auth_logout(request) + return render_to_response(template_name, {'url': url}, context_instance=RequestContext(request)) \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..8f06deb --- /dev/null +++ b/setup.py @@ -0,0 +1,14 @@ + +from setuptools import setup, find_packages + +setup( + name='django-cas-provider', + version='0.1dev', + description='A "provider" for the Central Authentication Service (http://jasig.org/cas)', + author='Chris Williams', + author_email='chris@nitron.org', + url='http://nitron.org/', + packages=find_packages(), + zip_safe=False, + install_requires=['setuptools'], +) -- 2.20.1