From 667483fc49ffb99672d58fd3281cc6acc57abfba Mon Sep 17 00:00:00 2001 From: Valentin Samir Date: Sat, 16 May 2015 23:43:46 +0200 Subject: [PATCH] initial commit --- .gitignore | 6 + cas_server/__init__.py | 1 + cas_server/admin.py | 28 +++ cas_server/default_settings.py | 24 +++ cas_server/forms.py | 36 ++++ cas_server/migrations/0001_initial.py | 127 +++++++++++ cas_server/migrations/__init__.py | 0 cas_server/models.py | 131 ++++++++++++ cas_server/static/cas_server/login.css | 42 ++++ cas_server/templates/cas_server/base.html | 6 + cas_server/templates/cas_server/logged.html | 20 ++ cas_server/templates/cas_server/login.html | 24 +++ cas_server/templates/cas_server/proxy.xml | 5 + .../templates/cas_server/serviceValidate.xml | 19 ++ .../cas_server/serviceValidateError.xml | 5 + cas_server/templates/cas_server/warn.html | 20 ++ cas_server/tests.py | 3 + cas_server/urls.py | 18 ++ cas_server/utils.py | 10 + cas_server/views.py | 199 ++++++++++++++++++ 20 files changed, 724 insertions(+) create mode 100644 .gitignore create mode 100644 cas_server/__init__.py create mode 100644 cas_server/admin.py create mode 100644 cas_server/default_settings.py create mode 100644 cas_server/forms.py create mode 100644 cas_server/migrations/0001_initial.py create mode 100644 cas_server/migrations/__init__.py create mode 100644 cas_server/models.py create mode 100644 cas_server/static/cas_server/login.css create mode 100644 cas_server/templates/cas_server/base.html create mode 100644 cas_server/templates/cas_server/logged.html create mode 100644 cas_server/templates/cas_server/login.html create mode 100644 cas_server/templates/cas_server/proxy.xml create mode 100644 cas_server/templates/cas_server/serviceValidate.xml create mode 100644 cas_server/templates/cas_server/serviceValidateError.xml create mode 100644 cas_server/templates/cas_server/warn.html create mode 100644 cas_server/tests.py create mode 100644 cas_server/urls.py create mode 100644 cas_server/utils.py create mode 100644 cas_server/views.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd0fe6e --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.pyc + +bootstrap3 +cas/ +db.sqlite3 +manage.py diff --git a/cas_server/__init__.py b/cas_server/__init__.py new file mode 100644 index 0000000..794c759 --- /dev/null +++ b/cas_server/__init__.py @@ -0,0 +1 @@ +import default_settings diff --git a/cas_server/admin.py b/cas_server/admin.py new file mode 100644 index 0000000..520efc6 --- /dev/null +++ b/cas_server/admin.py @@ -0,0 +1,28 @@ +from django.contrib import admin +from models import * +from forms import * +# Register your models here. + +class ServiceTicketInline(admin.TabularInline): + model = ServiceTicket + extra = 0 + form = TicketForm +class ProxyTicketInline(admin.TabularInline): + model = ProxyTicket + extra = 0 + form = TicketForm +class ProxyGrantingInline(admin.TabularInline): + model = ProxyGrantingTicket + extra = 0 + form = TicketForm + +class UserAdmin(admin.ModelAdmin): + inlines = (ServiceTicketInline, ProxyTicketInline, ProxyGrantingInline) + +class ServicePatternAdmin(admin.ModelAdmin): + list_display = ('pos', 'pattern', 'proxy') + + +admin.site.register(User, UserAdmin) +admin.site.register(ServicePattern, ServicePatternAdmin) +#admin.site.register(ProxyGrantingTicketIOU, admin.ModelAdmin) diff --git a/cas_server/default_settings.py b/cas_server/default_settings.py new file mode 100644 index 0000000..ec17441 --- /dev/null +++ b/cas_server/default_settings.py @@ -0,0 +1,24 @@ +from django.conf import settings + +def setting_default(name, default_value): + value = getattr(settings, name, default_value) + setattr(settings, name, value) + +class AuthUser(object): + def __init__(self, username): + self.username = username + + def test_password(self, password): + return self.username == "test" and password == "test" + + def attributs(self): + return {'nom':'Nymous', 'prenom':'Ano', 'email':'anonymous@example.net'} + + +setting_default('CAS_LOGIN_TEMPLATE', 'cas_server/login.html') +setting_default('CAS_WARN_TEMPLATE', 'cas_server/warn.html') +setting_default('CAS_LOGGED_TEMPLATE', 'cas_server/logged.html') +setting_default('CAS_AUTH_CLASS', AuthUser) +setting_default('CAS_ST_LEN', 30) +setting_default('CAS_TICKET_VALIDITY', 300) +setting_default('CAS_PROXY_CA_CERTIFICATE_PATH', True) diff --git a/cas_server/forms.py b/cas_server/forms.py new file mode 100644 index 0000000..9eecd70 --- /dev/null +++ b/cas_server/forms.py @@ -0,0 +1,36 @@ +from django import forms +from django.conf import settings + +import models + +class UserCredential(forms.Form): + username = forms.CharField(label='login') + service = forms.CharField(widget=forms.HiddenInput(), required=False) + password = forms.CharField(label='password', widget=forms.PasswordInput) + method = forms.CharField(widget=forms.HiddenInput(), required=False) + warn = forms.BooleanField(label='warn', required=False) + + def __init__(self, *args, **kwargs): + super(UserCredential, self).__init__(*args, **kwargs) + + def clean(self): + cleaned_data = super(UserCredential, self).clean() + auth = settings.CAS_AUTH_CLASS(cleaned_data.get("username")) + if auth.test_password(cleaned_data.get("password")): + try: + user = models.User.objects.get(username=auth.username) + user.attributs=auth.attributs() + user.save() + except models.User.DoesNotExist: + user = models.User.objects.create(username=auth.username, attributs=auth.attributs()) + user.save() + self.user = user + else: + raise forms.ValidationError("Bad user") + + +class TicketForm(forms.ModelForm): + class Meta: + model = models.Ticket + exclude = [] + service = forms.CharField(widget=forms.TextInput) diff --git a/cas_server/migrations/0001_initial.py b/cas_server/migrations/0001_initial.py new file mode 100644 index 0000000..dcde945 --- /dev/null +++ b/cas_server/migrations/0001_initial.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import cas_server.models +import picklefield.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Proxy', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('url', models.CharField(max_length=255)), + ], + options={ + 'ordering': ('-pk',), + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='ProxyGrantingTicket', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('attributs', picklefield.fields.PickledObjectField(editable=False)), + ('validate', models.BooleanField(default=False)), + ('service', models.TextField()), + ('creation', models.DateTimeField(auto_now_add=True)), + ('renew', models.BooleanField(default=False)), + ('value', models.CharField(default=cas_server.models._gen_pgt, unique=True, max_length=255)), + ], + options={ + 'abstract': False, + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='ProxyTicket', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('attributs', picklefield.fields.PickledObjectField(editable=False)), + ('validate', models.BooleanField(default=False)), + ('service', models.TextField()), + ('creation', models.DateTimeField(auto_now_add=True)), + ('renew', models.BooleanField(default=False)), + ('value', models.CharField(default=cas_server.models._gen_pt, unique=True, max_length=255)), + ], + options={ + 'abstract': False, + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='ServicePattern', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('pos', models.IntegerField(default=100)), + ('pattern', models.CharField(unique=True, max_length=255)), + ('user_field', models.CharField(default=b'', help_text=b"Nom de l'attribut transmit comme username, vide = login", max_length=255, blank=True)), + ('usernames', models.CharField(default=b'', help_text=b"Liste d'utilisateurs accept\xc3\xa9s s\xc3\xa9par\xc3\xa9 par des virgules, vide = tous les utilisateur", max_length=255, blank=True)), + ('attributs', models.CharField(default=b'', help_text=b"Liste des nom d'attributs \xc3\xa0 transmettre au service, s\xc3\xa9par\xc3\xa9 par une virgule. vide = aucun", max_length=255, blank=True)), + ('proxy', models.BooleanField(default=False, help_text=b"Un ProxyGrantingTicket peut \xc3\xaatre d\xc3\xa9livr\xc3\xa9 au service pour s'authentifier en temps que l'utilisateur sur d'autres services")), + ('filter', models.CharField(default=b'', help_text=b'Une lambda fonction pour filtrer sur les utilisateur o\xc3\xb9 leurs attribut, arg1: username, arg2:attrs_dict. vide = pas de filtre', max_length=255, blank=True)), + ], + options={ + 'ordering': ('pos',), + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='ServiceTicket', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('attributs', picklefield.fields.PickledObjectField(editable=False)), + ('validate', models.BooleanField(default=False)), + ('service', models.TextField()), + ('creation', models.DateTimeField(auto_now_add=True)), + ('renew', models.BooleanField(default=False)), + ('value', models.CharField(default=cas_server.models._gen_st, unique=True, max_length=255)), + ], + options={ + 'abstract': False, + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='User', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('username', models.CharField(unique=True, max_length=30)), + ('attributs', picklefield.fields.PickledObjectField(editable=False)), + ('date', models.DateTimeField(auto_now=True, auto_now_add=True)), + ], + options={ + }, + bases=(models.Model,), + ), + migrations.AddField( + model_name='serviceticket', + name='user', + field=models.ForeignKey(related_name='serviceticket', to='cas_server.User'), + preserve_default=True, + ), + migrations.AddField( + model_name='proxyticket', + name='user', + field=models.ForeignKey(related_name='proxyticket', to='cas_server.User'), + preserve_default=True, + ), + migrations.AddField( + model_name='proxygrantingticket', + name='user', + field=models.ForeignKey(related_name='proxygrantingticket', to='cas_server.User'), + preserve_default=True, + ), + migrations.AddField( + model_name='proxy', + name='proxy_ticket', + field=models.ForeignKey(related_name='proxies', to='cas_server.ProxyTicket'), + preserve_default=True, + ), + ] diff --git a/cas_server/migrations/__init__.py b/cas_server/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cas_server/models.py b/cas_server/models.py new file mode 100644 index 0000000..4e4376f --- /dev/null +++ b/cas_server/models.py @@ -0,0 +1,131 @@ +# ⁻*- coding: utf-8 -*- + +from django.conf import settings +from django.db import models +from django.contrib import messages +from picklefield.fields import PickledObjectField + +import re +import os +import time +import random +import string +import requests + +import utils +def _gen_ticket(prefix): + return '%s-%s' % (prefix, ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(settings.CAS_ST_LEN))) + +def _gen_st(): + return _gen_ticket('ST') + +def _gen_pt(): + return _gen_ticket('PT') + +def _gen_pgt(): + return _gen_ticket('PGT') + + +class User(models.Model): + username = models.CharField(max_length=30, unique=True) + attributs = PickledObjectField() + date = models.DateTimeField(auto_now_add=True, auto_now=True) + + def __unicode__(self): + return self.username + + def logout(self, request): + for ticket in ServiceTicket.objects.filter(user=self): + ticket.logout(request) + ticket.delete() + for ticket in ProxyTicket.objects.filter(user=self): + ticket.logout(request) + ticket.delete() + for ticket in ProxyGrantingTicket.objects.filter(user=self): + ticket.logout(request) + ticket.delete() + + def delete(self): + super(User, self).delete() + + def get_service_url(self, service, service_pattern, renew): + attributs = [s.strip() for s in service_pattern.attributs.split(',')] + ticket = ServiceTicket.objects.create(user=self, attributs = dict([(k, v) for (k, v) in self.attributs.items() if k in attributs]), service=service, renew=renew) + ticket.save() + url = utils.update_url(service, {'ticket':ticket.value}) + return url + +class Ticket(models.Model): + class Meta: + abstract = True + user = models.ForeignKey(User, related_name="%(class)s") + attributs = PickledObjectField() + validate = models.BooleanField(default=False) + service = models.TextField() + creation = models.DateTimeField(auto_now_add=True) + renew = models.BooleanField(default=False) + + def __unicode__(self): + return u"%s: %s %s" % (self.user, self.value, self.service) + + def logout(self, request): + #if self.validate: + xml = """ + + %(ticket)s + """ % {'id' : os.urandom(20).encode("hex"), 'datetime' : int(time.time()), 'ticket': self.value} + headers = {'Content-Type': 'text/xml'} + try: + requests.post(self.service.encode('utf-8'), data=xml.encode('utf-8'), headers=headers) + except Exception as e: + messages.add_message(request, messages.WARNING, u'Erreur lors de la déconnexion du service %s:\n%s' % (self.service, e)) + +class ServiceTicket(Ticket): + value = models.CharField(max_length=255, default=_gen_st, unique=True) +class ProxyTicket(Ticket): + value = models.CharField(max_length=255, default=_gen_pt, unique=True) +class ProxyGrantingTicket(Ticket): + value = models.CharField(max_length=255, default=_gen_pgt, unique=True) +#class ProxyGrantingTicketIOU(Ticket): +# value = models.CharField(max_length=255, default=lambda:_gen_ticket('PGTIOU'), unique=True) + +class Proxy(models.Model): + class Meta: + ordering = ("-pk", ) + url = models.CharField(max_length=255) + proxy_ticket = models.ForeignKey(ProxyTicket, related_name="proxies") + +class BadUsername(Exception): + pass +class BadFilter(Exception): + pass +class ServicePattern(models.Model): + class Meta: + ordering = ("pos", ) + + pos = models.IntegerField(default=100) + pattern = models.CharField(max_length=255, unique=True) + user_field = models.CharField(max_length=255, default="", blank=True, help_text="Nom de l'attribut transmit comme username, vide = login") + usernames = models.CharField(max_length=255, default="", blank=True, help_text="Liste d'utilisateurs acceptés séparé par des virgules, vide = tous les utilisateur") + attributs = models.CharField(max_length=255, default="", blank=True, help_text="Liste des nom d'attributs à transmettre au service, séparé par une virgule. vide = aucun") + proxy = models.BooleanField(default=False, help_text="Un ProxyGrantingTicket peut être délivré au service pour s'authentifier en temps que l'utilisateur sur d'autres services") + filter = models.CharField(max_length=255, default="", blank=True, help_text="Une lambda fonction pour filtrer sur les utilisateur où leurs attribut, arg1: username, arg2:attrs_dict. vide = pas de filtre") + + def __unicode__(self): + return u"%s: %s" % (self.pos, self.pattern) + + def check_user(self, user): + if self.usernames and not user.username in self.usernames.split(','): + raise BadUsername() + if self.filter and self.filter.startswith("lambda") and not eval(str(self.filter))(user.username, user.attributs): + raise BadFilter() + return True + + + @classmethod + def validate(cls, service): + for s in cls.objects.all(): + if re.match(s.pattern, service): + return s + raise cls.DoesNotExist() diff --git a/cas_server/static/cas_server/login.css b/cas_server/static/cas_server/login.css new file mode 100644 index 0000000..865312a --- /dev/null +++ b/cas_server/static/cas_server/login.css @@ -0,0 +1,42 @@ +body { + padding-top: 40px; + padding-bottom: 40px; + background-color: #eee; +} + +/*.form-signin { + max-width: 330px; + padding: 15px; + margin: 0 auto; +} +*/ +.form-signin .form-signin-heading, +.form-signin .checkbox { + margin-bottom: 10px; +} +.form-signin .checkbox { + font-weight: normal; +} +.form-signin .form-control { + position: relative; + height: auto; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 10px; + font-size: 16px; +} +.form-signin .form-control:focus { + z-index: 2; +} +.form-signin input[type="text"] { + margin-bottom: -1px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.form-signin input[type="password"] { + margin-bottom: 10px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} + diff --git a/cas_server/templates/cas_server/base.html b/cas_server/templates/cas_server/base.html new file mode 100644 index 0000000..30c134c --- /dev/null +++ b/cas_server/templates/cas_server/base.html @@ -0,0 +1,6 @@ +{% extends 'bootstrap3/bootstrap3.html' %} +{% block bootstrap3_title %}{% block title %}{% endblock %}{% endblock %} + +{% load url from future %} + +{% load bootstrap3 %} diff --git a/cas_server/templates/cas_server/logged.html b/cas_server/templates/cas_server/logged.html new file mode 100644 index 0000000..0b12d6f --- /dev/null +++ b/cas_server/templates/cas_server/logged.html @@ -0,0 +1,20 @@ +{% extends "cas_server/base.html" %} +{% load bootstrap3 %} +{% load staticfiles %} +{% block bootstrap3_extra_head %} + +{% endblock %} +{% block bootstrap3_content %} +
+
+
+
+{% bootstrap_messages %} + +{% bootstrap_button 'Arrêter' size='lg' button_class="btn-danger btn-block" href="logout" %} +
+
+
+
+{% endblock %} + diff --git a/cas_server/templates/cas_server/login.html b/cas_server/templates/cas_server/login.html new file mode 100644 index 0000000..3d88e34 --- /dev/null +++ b/cas_server/templates/cas_server/login.html @@ -0,0 +1,24 @@ +{% extends "cas_server/base.html" %} +{% load bootstrap3 %} +{% load staticfiles %} +{% block bootstrap3_extra_head %} + +{% endblock %} +{% block bootstrap3_content %} +
+
+
+
+{% bootstrap_messages %} + +
+
+
+
+{% endblock %} + diff --git a/cas_server/templates/cas_server/proxy.xml b/cas_server/templates/cas_server/proxy.xml new file mode 100644 index 0000000..ab51d89 --- /dev/null +++ b/cas_server/templates/cas_server/proxy.xml @@ -0,0 +1,5 @@ + + + {{ticket}} + + diff --git a/cas_server/templates/cas_server/serviceValidate.xml b/cas_server/templates/cas_server/serviceValidate.xml new file mode 100644 index 0000000..59cb01d --- /dev/null +++ b/cas_server/templates/cas_server/serviceValidate.xml @@ -0,0 +1,19 @@ + + + {{username}} + +{% for key, value in attributes %} {{value}} +{% endfor %} + +{% if proxyGrantingTicket %} + {{proxyGrantingTicket}} +{% endif %} +{% if proxies %} + + {% for proxy in proxies %} + {{proxy}} + {% endfor %} + +{% endif %} + + diff --git a/cas_server/templates/cas_server/serviceValidateError.xml b/cas_server/templates/cas_server/serviceValidateError.xml new file mode 100644 index 0000000..56e03c9 --- /dev/null +++ b/cas_server/templates/cas_server/serviceValidateError.xml @@ -0,0 +1,5 @@ + + + {{msg}} + + diff --git a/cas_server/templates/cas_server/warn.html b/cas_server/templates/cas_server/warn.html new file mode 100644 index 0000000..a238478 --- /dev/null +++ b/cas_server/templates/cas_server/warn.html @@ -0,0 +1,20 @@ +{% extends "cas_server/base.html" %} +{% load bootstrap3 %} +{% load staticfiles %} +{% block bootstrap3_extra_head %} + +{% endblock %} +{% block bootstrap3_content %} +
+
+
+
+{% bootstrap_messages %} + +{% bootstrap_button 'Se connecter au service' size='lg' button_class="btn-primary btn-block" href=service_ticket_url %} +
+
+
+
+{% endblock %} + diff --git a/cas_server/tests.py b/cas_server/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/cas_server/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/cas_server/urls.py b/cas_server/urls.py new file mode 100644 index 0000000..dac273a --- /dev/null +++ b/cas_server/urls.py @@ -0,0 +1,18 @@ +# ⁻*- coding: utf-8 -*- +from django.conf.urls import patterns, url +from django.views.generic import RedirectView + +import views + +urlpatterns = patterns('', + url(r'^$', RedirectView.as_view(pattern_name="login")), + url('^login$', views.login, name='login'), + url('^logout$', views.logout, name='logout'), + url('^validate$', views.validate, name='validate'), + url('^serviceValidate$', views.serviceValidate, name='serviceValidate'), + url('^proxyValidate$', views.proxyValidate, name='proxyValidate'), + url('^proxy$', views.proxy, name='proxy'), + url('^p3/serviceValidate$', views.p3_serviceValidate, name='p3_serviceValidate'), + url('^p3/proxyValidate$', views.p3_proxyValidate, name='p3_proxyValidate'), +) + diff --git a/cas_server/utils.py b/cas_server/utils.py new file mode 100644 index 0000000..fe7ea25 --- /dev/null +++ b/cas_server/utils.py @@ -0,0 +1,10 @@ +import urlparse +import urllib + +def update_url(url, params): + url = urlparse.urlparse(url) + url_parts = list(urlparse.urlparse(service)) + query = dict(urlparse.parse_qsl(url_parts[4])) + query.update(params) + url_parts[4] = urllib.urlencode(query) + return urlparse.urlunparse(url_parts) diff --git a/cas_server/views.py b/cas_server/views.py new file mode 100644 index 0000000..93a4cf5 --- /dev/null +++ b/cas_server/views.py @@ -0,0 +1,199 @@ +# ⁻*- coding: utf-8 -*- +from django.shortcuts import render, redirect +from django.http import HttpResponse, StreamingHttpResponse +from django.conf import settings +from django.contrib import messages + +import requests +from datetime import datetime, timedelta + +import utils +import forms +import models + +def _logout(request): + try: del request.session["authenticated"] + except KeyError: pass + try: del request.session["username"] + except KeyError: pass + try: del request.session["warn"] + except KeyError: pass + +def login(request): + user = None + form = None + service_pattern = None + renewed = False + if request.method == 'POST': + service = request.POST.get('service') + renew = True if request.POST.get('renew') else False + gateway = request.POST.get('gateway') + method = request.POST.get('method') + + if not request.session.get("authenticated") or renew: + form = forms.UserCredential(request.POST, initial={'service':service,'method':method,'warn':request.session.get("warn")}) + if form.is_valid(): + user = models.User.objects.get(username=form.cleaned_data['username']) + request.session["username"] = form.cleaned_data['username'] + request.session["warn"] = True if form.cleaned_data.get("warn") else False + request.session["authenticated"] = True + renewed = True + else: + _logout(request) + else: + service = request.GET.get('service') + renew = True if request.GET.get('renew') else False + gateway = request.GET.get('gateway') + method = request.GET.get('method') + + if not request.session.get("authenticated") or renew: + form = forms.UserCredential(initial={'service':service,'method':method,'warn':request.session.get("warn")}) + + # if authenticated and successfully renewed authentication if needed + if request.session.get("authenticated") and (not renew or renewed): + try: + user = models.User.objects.get(username=request.session["username"]) + except models.User.DoesNotExist: + _logout(request) + + # if login agains a service is requestest + if service: + try: + # is the service allowed + service_pattern = models.ServicePattern.validate(service) + # is the current user allowed on this service + service_pattern.check_user(user) + # if the user has asked to be warned before any login to a service (no transparent SSO) + if request.session["warn"]: + return render(request, settings.CAS_WARN_TEMPLATE, {'service_ticket_url':user.get_service_url(service, service_pattern, renew=renew),'service':service}) + else: + return redirect(user.get_service_url(service, service_pattern, renew=renew)) # redirect, using method ? + except models.ServicePattern.DoesNotExist: + messages.add_message(request, messages.ERROR, u'Service %s non autorisé.' % service) + except models.BadUsername: + messages.add_message(request, messages.ERROR, u"Nom d'utilisateur non autorisé") + except models.BadFilter: + messages.add_message(request, messages.ERROR, u"Caractéristique utilisateur non autorisé") + + # if gateway is set and auth failed redirect to the service without authentication + if gateway: + list(messages.get_messages(request)) # clean messages before leaving the django app + return redirect(service) + + return render(request, settings.CAS_LOGGED_TEMPLATE, {}) + else: + if service: + if gateway: + list(messages.get_messages(request)) # clean messages before leaving the django app + return redirect(service) + return render(request, settings.CAS_LOGIN_TEMPLATE, {'form':form}) + +def logout(request): + service = request.GET.get('service') + if request.session.get("authenticated"): + user = models.User.objects.get(username=request.session["username"]) + user.logout(request) + user.delete() + _logout(request) + # if service is set, redirect to service after logout + if service: + list(messages.get_messages(request)) # clean messages before leaving the django app + return redirect(service) + # else redirect to login page + else: + messages.add_message(request, messages.SUCCESS, u'Déconnecté avec succès') + return redirect("login") + +def validate(request): + service = request.GET.get('service') + ticket = request.GET.get('ticket') + renew = True if request.GET.get('renew') else False + if service and ticket: + try: + ticket = models.ServiceTicket.objects.get(value=ticket, service=service, validate=False, renew=renew, creation__gt=(datetime.now() - timedelta(seconds=settings.CAS_TICKET_VALIDITY))) + ticket.validate = True + ticket.save() + return HttpResponse("yes\n", content_type="text/plain") + except models.ServiceTicket.DoesNotExist: + return HttpResponse("no\n", content_type="text/plain") + else: + return HttpResponse("no\n", content_type="text/plain") + + +def psValidate(request, typ=['ST']): + service = request.GET.get('service') + ticket = request.GET.get('ticket') + pgtUrl = request.GET.get('pgtUrl') + renew = True if request.GET.get('renew') else False + if service and ticket: + for t in typ: + if ticket.startswith(t): + break + else: + return render(request, "cas_server/serviceValidateError.xml", {'code':'INVALID_TICKET'}, content_type="text/xml; charset=utf-8") + try: + proxies = [] + if ticket.startswith("ST"): + ticket = models.ServiceTicket.objects.get(value=ticket, service=service, validate=False, renew=renew, creation__gt=(datetime.now() - timedelta(seconds=settings.CAS_TICKET_VALIDITY))) + elif ticket.startswith("PT"): + ticket = models.ProxyTicket.objects.get(value=ticket, service=service, validate=False, renew=renew, creation__gt=(datetime.now() - timedelta(seconds=settings.CAS_TICKET_VALIDITY))) + for p in ticket.proxies.all(): + proxies.add(p.url) + ticket.validate = True + ticket.save() + attributes = [] + for key, value in ticket.attributs.items(): + if isinstance(value, list): + for v in value: + attributes.append((key, v)) + else: + attributes.append((key, value)) + params = {'username':ticket.user.username, 'attributes':attributes, 'proxies':proxies} + if pgtUrl and pgtUrl.startswith("https://"): + pattern = modele.ServicePattern(pgtUrl) + if pattern.proxy: + proxyid = models._gen_ticket('PGTIOU') + pticket = models.ProxyGrantingTicket.objects.create(user=ticket.user, service=pgtUrl) + url = utils.update_url(pgtUrl, {'pgtIou':proxyid, 'pgtId':pticket.value}) + try: + r = requests.get(url, verify=settings.CAS_PROXY_CA_CERTIFICATE_PATH) + if r.status_code == 200: + params['proxyGrantingTicket'] = proxyid + else: + pticket.delete() + return render(request, "cas_server/serviceValidate.xml", params, content_type="text/xml; charset=utf-8") + except requests.exceptions.SSLError: + return render(request, "cas_server/serviceValidateError.xml", {'code':'INVALID_PROXY_CALLBACK'}, content_type="text/xml; charset=utf-8") + else: + return render(request, "cas_server/serviceValidateError.xml", {'code':'INVALID_PROXY_CALLBACK'}, content_type="text/xml; charset=utf-8") + else: + return render(request, "cas_server/serviceValidate.xml", params, content_type="text/xml; charset=utf-8") + except (models.ServiceTicket.DoesNotExist, models.ProxyTicket.DoesNotExist): + return render(request, "cas_server/serviceValidateError.xml", {'code':'INVALID_TICKET'}, content_type="text/xml; charset=utf-8") + else: + return render(request, "cas_server/serviceValidateError.xml", {'code':'INVALID_REQUEST'}, content_type="text/xml; charset=utf-8") + +def serviceValidate(request): + return psValidate(request) +def proxyValidate(request): + return psValidate(request, ["ST", "PT"]) + +def proxy(request): + pgt = request.GET.get('pgt') + targetService = request.GET.get('targetService') + if pgt and targetService: + try: + ticket = models.ProxyGrantingTicket.objects.get(value=pgt, creation__gt=(datetime.now() - timedelta(seconds=settings.CAS_TICKET_VALIDITY))) + pticket = models.ProxyTicket.objects.create(user=ticket.user, service=targetService) + pticket.proxies.create(url=ticket.service) + return render(request, "cas_server/proxy.xml", {'ticket':pticket.value}, content_type="text/xml; charset=utf-8") + except models.ProxyGrantingTicket.DoesNotExist: + return render(request, "cas_server/serviceValidateError.xml", {'code':'INVALID_TICKET'}, content_type="text/xml; charset=utf-8") + else: + return render(request, "cas_server/serviceValidateError.xml", {'code':'INVALID_REQUEST'}, content_type="text/xml; charset=utf-8") + +def p3_serviceValidate(request): + return serviceValidate(request) + +def p3_proxyValidate(request): + return proxyValidate(request)