diff --git a/README.txt b/README.txt index aca641a..292d70e 100644 --- a/README.txt +++ b/README.txt @@ -9,7 +9,12 @@ be automatically loaded by the plugin framework. Settings ======== -*TODO* +Name Description +================= ========================= -A2_IDP_CAS_PROVIDER -A2_IDP_CAS_TICKET_EXPIRATION +A2_IDP_CAS_SERVICES A sequence of URL prefixes, any URL starting with this + prefix is authorized to request a ticket + +A2_IDP_CAS_PROVIDER Class implementating CAS views, default to + `authentic2_idp_cas.views.CasProvider` +A2_IDP_CAS_TICKET_EXPIRATION Ticket lifetime diff --git a/authentic2_idp_cas/__init__.py b/authentic2_idp_cas/__init__.py index 575a7f0..c93259f 100644 --- a/authentic2_idp_cas/__init__.py +++ b/authentic2_idp_cas/__init__.py @@ -1,6 +1,7 @@ -from django.utils.timezone import now from django.template.loader import render_to_string +from . import utils + __version__ = '1.0' class Plugin(object): @@ -12,22 +13,15 @@ class Plugin(object): return [__name__] def logout_list(self, request): - from . import models - - qs = models.CasService.objects.filter(accesstoken__user=request.user, - accesstoken__expires__gt=now(), logout_url__isnull=False) \ - .distinct() - - l = [] - for client in qs: - name = client.name - url = client.get_logout_url() + fragments = [] + for name, logout in utils.get_logout_urls(request): + url = logout.get_logout_url() ctx = { - 'needs_iframe': client.logout_use_iframe, + 'needs_iframe': logout.logout_use_iframe, 'name': name, 'url': url, - 'iframe_timeout': client.logout_use_iframe_timeout, + 'iframe_timeout': logout.logout_use_iframe_timeout, } - content = render_to_string('idp/saml/logout_fragment.html', ctx) - l.append(content) - return l + content = render_to_string('authentic2_idp_cas/logout_fragment.html', ctx) + fragments.append(content) + return fragments diff --git a/authentic2_idp_cas/app_settings.py b/authentic2_idp_cas/app_settings.py index 4560fd4..5bddd8c 100644 --- a/authentic2_idp_cas/app_settings.py +++ b/authentic2_idp_cas/app_settings.py @@ -1,28 +1,35 @@ -from django.utils.importlib import import_module - class AppSettings(object): + __DEFAULTS = { + 'SERVICES': (), + } def __init__(self, prefix): self.prefix = prefix @property def PROVIDER(self): + from django.utils.importlib import import_module cas_provider = self._setting('CAS_PROVIDER', 'authentic2_idp_cas.views.Authentic2CasProvider') module, cls = cas_provider.rsplit('.', 1) module = import_module(module) return getattr(module, cls) + @property def TICKET_EXPIRATION(self): return self._setting('TICKET_EXPIRATION', 240) - def _setting(self, name, dflt): from django.conf import settings return getattr(settings, self.prefix + name, dflt) + def __getattr__(self, name): + if name not in self.__DEFAULTS: + raise AttributeError(name) + return self._setting(name, self.__DEFAULTS[name]) + # Ugly? Guido recommends this himself ... # http://mail.python.org/pipermail/python-ideas/2012-May/014969.html diff --git a/authentic2_idp_cas/managers.py b/authentic2_idp_cas/managers.py index 106819d..ee7dea4 100644 --- a/authentic2_idp_cas/managers.py +++ b/authentic2_idp_cas/managers.py @@ -19,4 +19,15 @@ class CasTicketQuerySet(query.QuerySet): qs = self.filter(creation__lt=now()-delta) qs.delete() + +class CasServiceQuerySet(query.QuerySet): + def for_domain(self, domain): + q = query.Q(domain=domain) + parts = domain.split('.') + for i in range(1, len(parts)): + q |= query.Q(domain='.%s' % '.'.join(parts[i:])) + return self.filter(q).order_by('-domain') + +CasServiceManager = managers.PassThroughManager.for_queryset_class(CasServiceQuerySet) + CasTicketManager = managers.PassThroughManager.for_queryset_class(CasTicketQuerySet) diff --git a/authentic2_idp_cas/migrations/0001_initial.py b/authentic2_idp_cas/migrations/0001_initial.py new file mode 100644 index 0000000..25b876d --- /dev/null +++ b/authentic2_idp_cas/migrations/0001_initial.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'CasTicket' + db.create_table(u'authentic2_idp_cas_casticket', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('ticket_id', self.gf('django.db.models.fields.CharField')(max_length=64)), + ('renew', self.gf('django.db.models.fields.BooleanField')(default=False)), + ('validity', self.gf('django.db.models.fields.BooleanField')(default=False)), + ('service', self.gf('django.db.models.fields.CharField')(max_length=256)), + ('user', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)), + ('creation', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('expire', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)), + ('session_key', self.gf('django.db.models.fields.CharField')(db_index=True, max_length=64, blank=True)), + )) + db.send_create_signal(u'authentic2_idp_cas', ['CasTicket']) + + # Adding model 'CasService' + db.create_table(u'authentic2_idp_cas_casservice', ( + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('logout_url', self.gf('django.db.models.fields.URLField')(max_length=255, null=True, blank=True)), + ('logout_use_iframe', self.gf('django.db.models.fields.BooleanField')(default=False)), + ('logout_use_iframe_timeout', self.gf('django.db.models.fields.PositiveIntegerField')(default=300)), + ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=128)), + ('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=128)), + ('domain', self.gf('django.db.models.fields.CharField')(unique=True, max_length=128)), + )) + db.send_create_signal(u'authentic2_idp_cas', ['CasService']) + + + def backwards(self, orm): + # Deleting model 'CasTicket' + db.delete_table(u'authentic2_idp_cas_casticket') + + # Deleting model 'CasService' + db.delete_table(u'authentic2_idp_cas_casservice') + + + models = { + u'authentic2_idp_cas.casservice': { + 'Meta': {'object_name': 'CasService'}, + 'domain': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'logout_url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'logout_use_iframe': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'logout_use_iframe_timeout': ('django.db.models.fields.PositiveIntegerField', [], {'default': '300'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '128'}) + }, + u'authentic2_idp_cas.casticket': { + 'Meta': {'object_name': 'CasTicket'}, + 'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'expire': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'renew': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'service': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'session_key': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'blank': 'True'}), + 'ticket_id': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'user': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'validity': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + } + } + + complete_apps = ['authentic2_idp_cas'] \ No newline at end of file diff --git a/authentic2_idp_cas/migrations/__init__.py b/authentic2_idp_cas/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/authentic2_idp_cas/models.py b/authentic2_idp_cas/models.py index 7e1985e..b146575 100644 --- a/authentic2_idp_cas/models.py +++ b/authentic2_idp_cas/models.py @@ -1,11 +1,10 @@ -from datetime import timedelta - from django.db import models from django.utils.translation import ugettext_lazy as _ +from django.utils.timezone import now from authentic2.models import LogoutUrlAbstract -from . import app_setting, managers +from . import managers class CasTicket(models.Model): @@ -15,10 +14,11 @@ class CasTicket(models.Model): renew = models.BooleanField(default=False) validity = models.BooleanField(default=False) service = models.CharField(max_length=256) - user = models.CharField(max_length=128,blank=True,null=True) + user = models.CharField(max_length=128, blank=True, null=True) creation = models.DateTimeField(auto_now_add=True) '''Duration length for the ticket as seconds''' expire = models.DateTimeField(blank=True, null=True) + session_key = models.CharField(max_length=64, db_index=True, blank=True) objects = managers.CasTicketManager() @@ -38,5 +38,9 @@ class CasService(LogoutUrlAbstract): domain = models.CharField(max_length=128, unique=True, verbose_name=_('domain')) - class Meta: + objects = managers.CasServiceManager() + + class Meta: + verbose_name = _('cas service') + verbose_name_plural = _('cas services') diff --git a/authentic2_idp_cas/templates/authentic2_idp_cas/logout_fragment.html b/authentic2_idp_cas/templates/authentic2_idp_cas/logout_fragment.html new file mode 100644 index 0000000..f9294f0 --- /dev/null +++ b/authentic2_idp_cas/templates/authentic2_idp_cas/logout_fragment.html @@ -0,0 +1,10 @@ +{% load i18n %} +