From: Alex Kamedov Date: Sun, 24 Apr 2011 15:26:33 +0000 (+0600) Subject: Merge branch 'develop' X-Git-Tag: 22.4~32^2~20 X-Git-Url: https://git.mdrn.pl/django-cas-provider.git/commitdiff_plain/73bfc6b53448f5f1d10d9b3f58f2cf4f3756be41?hp=34cdae232d0d9a1b26133b7dd39ab484243d6493 Merge branch 'develop' --- diff --git a/README.rst b/README.rst index d313340..bc2874d 100644 --- a/README.rst +++ b/README.rst @@ -40,4 +40,64 @@ SETTINGS ========= CAS_TICKET_EXPIRATION - minutes to tickets expiration (default is 5 minutes) +CAS_CHECK_SERVICE - check if ticket service is equal with service GET argument + +PROTOCOL DOCUMENTATION +===================== + +* `CAS Protocol ` +* `CAS 1 Architecture ` +* `CAS 2 Architecture ` +* `Proxy Authentication ` +* `CAS – Central Authentication Service ` +* `Proxy CAS Walkthrough ` + +PROVIDED VIEWS +============= + +login +--------- + +It has not required arguments. + +Optional arguments: + +* template_name - login form template name (default is 'cas/login.html') +* success_redirect - redirect after successful login if service GET argument is not provided + (default is settings.LOGIN_REDIRECT_URL) +* warn_template_name - warning page template name to allow login user to service if he + already authenticated in SSO (default is 'cas/warn.html') + +If request.GET has 'warn' argument - it shows warning message if user has already +authenticated in SSO instead of generate Service Ticket and redirect. + +logout +----------- + +This destroys a client's single sign-on CAS session. The ticket-granting cookie is destroyed, +and subsequent requests to login view will not obtain service tickets until the user again +presents primary credentials (and thereby establishes a new single sign-on session). + +It has not required arguments. + +Optional arguments: + +* template_name - template name for page with successful logout message (default is 'cas/logout.html') + +validate +------------- + +It checks the validity of a service ticket. It is part of the CAS 1.0 protocol and thus does +not handle proxy authentication. + +It has not arguments. + +service_validate +------------------------- + +It checks the validity of a service ticket and returns an XML-fragment response via CAS 2.0 protocol. +Work with proxy is not supported yet. + +It has not arguments. + diff --git a/cas_provider/__init__.py b/cas_provider/__init__.py index 1a719b4..91b3b2c 100644 --- a/cas_provider/__init__.py +++ b/cas_provider/__init__.py @@ -4,6 +4,7 @@ __all__ = [] _DEFAULTS = { 'CAS_TICKET_EXPIRATION': 5, # In minutes + 'CAS_CHECK_SERVICE': False, } for key, value in _DEFAULTS.iteritems(): @@ -12,4 +13,4 @@ for key, value in _DEFAULTS.iteritems(): except AttributeError: setattr(settings, key, value) except ImportError: - pass \ No newline at end of file + pass diff --git a/cas_provider/fixtures/cas_users.json b/cas_provider/fixtures/cas_users.json new file mode 100644 index 0000000..f31d5cb --- /dev/null +++ b/cas_provider/fixtures/cas_users.json @@ -0,0 +1,130 @@ +[ + { + "pk": 1, + "model": "auth.group", + "fields": { + "name": "editor", + "permissions": [] + } + }, + { + "pk": 2, + "model": "auth.group", + "fields": { + "name": "author", + "permissions": [] + } + }, + { + "pk": 1, + "model": "auth.user", + "fields": { + "username": "root", + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": true, + "is_staff": true, + "last_login": "2011-04-24 11:29:11", + "groups": [], + "user_permissions": [], + "password": "sha1$602c5$ba8608296f6bfcb352e978084b337a90d586ecc3", + "email": "root@example.com", + "date_joined": "2010-07-04 13:33:14" + } + }, + { + "pk": 26, + "model": "auth.user", + "fields": { + "username": "active", + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2011-04-01 12:42:53", + "groups": [], + "user_permissions": [], + "password": "sha1$7dfb4$d19f8340a01b597089dfde6dc17bc5288c1f863e", + "email": "active@example.com", + "date_joined": "2011-04-01 11:12:45" + } + }, + { + "pk": 30, + "model": "auth.user", + "fields": { + "username": "author", + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": false, + "is_staff": true, + "last_login": "2011-04-24 11:32:16", + "groups": [ + 2 + ], + "user_permissions": [], + "password": "sha1$6c580$01509bea19e3ade9f1bcf303205a7cb10ce6762d", + "email": "", + "date_joined": "2011-04-24 11:32:16" + } + }, + { + "pk": 29, + "model": "auth.user", + "fields": { + "username": "editor", + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": false, + "is_staff": true, + "last_login": "2011-04-24 11:31:50", + "groups": [ + 1 + ], + "user_permissions": [], + "password": "sha1$3be01$b6aa05c61fc52edae3055c55e160d4cfd4756d91", + "email": "editor@exapmle.com", + "date_joined": "2011-04-24 11:31:50" + } + }, + { + "pk": 27, + "model": "auth.user", + "fields": { + "username": "nonactive", + "first_name": "", + "last_name": "", + "is_active": false, + "is_superuser": false, + "is_staff": false, + "last_login": "2011-04-24 11:31:00", + "groups": [], + "user_permissions": [], + "password": "sha1$0bf10$d60f146d15e4fe3cb0de5a607a17902d0f63a95c", + "email": "", + "date_joined": "2011-04-24 11:31:00" + } + }, + { + "pk": 28, + "model": "auth.user", + "fields": { + "username": "staff", + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": false, + "is_staff": true, + "last_login": "2011-04-24 11:31:26", + "groups": [], + "user_permissions": [], + "password": "sha1$5df85$bb4c1894a866fb86465d28831000af20316233d5", + "email": "staff@example.com", + "date_joined": "2011-04-24 11:31:26" + } + } +] \ No newline at end of file diff --git a/cas_provider/forms.py b/cas_provider/forms.py index 80b8913..47c2fdc 100644 --- a/cas_provider/forms.py +++ b/cas_provider/forms.py @@ -1,15 +1,52 @@ from django import forms +from django.conf import settings +from django.contrib.auth import authenticate +from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ +from models import LoginTicket +import datetime + + +__all__ = ['LoginForm', ] -from utils import create_login_ticket class LoginForm(forms.Form): username = forms.CharField(max_length=30, label=_('username')) password = forms.CharField(widget=forms.PasswordInput, label=_('password')) - #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): + lt = forms.CharField(widget=forms.HiddenInput) + service = forms.CharField(widget=forms.HiddenInput, required=False) + + def __init__(self, *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 + self._user = None + + def clean_lt(self): + ticket = self.cleaned_data['lt'] + timeframe = datetime.datetime.now() - \ + datetime.timedelta(minutes=settings.CAS_TICKET_EXPIRATION) + try: + return LoginTicket.objects.get(ticket=ticket, created__gte=timeframe) + except LoginTicket.DoesNotExist: + raise ValidationError(_('Login ticket expired. Please try again.')) + return ticket + + def clean(self): + username = self.cleaned_data.get('username') + password = self.cleaned_data.get('password') + user = authenticate(username=username, password=password) + if user is None: + raise ValidationError(_('Incorrect username and/or password.')) + if not user.is_active: + raise ValidationError(_('This account is disabled.')) + self._user = user + self.cleaned_data.get('lt').delete() + return self.cleaned_data + + def get_user(self): + return self._user + + def get_errors(self): + errors = [] + for k, error in self.errors.items(): + errors += [e for e in error] + return errors diff --git a/cas_provider/locale/ru/LC_MESSAGES/django.mo b/cas_provider/locale/ru/LC_MESSAGES/django.mo index b0e243a..c02ed63 100644 Binary files a/cas_provider/locale/ru/LC_MESSAGES/django.mo and b/cas_provider/locale/ru/LC_MESSAGES/django.mo differ diff --git a/cas_provider/locale/ru/LC_MESSAGES/django.po b/cas_provider/locale/ru/LC_MESSAGES/django.po index 8f3cbec..42a8506 100644 --- a/cas_provider/locale/ru/LC_MESSAGES/django.po +++ b/cas_provider/locale/ru/LC_MESSAGES/django.po @@ -1,12 +1,12 @@ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# Volf , 2011. +# Alex Kamedov , 2011. msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-04-07 11:57+0600\n" +"POT-Creation-Date: 2011-04-24 18:51+0600\n" "PO-Revision-Date: 2011-04-07 12:01+0600\n" "Last-Translator: Volf \n" "Language-Team: delux\n" @@ -14,58 +14,58 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%" -"10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Generator: Virtaal 0.6.1\n" -#: forms.py:9 +#: forms.py:14 msgid "username" msgstr "имя пользователя" -#: forms.py:10 +#: forms.py:15 msgid "password" msgstr "пароль" -#: models.py:6 -msgid "user" -msgstr "пользователь" +#: forms.py:30 +msgid "Login ticket expired. Please try again." +msgstr "Истек срок действия билета входа. Пожалуйста, попробуйте еще раз." -#: models.py:7 -msgid "service" -msgstr "сервис" +#: forms.py:38 +msgid "Incorrect username and/or password." +msgstr "Неверное имя пользователя и/или пароль." + +#: forms.py:40 +msgid "This account is disabled." +msgstr "Эта учетная запись отключена." -#: models.py:8 models.py:19 +#: models.py:14 msgid "ticket" msgstr "билет" -#: models.py:9 models.py:20 +#: models.py:15 msgid "created" msgstr "создан" -#: models.py:12 +#: models.py:34 +msgid "user" +msgstr "пользователь" + +#: models.py:35 +msgid "service" +msgstr "сервис" + +#: models.py:40 msgid "Service Ticket" msgstr "Билет для сервиса" -#: models.py:13 +#: models.py:41 msgid "Service Tickets" msgstr "Билеты для сервисов" -#: models.py:23 +#: models.py:59 msgid "Login Ticket" msgstr "Билет для входа" -#: models.py:24 +#: models.py:60 msgid "Login Tickets" msgstr "Билеты для входа" - -#: views.py:36 -msgid "Login ticket expired. Please try again." -msgstr "Истек срок действия билета входа. Пожалуйста, попробуйте еще раз." - -#: views.py:49 -msgid "This account is disabled." -msgstr "Эта учетная запись отключена." - -#: views.py:51 -msgid "Incorrect username and/or password." -msgstr "Неверное имя пользователя и/или пароль." diff --git a/cas_provider/models.py b/cas_provider/models.py index 6eecc3e..ec4b695 100644 --- a/cas_provider/models.py +++ b/cas_provider/models.py @@ -1,29 +1,60 @@ -from django.db import models from django.contrib.auth.models import User +from django.db import models from django.utils.translation import ugettext_lazy as _ +from random import Random +import string +import urllib +import urlparse + __all__ = ['ServiceTicket', 'LoginTicket'] -class ServiceTicket(models.Model): + +class BaseTicket(models.Model): + ticket = models.CharField(_('ticket'), max_length=32) + created = models.DateTimeField(_('created'), auto_now=True) + + class Meta: + abstract = True + + def __init__(self, *args, **kwargs): + if 'ticket' not in kwargs: + kwargs['ticket'] = self._generate_ticket() + super(BaseTicket, self).__init__(*args, **kwargs) + + def __unicode__(self): + return self.ticket + + def _generate_ticket(self, length=29, chars=string.ascii_letters + string.digits): + """ Generates a random string of the requested length. Used for creation of tickets. """ + return u"%s-%s" % (self.prefix, ''.join(Random().sample(chars, length))) + + +class ServiceTicket(BaseTicket): user = models.ForeignKey(User, verbose_name=_('user')) service = models.URLField(_('service'), verify_exists=False) - ticket = models.CharField(_('ticket'), max_length=256) - created = models.DateTimeField(_('created'), auto_now=True) + + prefix = 'ST' class Meta: verbose_name = _('Service Ticket') verbose_name_plural = _('Service Tickets') - def __unicode__(self): - return "%s (%s) - %s" % (self.user.username, self.service, self.created) + def get_redirect_url(self): + parsed = urlparse.urlparse(self.service) + query = urlparse.parse_qs(parsed.query) + query['ticket'] = [self.ticket] + query = [ ((k, v) if len(v) > 1 else (k, v[0])) for k, v in query.iteritems()] + parsed = urlparse.ParseResult(parsed.scheme, parsed.netloc, + parsed.path, parsed.params, + urllib.urlencode(query), parsed.fragment) + return parsed.geturl() -class LoginTicket(models.Model): - ticket = models.CharField(_('ticket'), max_length=32) - created = models.DateTimeField(_('created'), auto_now=True) + +class LoginTicket(BaseTicket): + + prefix = 'LT' class Meta: verbose_name = _('Login Ticket') verbose_name_plural = _('Login Tickets') - - def __unicode__(self): - return "%s - %s" % (self.ticket, self.created) diff --git a/cas_provider/templates/cas/warn.html b/cas_provider/templates/cas/warn.html new file mode 100644 index 0000000..58f4ed8 --- /dev/null +++ b/cas_provider/templates/cas/warn.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} + +{% block title %} +Warning +{% endblock %} + +{% block content %} +
+
+ Confirm to log in to {{ service }} + +

+
+
+{% endblock %} diff --git a/cas_provider/tests.py b/cas_provider/tests.py new file mode 100644 index 0000000..c876148 --- /dev/null +++ b/cas_provider/tests.py @@ -0,0 +1,143 @@ +from cas_provider.models import ServiceTicket +from cas_provider.views import _cas2_sucess_response, _cas2_error_response, \ + INVALID_TICKET +from django.contrib.auth.models import User +from django.core.urlresolvers import reverse +from django.test import TestCase +from urlparse import urlparse + + +class ViewsTest(TestCase): + + fixtures = ['cas_users.json', ] + + def setUp(self): + self.service = 'http://example.com/' + + + def test_succeessful_login(self): + response = self._login_user('root', '123') + self._validate_cas1(response, True) + + response = self.client.get(reverse('cas_login'), {'service': self.service}, follow=False) + self.assertEqual(response.status_code, 302) + self.assertTrue(response['location'].startswith('%s?ticket=' % self.service)) + + response = self.client.get(reverse('cas_login'), follow=False) + self.assertEqual(response.status_code, 302) + self.assertTrue(response['location'].startswith('http://testserver/')) + + response = self.client.get(response['location'], follow=False) + self.assertIn(response.status_code, [302, 200]) + + response = self.client.get(reverse('cas_login'), {'service': self.service, 'warn': True}, follow=False) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'cas/warn.html') + + + def test_logout(self): + response = self._login_user('root', '123') + self._validate_cas1(response, True) + + response = self.client.get(reverse('cas_logout'), follow=False) + self.assertEqual(response.status_code, 200) + + response = self.client.get(reverse('cas_login'), follow=False) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context['user'].is_anonymous(), True) + + + def test_broken_pwd(self): + self._fail_login('root', '321') + + def test_broken_username(self): + self._fail_login('notroot', '123') + + def test_nonactive_user_login(self): + self._fail_login('nonactive', '123') + + def test_cas2_success_validate(self): + response = self._login_user('root', '123') + self._validate_cas2(response, True) + + def test_cas2_fail_validate(self): + for user, pwd in (('root', '321'), ('notroot', '123'), ('nonactive', '123')): + response = self._login_user(user, pwd) + self._validate_cas2(response, False) + + + def _fail_login(self, username, password): + response = self._login_user(username, password) + self._validate_cas1(response, False) + + response = self.client.get(reverse('cas_login'), {'service': self.service}, follow=False) + self.assertEqual(response.status_code, 200) + response = self.client.get(reverse('cas_login'), follow=False) + self.assertEqual(response.status_code, 200) + + + + def _login_user(self, username, password): + self.username = username + response = self.client.get(reverse('cas_login'), {'service': self.service}) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'cas/login.html') + form = response.context['form'] + service = form['service'].value() + return self.client.post(reverse('cas_login'), { + 'username': username, + 'password': password, + 'lt': form['lt'].value(), + 'service': service + }, follow=False) + + + def _validate_cas1(self, response, is_correct=True): + if is_correct: + self.assertEqual(response.status_code, 302) + self.assertTrue(response.has_header('location')) + location = urlparse(response['location']) + ticket = location.query.split('=')[1] + + response = self.client.get(reverse('cas_validate'), {'ticket': ticket, 'service': self.service}, follow=False) + self.assertEqual(response.status_code, 200) + self.assertEqual(unicode(response.content), u'yes\n%s\n' % self.username) + else: + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context['form'].errors), 1) + + response = self.client.get(reverse('cas_validate'), {'ticket': 'ST-12312312312312312312312', 'service': self.service}, follow=False) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, u'no\n\n') + + + def _validate_cas2(self, response, is_correct=True): + if is_correct: + self.assertEqual(response.status_code, 302) + self.assertTrue(response.has_header('location')) + location = urlparse(response['location']) + ticket = location.query.split('=')[1] + + response = self.client.get(reverse('cas_service_validate'), {'ticket': ticket, 'service': self.service}, follow=False) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, _cas2_sucess_response(self.username).content) + else: + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.context['form'].errors), 1) + + response = self.client.get(reverse('cas_service_validate'), {'ticket': 'ST-12312312312312312312312', 'service': self.service}, follow=False) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.content, _cas2_error_response(INVALID_TICKET).content) + + +class ModelsTestCase(TestCase): + + fixtures = ['cas_users.json', ] + + def setUp(self): + self.user = User.objects.get(username='root') + + def test_redirects(self): + ticket = ServiceTicket.objects.create(service='http://example.com', user=self.user) + self.assertEqual(ticket.get_redirect_url(), '%(service)s?ticket=%(ticket)s' % ticket.__dict__) + diff --git a/cas_provider/urls.py b/cas_provider/urls.py index 8edc91a..16a6744 100644 --- a/cas_provider/urls.py +++ b/cas_provider/urls.py @@ -1,9 +1,9 @@ -from django.conf.urls.defaults import * +from django.conf.urls.defaults import patterns, url -from views import * -urlpatterns = patterns('', - url(r'^login/', login), - url(r'^validate/', validate), - url(r'^logout/', logout), -) \ No newline at end of file +urlpatterns = patterns('cas_provider.views', + url(r'^login/?$', 'login', name='cas_login'), + url(r'^validate/?$', 'validate', name='cas_validate'), + url(r'^serviceValidate/?$', 'service_validate', name='cas_service_validate'), + url(r'^logout/?$', 'logout', name='cas_logout'), +) diff --git a/cas_provider/utils.py b/cas_provider/utils.py deleted file mode 100644 index 04a5c12..0000000 --- a/cas_provider/utils.py +++ /dev/null @@ -1,24 +0,0 @@ -from random import Random -import string - -from models import ServiceTicket, LoginTicket - -def _generate_string(length=8, chars=string.ascii_letters + string.digits): - """ Generates a random string of the requested length. Used for creation of tickets. """ - return ''.join(Random().sample(chars, length)) - -def create_service_ticket(user, service): - """ Creates a new service ticket for the specified user and service. - Uses _generate_string. - """ - 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(): - """ Creates a new login ticket for the login form. Uses _generate_string. """ - 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 index 5343232..32ed6e9 100644 --- a/cas_provider/views.py +++ b/cas_provider/views.py @@ -1,70 +1,125 @@ +from django.conf import settings +from django.contrib.auth import login as auth_login, \ + logout as auth_logout from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import render_to_response from django.template import RequestContext -from django.contrib.auth import authenticate -from django.contrib.auth import login as auth_login, logout as auth_logout -from django.utils.translation import ugettext_lazy as _ - 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/'): +__all__ = ['login', 'validate', 'logout', 'service_validate'] + + +INVALID_TICKET = 'INVALID_TICKET' +INVALID_SERVICE = 'INVALID_SERVICE' +INVALID_REQUEST = 'INVALID_REQUEST' +INTERNAL_ERROR = 'INTERNAL_ERROR' + +ERROR_MESSAGES = ( + (INVALID_TICKET, u'The provided ticket is invalid.'), + (INVALID_SERVICE, u'Service is invalid'), + (INVALID_REQUEST, u'Not all required parameters were sent.'), + (INTERNAL_ERROR, u'An internal error occurred during ticket validation'), +) + + +def login(request, template_name='cas/login.html', \ + success_redirect=settings.LOGIN_REDIRECT_URL, + warn_template_name='cas/warn.html'): 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) + if request.GET.get('warn', False): + return render_to_response(warn_template_name, { + 'service': service, + 'warn': False + }, context_instance=RequestContext(request)) + ticket = ServiceTicket.objects.create(service=service, user=request.user) + return HttpResponseRedirect(ticket.get_redirect_url()) 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)) - + form = LoginForm(request.POST) + if form.is_valid(): + user = form.get_user() + auth_login(request, user) + service = form.cleaned_data.get('service') + if service is not None: + ticket = ServiceTicket.objects.create(service=service, user=user) + success_redirect = ticket.get_redirect_url() + return HttpResponseRedirect(success_redirect) + else: + form = LoginForm(initial={ + 'service': service, + 'lt': LoginTicket.objects.create() + }) + return render_to_response(template_name, { + 'form': form, + 'errors': form.get_errors() + }, context_instance=RequestContext(request)) + + def validate(request): + """Validate ticket via CAS v.1 protocol""" service = request.GET.get('service', None) ticket_string = request.GET.get('ticket', None) if service is not None and ticket_string is not None: + #renew = request.GET.get('renew', True) + #if not renew: + # TODO: check user SSO session try: ticket = ServiceTicket.objects.get(ticket=ticket_string) username = ticket.user.username ticket.delete() - return HttpResponse("yes\r\n%s\r\n" % username) + return HttpResponse("yes\n%s\n" % username) except: pass - return HttpResponse("no\r\n\r\n") - + return HttpResponse("no\n\n") + + 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)) + return render_to_response(template_name, {'url': url}, \ + context_instance=RequestContext(request)) + + +def service_validate(request): + """Validate ticket via CAS v.2 protocol""" + service = request.GET.get('service', None) + ticket_string = request.GET.get('ticket', None) + if service is None or ticket_string is None: + return _cas2_error_response(INVALID_REQUEST) + + try: + ticket = ServiceTicket.objects.get(ticket=ticket_string) + except ServiceTicket.DoesNotExist: + return _cas2_error_response(INVALID_TICKET) + + if settings.CAS_CHECK_SERVICE and ticket.service != service: + ticket.delete() + return _cas2_error_response(INVALID_SERVICE) + + username = ticket.user.username + ticket.delete() + return _cas2_sucess_response(username) + + +def _cas2_error_response(code): + return HttpResponse(u'''' + + %(message)s + + ''' % { + 'code': code, + 'message': dict(ERROR_MESSAGES).get(code) + }, mimetype='text/xml') + + +def _cas2_sucess_response(username): + return HttpResponse(u''' + + %(username)s + + ''' % {'username': username}, mimetype='text/xml')