initial commit
This commit is contained in:
commit
667483fc49
20 changed files with 724 additions and 0 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
bootstrap3
|
||||||
|
cas/
|
||||||
|
db.sqlite3
|
||||||
|
manage.py
|
1
cas_server/__init__.py
Normal file
1
cas_server/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
import default_settings
|
28
cas_server/admin.py
Normal file
28
cas_server/admin.py
Normal file
|
@ -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)
|
24
cas_server/default_settings.py
Normal file
24
cas_server/default_settings.py
Normal file
|
@ -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)
|
36
cas_server/forms.py
Normal file
36
cas_server/forms.py
Normal file
|
@ -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)
|
127
cas_server/migrations/0001_initial.py
Normal file
127
cas_server/migrations/0001_initial.py
Normal file
|
@ -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,
|
||||||
|
),
|
||||||
|
]
|
0
cas_server/migrations/__init__.py
Normal file
0
cas_server/migrations/__init__.py
Normal file
131
cas_server/models.py
Normal file
131
cas_server/models.py
Normal file
|
@ -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 = """<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
|
||||||
|
ID="%(id)s" Version="2.0" IssueInstant="%(datetime)s">
|
||||||
|
<saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"></saml:NameID>
|
||||||
|
<samlp:SessionIndex>%(ticket)s</samlp:SessionIndex>
|
||||||
|
</samlp:LogoutRequest>""" % {'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()
|
42
cas_server/static/cas_server/login.css
Normal file
42
cas_server/static/cas_server/login.css
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
|
6
cas_server/templates/cas_server/base.html
Normal file
6
cas_server/templates/cas_server/base.html
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{% extends 'bootstrap3/bootstrap3.html' %}
|
||||||
|
{% block bootstrap3_title %}{% block title %}{% endblock %}{% endblock %}
|
||||||
|
|
||||||
|
{% load url from future %}
|
||||||
|
|
||||||
|
{% load bootstrap3 %}
|
20
cas_server/templates/cas_server/logged.html
Normal file
20
cas_server/templates/cas_server/logged.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{% extends "cas_server/base.html" %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% block bootstrap3_extra_head %}
|
||||||
|
<link href="{% static "cas_server/login.css" %}" rel="stylesheet">
|
||||||
|
{% endblock %}
|
||||||
|
{% block bootstrap3_content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3"></div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% bootstrap_messages %}
|
||||||
|
<div class="alert alert-success" role="alert">Logged</div>
|
||||||
|
{% bootstrap_button 'Arrêter' size='lg' button_class="btn-danger btn-block" href="logout" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3"></div>
|
||||||
|
</div>
|
||||||
|
</div> <!-- /container -->
|
||||||
|
{% endblock %}
|
||||||
|
|
24
cas_server/templates/cas_server/login.html
Normal file
24
cas_server/templates/cas_server/login.html
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{% extends "cas_server/base.html" %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% block bootstrap3_extra_head %}
|
||||||
|
<link href="{% static "cas_server/login.css" %}" rel="stylesheet">
|
||||||
|
{% endblock %}
|
||||||
|
{% block bootstrap3_content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3"></div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% bootstrap_messages %}
|
||||||
|
<form class="form-signin" method="post">
|
||||||
|
<h2 class="form-signin-heading">Merci de se connecter</h2>
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
{% bootstrap_button 'Connection' size='lg' button_type="submit" button_class="btn-block"%}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3"></div>
|
||||||
|
</div>
|
||||||
|
</div> <!-- /container -->
|
||||||
|
{% endblock %}
|
||||||
|
|
5
cas_server/templates/cas_server/proxy.xml
Normal file
5
cas_server/templates/cas_server/proxy.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
|
||||||
|
<cas:proxySuccess>
|
||||||
|
<cas:proxyTicket>{{ticket}}</cas:proxyTicket>
|
||||||
|
</cas:proxySuccess>
|
||||||
|
</cas:serviceResponse>
|
19
cas_server/templates/cas_server/serviceValidate.xml
Normal file
19
cas_server/templates/cas_server/serviceValidate.xml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
|
||||||
|
<cas:authenticationSuccess>
|
||||||
|
<cas:user>{{username}}</cas:user>
|
||||||
|
<cas:attributes>
|
||||||
|
{% for key, value in attributes %} <cas:{{key}}>{{value}}</cas:{{key}}>
|
||||||
|
{% endfor %}
|
||||||
|
</cas:attributes>
|
||||||
|
{% if proxyGrantingTicket %}
|
||||||
|
<cas:proxyGrantingTicket>{{proxyGrantingTicket}}</cas:proxyGrantingTicket>
|
||||||
|
{% endif %}
|
||||||
|
{% if proxies %}
|
||||||
|
<cas:proxies>
|
||||||
|
{% for proxy in proxies %}
|
||||||
|
<cas:proxy>{{proxy}}</cas:proxy>
|
||||||
|
{% endfor %}
|
||||||
|
</cas:proxies>
|
||||||
|
{% endif %}
|
||||||
|
</cas:authenticationSuccess>
|
||||||
|
</cas:serviceResponse>
|
5
cas_server/templates/cas_server/serviceValidateError.xml
Normal file
5
cas_server/templates/cas_server/serviceValidateError.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">
|
||||||
|
<cas:authenticationFailure code="{{code}}">
|
||||||
|
{{msg}}
|
||||||
|
</cas:authenticationFailure>
|
||||||
|
</cas:serviceResponse>
|
20
cas_server/templates/cas_server/warn.html
Normal file
20
cas_server/templates/cas_server/warn.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{% extends "cas_server/base.html" %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% block bootstrap3_extra_head %}
|
||||||
|
<link href="{% static "cas_server/login.css" %}" rel="stylesheet">
|
||||||
|
{% endblock %}
|
||||||
|
{% block bootstrap3_content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3"></div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% bootstrap_messages %}
|
||||||
|
<div class="alert alert-warning" role="alert">Une demande d'authentification a été émise pour le service {{service}}</div>
|
||||||
|
{% bootstrap_button 'Se connecter au service' size='lg' button_class="btn-primary btn-block" href=service_ticket_url %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3"></div>
|
||||||
|
</div>
|
||||||
|
</div> <!-- /container -->
|
||||||
|
{% endblock %}
|
||||||
|
|
3
cas_server/tests.py
Normal file
3
cas_server/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
18
cas_server/urls.py
Normal file
18
cas_server/urls.py
Normal file
|
@ -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'),
|
||||||
|
)
|
||||||
|
|
10
cas_server/utils.py
Normal file
10
cas_server/utils.py
Normal file
|
@ -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)
|
199
cas_server/views.py
Normal file
199
cas_server/views.py
Normal file
|
@ -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)
|
Loading…
Reference in a new issue