diff --git a/cas_server/admin.py b/cas_server/admin.py
index 520efc6..f9605a2 100644
--- a/cas_server/admin.py
+++ b/cas_server/admin.py
@@ -19,8 +19,22 @@ class ProxyGrantingInline(admin.TabularInline):
class UserAdmin(admin.ModelAdmin):
inlines = (ServiceTicketInline, ProxyTicketInline, ProxyGrantingInline)
+class UsernamesInline(admin.TabularInline):
+ model = Usernames
+ extra = 0
+class ReplaceAttributNameInline(admin.TabularInline):
+ model = ReplaceAttributName
+ extra = 0
+class ReplaceAttributValueInline(admin.TabularInline):
+ model = ReplaceAttributValue
+ extra = 0
+class FilterAttributValueInline(admin.TabularInline):
+ model = FilterAttributValue
+ extra = 0
+
class ServicePatternAdmin(admin.ModelAdmin):
- list_display = ('pos', 'pattern', 'proxy')
+ inlines = (UsernamesInline, ReplaceAttributNameInline, ReplaceAttributValueInline, FilterAttributValueInline)
+ list_display = ('pos', 'name', 'pattern', 'proxy')
admin.site.register(User, UserAdmin)
diff --git a/cas_server/migrations/0003_auto_20150518_1648.py b/cas_server/migrations/0003_auto_20150518_1648.py
new file mode 100644
index 0000000..e4f3dbc
--- /dev/null
+++ b/cas_server/migrations/0003_auto_20150518_1648.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cas_server', '0002_auto_20150517_1406'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Attribut',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=255)),
+ ('replace', models.CharField(default=b'', max_length=255, blank=True)),
+ ('service_pattern', models.ForeignKey(related_name='attributs', to='cas_server.ServicePattern')),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.RemoveField(
+ model_name='servicepattern',
+ name='attributs',
+ ),
+ ]
diff --git a/cas_server/migrations/0004_auto_20150518_1659.py b/cas_server/migrations/0004_auto_20150518_1659.py
new file mode 100644
index 0000000..f6b93de
--- /dev/null
+++ b/cas_server/migrations/0004_auto_20150518_1659.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cas_server', '0003_auto_20150518_1648'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='FilterAttributValue',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('attribut', models.CharField(max_length=255)),
+ ('pattern', models.CharField(max_length=255)),
+ ('service_pattern', models.ForeignKey(related_name='filters', to='cas_server.ServicePattern')),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='ReplaceAttributValue',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('attribut', models.CharField(max_length=255)),
+ ('pattern', models.CharField(max_length=255)),
+ ('replace', models.CharField(max_length=255)),
+ ('service_pattern', models.ForeignKey(related_name='replacements', to='cas_server.ServicePattern')),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.RenameModel(
+ old_name='Attribut',
+ new_name='ReplaceAttributName',
+ ),
+ migrations.RemoveField(
+ model_name='servicepattern',
+ name='filter',
+ ),
+ ]
diff --git a/cas_server/migrations/0005_auto_20150518_1717.py b/cas_server/migrations/0005_auto_20150518_1717.py
new file mode 100644
index 0000000..df7ae40
--- /dev/null
+++ b/cas_server/migrations/0005_auto_20150518_1717.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cas_server', '0004_auto_20150518_1659'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='filterattributvalue',
+ name='attribut',
+ field=models.CharField(help_text="Nom de l'attribut devant v\xe9rifier pattern", max_length=255),
+ preserve_default=True,
+ ),
+ migrations.AlterField(
+ model_name='filterattributvalue',
+ name='pattern',
+ field=models.CharField(help_text='Une expression r\xe9guli\xe8re', max_length=255),
+ preserve_default=True,
+ ),
+ migrations.AlterField(
+ model_name='replaceattributname',
+ name='name',
+ field=models.CharField(help_text="nom d'un attributs \xe0 transmettre au service", max_length=255),
+ preserve_default=True,
+ ),
+ migrations.AlterField(
+ model_name='replaceattributname',
+ name='replace',
+ field=models.CharField(help_text="nom sous lequel l'attribut sera pr\xe9sent\xe9 au service. vide = inchang\xe9", max_length=255, blank=True),
+ preserve_default=True,
+ ),
+ migrations.AlterField(
+ model_name='replaceattributvalue',
+ name='attribut',
+ field=models.CharField(help_text="Nom de l'attribut dont la valeur doit \xeatre modifi\xe9", max_length=255),
+ preserve_default=True,
+ ),
+ migrations.AlterField(
+ model_name='replaceattributvalue',
+ name='pattern',
+ field=models.CharField(help_text='Une expression r\xe9guli\xe8re de ce qui doit \xeatre modifi\xe9', max_length=255),
+ preserve_default=True,
+ ),
+ migrations.AlterField(
+ model_name='replaceattributvalue',
+ name='replace',
+ field=models.CharField(help_text='Par quoi le remplacer, les groupes sont captur\xe9 par \\1, \\2 \u2026', max_length=255, blank=True),
+ preserve_default=True,
+ ),
+ ]
diff --git a/cas_server/migrations/0006_auto_20150518_1720.py b/cas_server/migrations/0006_auto_20150518_1720.py
new file mode 100644
index 0000000..15b503d
--- /dev/null
+++ b/cas_server/migrations/0006_auto_20150518_1720.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cas_server', '0005_auto_20150518_1717'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='replaceattributname',
+ name='name',
+ field=models.CharField(help_text="nom d'un attributs \xe0 transmettre au service", unique=True, max_length=255),
+ preserve_default=True,
+ ),
+ ]
diff --git a/cas_server/migrations/0007_auto_20150518_1727.py b/cas_server/migrations/0007_auto_20150518_1727.py
new file mode 100644
index 0000000..4ee050f
--- /dev/null
+++ b/cas_server/migrations/0007_auto_20150518_1727.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cas_server', '0006_auto_20150518_1720'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Usernames',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('value', models.CharField(max_length=255)),
+ ('service_pattern', models.ForeignKey(related_name='usernames', to='cas_server.ServicePattern')),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.RemoveField(
+ model_name='servicepattern',
+ name='usernames',
+ ),
+ migrations.AddField(
+ model_name='servicepattern',
+ name='restrict_users',
+ field=models.BooleanField(default=False, help_text=b'Limiter les utilisateur autoris\xc3\xa9 a se connect\xc3\xa9 a ce service \xc3\xa0 celle ci-dessous'),
+ preserve_default=True,
+ ),
+ ]
diff --git a/cas_server/migrations/0008_servicepattern_name.py b/cas_server/migrations/0008_servicepattern_name.py
new file mode 100644
index 0000000..9a30141
--- /dev/null
+++ b/cas_server/migrations/0008_servicepattern_name.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cas_server', '0007_auto_20150518_1727'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='servicepattern',
+ name='name',
+ field=models.CharField(max_length=255, unique=True, null=True, blank=True),
+ preserve_default=True,
+ ),
+ ]
diff --git a/cas_server/migrations/0009_auto_20150518_1740.py b/cas_server/migrations/0009_auto_20150518_1740.py
new file mode 100644
index 0000000..e119c3d
--- /dev/null
+++ b/cas_server/migrations/0009_auto_20150518_1740.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cas_server', '0008_servicepattern_name'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='servicepattern',
+ name='name',
+ field=models.CharField(help_text=b'Un nom pour le service', max_length=255, unique=True, null=True, blank=True),
+ preserve_default=True,
+ ),
+ ]
diff --git a/cas_server/models.py b/cas_server/models.py
index 12406a4..ace74fb 100644
--- a/cas_server/models.py
+++ b/cas_server/models.py
@@ -11,7 +11,9 @@ import os
import time
import random
import string
-import requests
+
+from concurrent.futures import ThreadPoolExecutor
+from requests_futures.sessions import FuturesSession
import utils
def _gen_ticket(prefix):
@@ -36,23 +38,42 @@ class User(models.Model):
return self.username
def logout(self, request):
+ async_list = []
+ session = FuturesSession(executor=ThreadPoolExecutor(max_workers=10))
for ticket in ServiceTicket.objects.filter(user=self):
- ticket.logout(request)
+ async_list.append(ticket.logout(request, session))
ticket.delete()
for ticket in ProxyTicket.objects.filter(user=self):
- ticket.logout(request)
+ async_list.append(ticket.logout(request, session))
ticket.delete()
for ticket in ProxyGrantingTicket.objects.filter(user=self):
- ticket.logout(request)
+ async_list.append(ticket.logout(request, session))
ticket.delete()
+ for future in async_list:
+ try:
+ future.result()
+ except Exception as e:
+ messages.add_message(request, messages.WARNING, u'Erreur lors de la déconnexion des services %s' % e)
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, service_pattern=service_pattern)
+
+ def get_ticket(self, TicketClass, service, service_pattern, renew):
+ attributs = dict((a.name, a.replace if a.replace else a.name) for a in service_pattern.attributs.all())
+ replacements = dict((a.name, (a.pattern, a.replace)) for a in service_pattern.replacements.all())
+ service_attributs = {}
+ for (k,v) in self.attributs.items():
+ if k in attributs:
+ if k in replacements:
+ v = re.sub(replacements[k][0], replacements[k][1], v)
+ service_attributs[attributs[k]] = v
+ ticket = TicketClass.objects.create(user=self, attributs = service_attributs, service=service, renew=renew, service_pattern=service_pattern)
ticket.save()
+ return ticket
+
+ def get_service_url(self, service, service_pattern, renew):
+ ticket = self.get_ticket(ServiceTicket, service, service_pattern, renew)
url = utils.update_url(service, {'ticket':ticket.value})
return url
@@ -67,21 +88,31 @@ class ServicePattern(models.Model):
ordering = ("pos", )
pos = models.IntegerField(default=100)
+ name = models.CharField(max_length=255, unique=True, blank=True, null=True, help_text="Un nom pour le service")
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")
+ #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")
+ restrict_users = models.BooleanField(default=False, help_text="Limiter les utilisateur autorisé a se connecté a ce service à celle ci-dessous")
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")
+ #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(','):
+ if self.restrict_users and not self.usernames.filter(value=user.username):
raise BadUsername()
- if self.filter and self.filter.startswith("lambda") and not eval(str(self.filter))(user.username, user.attributs):
- raise BadFilter()
+ for f in self.filters.all():
+ if isinstance(user.attributs[f.attribut], list):
+ l = user.attributs[f.attribut]
+ else:
+ l = [user.attributs[f.attribut]]
+ for v in l:
+ if re.match(f.pattern, str(v)):
+ break
+ else:
+ raise BadFilter('%s do not match %s %s' % (f.pattern, f.attribut, user.attributs[f.attribut]) )
if self.user_field and not user.attributs.get(self.user_field):
raise UserFieldNotDefined()
return True
@@ -94,6 +125,36 @@ class ServicePattern(models.Model):
return s
raise cls.DoesNotExist()
+class Usernames(models.Model):
+ value = models.CharField(max_length=255)
+ service_pattern = models.ForeignKey(ServicePattern, related_name="usernames")
+class ReplaceAttributName(models.Model):
+ name = models.CharField(max_length=255, unique=True, help_text=u"nom d'un attributs à transmettre au service")
+ replace = models.CharField(max_length=255, blank=True, help_text=u"nom sous lequel l'attribut sera présenté au service. vide = inchangé")
+ service_pattern = models.ForeignKey(ServicePattern, related_name="attributs")
+
+ def __unicode__(self):
+ if not self.replace:
+ return self.name
+ else:
+ return u"%s → %s" % (self.name, self.replace)
+
+class FilterAttributValue(models.Model):
+ attribut = models.CharField(max_length=255, help_text=u"Nom de l'attribut devant vérifier pattern")
+ pattern = models.CharField(max_length=255, help_text=u"Une expression régulière")
+ service_pattern = models.ForeignKey(ServicePattern, related_name="filters")
+
+ def __unicode__(self):
+ return u"%s %s" % (self.attribut, self.pattern)
+
+class ReplaceAttributValue(models.Model):
+ attribut = models.CharField(max_length=255, help_text=u"Nom de l'attribut dont la valeur doit être modifié")
+ pattern = models.CharField(max_length=255, help_text=u"Une expression régulière de ce qui doit être modifié")
+ replace = models.CharField(max_length=255, blank=True, help_text=u"Par quoi le remplacer, les groupes sont capturé par \\1, \\2 …")
+ service_pattern = models.ForeignKey(ServicePattern, related_name="replacements")
+
+ def __unicode__(self):
+ return u"%s %s %s" % (self.attribut, self.pattern, self.replace)
class Ticket(models.Model):
@@ -110,7 +171,7 @@ class Ticket(models.Model):
def __unicode__(self):
return u"%s: %s %s" % (self.user, self.value, self.service)
- def logout(self, request):
+ def logout(self, request, session):
#if self.validate:
xml = """