First commit
authorChris Williams <chris@nitron.org>
Tue, 17 Mar 2009 23:05:57 +0000 (19:05 -0400)
committerChris Williams <chris@nitron.org>
Tue, 17 Mar 2009 23:05:57 +0000 (19:05 -0400)
21 files changed:
README [new file with mode: 0644]
cas_provider/__init__.py [new file with mode: 0644]
cas_provider/admin.py [new file with mode: 0644]
cas_provider/forms.py [new file with mode: 0644]
cas_provider/management/.svn/all-wcprops [new file with mode: 0644]
cas_provider/management/.svn/entries [new file with mode: 0644]
cas_provider/management/.svn/format [new file with mode: 0644]
cas_provider/management/.svn/text-base/__init__.py.svn-base [new file with mode: 0644]
cas_provider/management/__init__.py [new file with mode: 0644]
cas_provider/management/commands/.svn/all-wcprops [new file with mode: 0644]
cas_provider/management/commands/.svn/entries [new file with mode: 0644]
cas_provider/management/commands/.svn/format [new file with mode: 0644]
cas_provider/management/commands/.svn/text-base/__init__.py.svn-base [new file with mode: 0644]
cas_provider/management/commands/.svn/text-base/cleanupregistration.py.svn-base [new file with mode: 0644]
cas_provider/management/commands/__init__.py [new file with mode: 0644]
cas_provider/management/commands/cleanuptickets.py [new file with mode: 0644]
cas_provider/models.py [new file with mode: 0644]
cas_provider/urls.py [new file with mode: 0644]
cas_provider/utils.py [new file with mode: 0644]
cas_provider/views.py [new file with mode: 0644]
setup.py [new file with mode: 0644]

diff --git a/README b/README
new file mode 100644 (file)
index 0000000..4cc70b3
--- /dev/null
+++ b/README
@@ -0,0 +1,5 @@
+django-cas-provider
+
+Chris Williams <chris@nitron.org>
+
+TODO: Write documentation
diff --git a/cas_provider/__init__.py b/cas_provider/__init__.py
new file mode 100644 (file)
index 0000000..1a719b4
--- /dev/null
@@ -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 (file)
index 0000000..5091f8e
--- /dev/null
@@ -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 (file)
index 0000000..f270c37
--- /dev/null
@@ -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 (file)
index 0000000..1ea1964
--- /dev/null
@@ -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 (file)
index 0000000..30ca011
--- /dev/null
@@ -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
+\f
+commands
+dir
+\f
+__init__.py
+file
+
+
+
+
+2009-02-18T02:12:14.000000Z
+d41d8cd98f00b204e9800998ecf8427e
+2008-09-22T10:00:14.397881Z
+170
+ubernostrum
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0
+\f
diff --git a/cas_provider/management/.svn/format b/cas_provider/management/.svn/format
new file mode 100644 (file)
index 0000000..ec63514
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/cas_provider/management/__init__.py b/cas_provider/management/__init__.py
new file mode 100644 (file)
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 (file)
index 0000000..90c9ca7
--- /dev/null
@@ -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 (file)
index 0000000..58adc69
--- /dev/null
@@ -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
+\f
+__init__.py
+file
+
+
+
+
+2009-02-18T02:12:14.000000Z
+d41d8cd98f00b204e9800998ecf8427e
+2008-09-22T10:00:14.397881Z
+170
+ubernostrum
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+0
+\f
+cleanupregistration.py
+file
+
+
+
+
+2009-02-18T02:12:14.000000Z
+2fc6dce3e47cc12aafd11130c6c8b45e
+2008-09-22T10:00:14.397881Z
+170
+ubernostrum
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+630
+\f
diff --git a/cas_provider/management/commands/.svn/format b/cas_provider/management/commands/.svn/format
new file mode 100644 (file)
index 0000000..ec63514
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..cfaee32
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/cas_provider/management/commands/cleanuptickets.py b/cas_provider/management/commands/cleanuptickets.py
new file mode 100644 (file)
index 0000000..751e220
--- /dev/null
@@ -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 (file)
index 0000000..94023b7
--- /dev/null
@@ -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 (file)
index 0000000..8edc91a
--- /dev/null
@@ -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 (file)
index 0000000..501f63a
--- /dev/null
@@ -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 (file)
index 0000000..6fc19f6
--- /dev/null
@@ -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 (file)
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'],
+)