From 53dd351f9ed97088251a1c2267f1382f22f13e89 Mon Sep 17 00:00:00 2001
From: Benjamin Dauvergne <bdauvergne@entrouvert.com>
Date: Tue, 3 Jun 2014 10:39:06 +0200
Subject: [PATCH 1/3] add attribute source models, AttributeSource is a base
 class for all attribute source models

---
 .../migrations/0016_auto__add_attributesource.py   |  132 +++++++++
 authentic2/models.py                               |   41 ++-
 authentic2/saml/admin.py                           |    2 +
 authentic2/saml/migrations/0040_auto.py            |  297 ++++++++++++++++++++
 authentic2/saml/models.py                          |    4 +-
 5 files changed, 467 insertions(+), 9 deletions(-)
 create mode 100644 authentic2/migrations/0016_auto__add_attributesource.py
 create mode 100644 authentic2/saml/migrations/0040_auto.py

diff --git a/authentic2/migrations/0016_auto__add_attributesource.py b/authentic2/migrations/0016_auto__add_attributesource.py
new file mode 100644
index 0000000..b90b7c7
--- /dev/null
+++ b/authentic2/migrations/0016_auto__add_attributesource.py
@@ -0,0 +1,132 @@
+# -*- 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 'AttributeSource'
+        db.create_table(u'authentic2_attributesource', (
+            (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('description', self.gf('django.db.models.fields.CharField')(max_length=100)),
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=50)),
+        ))
+        db.send_create_signal(u'authentic2', ['AttributeSource'])
+
+
+    def backwards(self, orm):
+        # Deleting model 'AttributeSource'
+        db.delete_table(u'authentic2_attributesource')
+
+
+    models = {
+        u'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        u'auth.permission': {
+            'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        u'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+        },
+        u'authentic2.attribute': {
+            'Meta': {'object_name': 'Attribute'},
+            'asked_on_registration': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'kind': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
+            'label': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '63'}),
+            'multiple': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '256'}),
+            'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'user_editable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'user_visible': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+        },
+        u'authentic2.attributesource': {
+            'Meta': {'object_name': 'AttributeSource'},
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        u'authentic2.attributevalue': {
+            'Meta': {'object_name': 'AttributeValue'},
+            'attribute': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['authentic2.Attribute']"}),
+            'content': ('django.db.models.fields.TextField', [], {}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {})
+        },
+        u'authentic2.authenticationevent': {
+            'Meta': {'object_name': 'AuthenticationEvent'},
+            'how': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'nonce': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'when': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'who': ('django.db.models.fields.CharField', [], {'max_length': '80'})
+        },
+        u'authentic2.deleteduser': {
+            'Meta': {'object_name': 'DeletedUser'},
+            'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
+        },
+        u'authentic2.federatedid': {
+            'Meta': {'object_name': 'FederatedId'},
+            'about': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'id_format': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'id_value': ('django.db.models.fields.TextField', [], {}),
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'service': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        u'authentic2.logouturl': {
+            'Meta': {'object_name': 'LogoutUrl'},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+            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'}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {})
+        },
+        u'authentic2.userexternalid': {
+            'Meta': {'object_name': 'UserExternalId'},
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'external_id': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'source': ('django.db.models.fields.URLField', [], {'max_length': '256'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
+        },
+        u'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        }
+    }
+
+    complete_apps = ['authentic2']
\ No newline at end of file
diff --git a/authentic2/models.py b/authentic2/models.py
index 838fb3d..721657b 100644
--- a/authentic2/models.py
+++ b/authentic2/models.py
@@ -13,20 +13,17 @@ from django.utils.translation import ugettext_lazy as _
 from django.contrib.auth.models import (AbstractBaseUser, PermissionsMixin,
         BaseUserManager, SiteProfileNotAvailable)
 from django.contrib.auth import load_backend
-
-from model_utils.managers import QueryManager
-
-from . import attribute_kinds
-
-
 try:
     from django.contrib.contenttypes.fields import GenericForeignKey
 except ImportError:
     from django.contrib.contenttypes.generic import GenericForeignKey
 from django.contrib.contenttypes.models import ContentType
 
+from model_utils.managers import QueryManager, InheritanceManager
+from safe_expression import SafeExpression, InvalidExpression
+
 
-from . import managers, plugins
+from . import managers, plugins, attribute_kinds
 
 plugins.init()
 
@@ -376,3 +373,33 @@ class AttributeValue(models.Model):
     class Meta:
         verbose_name = _('attribute value')
         verbose_name_plural = _('attribute values')
+
+class AttributeSource(models.Model):
+    '''Base class for any attribute generator'''
+    description = models.CharField(max_length=100)
+    name = models.CharField(max_length=50)
+
+    objects = InheritanceManager()
+
+    def get_definition(self, ctx):
+        raise NotImplementedError
+
+    def __unicode__(self):
+        return self.description + u' (%s)' % self.name
+
+    class Meta:
+        verbose_name = _('attribute source')
+        verbose_name_plural = _('attribute sources')
+
+
+class ModelWithAttributeSources(models.Model):
+    attribute_sources = models.ManyToManyField(AttributeSource, blank=True)
+
+    def add_attribute_sources_to_context(self, ctx):
+        '''Fill a context with local attribute sources'''
+        attribute_sources = ctx.setdefault('__attribute_sources__', [])
+        for source in self.attribute_sources.select_subclasses():
+            attribute_sources.append(source.get_definition())
+
+    class Meta:
+        abstract = True
diff --git a/authentic2/saml/admin.py b/authentic2/saml/admin.py
index 1073fc3..1821991 100644
--- a/authentic2/saml/admin.py
+++ b/authentic2/saml/admin.py
@@ -253,8 +253,10 @@ class SPOptionsIdPPolicyAdmin(admin.ModelAdmin):
             'needs_iframe_logout',
             'iframe_logout_timeout',
             'http_method_for_slo_request',
+            'attribute_sources',
     )
 
+    filter_horizontal = [ 'attribute_sources' ]
 
 admin.site.register(SPOptionsIdPPolicy, SPOptionsIdPPolicyAdmin)
 admin.site.register(LibertyProvider, LibertyProviderAdmin)
diff --git a/authentic2/saml/migrations/0040_auto.py b/authentic2/saml/migrations/0040_auto.py
new file mode 100644
index 0000000..ebbb664
--- /dev/null
+++ b/authentic2/saml/migrations/0040_auto.py
@@ -0,0 +1,297 @@
+# -*- 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 M2M table for field attribute_sources on 'SPOptionsIdPPolicy'
+        m2m_table_name = db.shorten_name(u'saml_spoptionsidppolicy_attribute_sources')
+        db.create_table(m2m_table_name, (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('spoptionsidppolicy', models.ForeignKey(orm[u'saml.spoptionsidppolicy'], null=False)),
+            ('attributesource', models.ForeignKey(orm[u'authentic2.attributesource'], null=False))
+        ))
+        db.create_unique(m2m_table_name, ['spoptionsidppolicy_id', 'attributesource_id'])
+
+
+    def backwards(self, orm):
+        # Removing M2M table for field attribute_sources on 'SPOptionsIdPPolicy'
+        db.delete_table(db.shorten_name(u'saml_spoptionsidppolicy_attribute_sources'))
+
+
+    models = {
+        u'attribute_aggregator.attributeitem': {
+            'Meta': {'object_name': 'AttributeItem'},
+            'attribute_name': ('django.db.models.fields.CharField', [], {'default': "('OpenLDAProotDSE', 'OpenLDAProotDSE')", 'max_length': '100'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'output_name_format': ('django.db.models.fields.CharField', [], {'default': "('urn:oasis:names:tc:SAML:2.0:attrname-format:uri', 'SAMLv2 URI')", 'max_length': '100'}),
+            'output_namespace': ('django.db.models.fields.CharField', [], {'default': "('Default', 'Default')", 'max_length': '100'}),
+            'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['attribute_aggregator.AttributeSource']", 'null': 'True', 'blank': 'True'})
+        },
+        u'attribute_aggregator.attributelist': {
+            'Meta': {'object_name': 'AttributeList'},
+            'attributes': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'attributes of the list'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['attribute_aggregator.AttributeItem']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'})
+        },
+        u'attribute_aggregator.attributesource': {
+            'Meta': {'object_name': 'AttributeSource'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}),
+            'namespace': ('django.db.models.fields.CharField', [], {'default': "('Default', 'Default')", 'max_length': '100'})
+        },
+        u'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        u'auth.permission': {
+            'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        u'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+        },
+        u'authentic2.attributesource': {
+            'Meta': {'object_name': 'AttributeSource'},
+            'description': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        u'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        u'idp.attributepolicy': {
+            'Meta': {'object_name': 'AttributePolicy'},
+            'allow_attributes_selection': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'ask_consent_attributes': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'attribute_filter_for_sso_from_push_sources': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'filter attributes of push sources with list'", 'null': 'True', 'to': u"orm['attribute_aggregator.AttributeList']"}),
+            'attribute_list_for_sso_from_pull_sources': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attributes from pull sources'", 'null': 'True', 'to': u"orm['attribute_aggregator.AttributeList']"}),
+            'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'filter_source_of_filtered_attributes': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'forward_attributes_from_push_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'map_attributes_from_push_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'map_attributes_of_filtered_attributes': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+            'output_name_format': ('django.db.models.fields.CharField', [], {'default': "('urn:oasis:names:tc:SAML:2.0:attrname-format:uri', 'SAMLv2 URI')", 'max_length': '100'}),
+            'output_namespace': ('django.db.models.fields.CharField', [], {'default': "('Default', 'Default')", 'max_length': '100'}),
+            'send_error_and_no_attrs_if_missing_required_attrs': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'source_filter_for_sso_from_push_sources': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'filter attributes of push sources with sources'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['attribute_aggregator.AttributeSource']"})
+        },
+        u'saml.authorizationattributemap': {
+            'Meta': {'object_name': 'AuthorizationAttributeMap'},
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'})
+        },
+        u'saml.authorizationattributemapping': {
+            'Meta': {'object_name': 'AuthorizationAttributeMapping'},
+            'attribute_name': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'attribute_value': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'attribute_value_format': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'map': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['saml.AuthorizationAttributeMap']"}),
+            'source_attribute_name': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'})
+        },
+        u'saml.authorizationsppolicy': {
+            'Meta': {'object_name': 'AuthorizationSPPolicy'},
+            'attribute_map': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'authorization_attributes'", 'null': 'True', 'to': u"orm['saml.AuthorizationAttributeMap']"}),
+            'default_denial_message': ('django.db.models.fields.CharField', [], {'default': "u'You are not authorized to access the service.'", 'max_length': '80'}),
+            'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'})
+        },
+        u'saml.idpoptionssppolicy': {
+            'Meta': {'object_name': 'IdPOptionsSPPolicy'},
+            'accept_slo': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'allow_create': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'back_url': ('django.db.models.fields.CharField', [], {'default': "'/'", 'max_length': '200'}),
+            'binding_for_sso_response': ('django.db.models.fields.CharField', [], {'default': "'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact'", 'max_length': '200'}),
+            'enable_binding_for_sso_response': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'enable_http_method_for_defederation_request': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'enable_http_method_for_slo_request': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'force_user_consent': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'forward_slo': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'handle_persistent': ('django.db.models.fields.CharField', [], {'default': "'AUTHSAML2_UNAUTH_PERSISTENT_ACCOUNT_LINKING_BY_AUTH'", 'max_length': '200'}),
+            'handle_transient': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '200'}),
+            'http_method_for_defederation_request': ('django.db.models.fields.IntegerField', [], {'default': '5', 'max_length': '200'}),
+            'http_method_for_slo_request': ('django.db.models.fields.IntegerField', [], {'default': '4', 'max_length': '200'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}),
+            'no_nameid_policy': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'persistent_identifier_attribute': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+            'requested_name_id_format': ('django.db.models.fields.CharField', [], {'default': "'none'", 'max_length': '200'}),
+            'transient_is_persistent': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'want_authn_request_signed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'want_force_authn_request': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'want_is_passive_authn_request': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+        },
+        u'saml.keyvalue': {
+            'Meta': {'object_name': 'KeyValue'},
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'primary_key': 'True'}),
+            'value': ('authentic2.saml.fields.PickledObjectField', [], {})
+        },
+        u'saml.libertyartifact': {
+            'Meta': {'object_name': 'LibertyArtifact'},
+            'artifact': ('django.db.models.fields.CharField', [], {'max_length': '128', 'primary_key': 'True'}),
+            'content': ('django.db.models.fields.TextField', [], {}),
+            'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'provider_id': ('django.db.models.fields.CharField', [], {'max_length': '256'})
+        },
+        u'saml.libertyassertion': {
+            'Meta': {'object_name': 'LibertyAssertion'},
+            'assertion': ('django.db.models.fields.TextField', [], {}),
+            'assertion_id': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'provider_id': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'session_index': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+        },
+        u'saml.libertyfederation': {
+            'Meta': {'object_name': 'LibertyFederation'},
+            'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'idp': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['saml.LibertyIdentityProvider']", 'null': 'True', 'blank': 'True'}),
+            'last_modification': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'name_id_content': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name_id_format': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+            'name_id_qualifier': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}),
+            'name_id_sp_name_qualifier': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}),
+            'sp': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['saml.LibertyServiceProvider']", 'null': 'True', 'blank': 'True'}),
+            'termination_notified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'})
+        },
+        u'saml.libertyidentityprovider': {
+            'Meta': {'object_name': 'LibertyIdentityProvider'},
+            'authorization_policy': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'authorization_policy'", 'null': 'True', 'to': u"orm['saml.AuthorizationSPPolicy']"}),
+            'enable_following_authorization_policy': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'enable_following_idp_options_policy': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'idp_options_policy': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'idp_options_policy'", 'null': 'True', 'to': u"orm['saml.IdPOptionsSPPolicy']"}),
+            'liberty_provider': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'identity_provider'", 'unique': 'True', 'primary_key': 'True', 'to': u"orm['saml.LibertyProvider']"})
+        },
+        u'saml.libertymanagedump': {
+            'Meta': {'object_name': 'LibertyManageDump'},
+            'django_session_key': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'manage_dump': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        u'saml.libertyprovider': {
+            'Meta': {'ordering': "('name',)", 'object_name': 'LibertyProvider'},
+            'ca_cert_chain': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'entity_id': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '200'}),
+            'entity_id_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}),
+            'federation_source': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'metadata': ('django.db.models.fields.TextField', [], {}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '140', 'blank': 'True'}),
+            'protocol_conformance': ('django.db.models.fields.IntegerField', [], {'max_length': '10'}),
+            'public_key': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '140'}),
+            'ssl_certificate': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        u'saml.libertyproviderpolicy': {
+            'Meta': {'object_name': 'LibertyProviderPolicy'},
+            'authn_request_signature_check_hint': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
+        },
+        u'saml.libertyserviceprovider': {
+            'Meta': {'object_name': 'LibertyServiceProvider'},
+            'attribute_policy': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attribute_policy'", 'null': 'True', 'to': u"orm['idp.AttributePolicy']"}),
+            'enable_following_attribute_policy': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'enable_following_sp_options_policy': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'liberty_provider': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'service_provider'", 'unique': 'True', 'primary_key': 'True', 'to': u"orm['saml.LibertyProvider']"}),
+            'policy': ('django.db.models.fields.related.ForeignKey', [], {'default': '1', 'to': u"orm['saml.LibertyProviderPolicy']", 'null': 'True'}),
+            'sp_options_policy': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'sp_options_policy'", 'null': 'True', 'to': u"orm['saml.SPOptionsIdPPolicy']"})
+        },
+        u'saml.libertysession': {
+            'Meta': {'object_name': 'LibertySession'},
+            'assertion': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['saml.LibertyAssertion']", 'null': 'True', 'blank': 'True'}),
+            'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'django_session_key': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'federation': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['saml.LibertyFederation']", 'null': 'True', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name_id_content': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name_id_format': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True'}),
+            'name_id_qualifier': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
+            'name_id_sp_name_qualifier': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
+            'provider_id': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'session_index': ('django.db.models.fields.CharField', [], {'max_length': '80'})
+        },
+        u'saml.libertysessiondump': {
+            'Meta': {'object_name': 'LibertySessionDump'},
+            'django_session_key': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'kind': ('django.db.models.fields.IntegerField', [], {}),
+            'session_dump': ('django.db.models.fields.TextField', [], {'blank': 'True'})
+        },
+        u'saml.libertysessionsp': {
+            'Meta': {'object_name': 'LibertySessionSP'},
+            'django_session_key': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'federation': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['saml.LibertyFederation']"}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'session_index': ('django.db.models.fields.CharField', [], {'max_length': '80'})
+        },
+        u'saml.samlattribute': {
+            'Meta': {'object_name': 'SAMLAttribute'},
+            'attribute_name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+            'friendly_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
+            'name_format': ('django.db.models.fields.CharField', [], {'default': "'basic'", 'max_length': '64'}),
+            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {})
+        },
+        u'saml.spoptionsidppolicy': {
+            'Meta': {'object_name': 'SPOptionsIdPPolicy'},
+            'accept_slo': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'accepted_name_id_format': ('authentic2.saml.fields.MultiSelectField', [], {'max_length': '1024', 'blank': 'True'}),
+            'ask_user_consent': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'attribute_sources': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['authentic2.AttributeSource']", 'symmetrical': 'False'}),
+            'authn_request_signed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'default_name_id_format': ('django.db.models.fields.CharField', [], {'default': "'none'", 'max_length': '256'}),
+            'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'encrypt_assertion': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'encrypt_nameid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'federation_mode': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'forward_slo': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'http_method_for_slo_request': ('django.db.models.fields.IntegerField', [], {'default': '4'}),
+            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'idp_initiated_sso': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'iframe_logout_timeout': ('django.db.models.fields.PositiveIntegerField', [], {'default': '300'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'needs_iframe_logout': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'prefered_assertion_consumer_binding': ('django.db.models.fields.CharField', [], {'default': "'meta'", 'max_length': '4'})
+        }
+    }
+
+    complete_apps = ['saml']
\ No newline at end of file
diff --git a/authentic2/saml/models.py b/authentic2/saml/models.py
index 88f9133..f25117b 100644
--- a/authentic2/saml/models.py
+++ b/authentic2/saml/models.py
@@ -24,7 +24,7 @@ except ImportError:
 from fields import PickledObjectField, MultiSelectField
 
 from . import app_settings, managers
-from .. import managers as a2_managers
+from .. import managers as a2_managers, models as a2_models
 
 def metadata_validator(meta):
     provider=lasso.Provider.newFromBuffer(lasso.PROVIDER_ROLE_ANY, meta.encode('utf8'))
@@ -254,7 +254,7 @@ _("This IdP sends a transient NameID but you want a persistent behaviour for you
         return self.name
 
 
-class SPOptionsIdPPolicy(models.Model):
+class SPOptionsIdPPolicy(a2_models.ModelWithAttributeSources):
     '''
         Policies configured as a SAML2 identity provider.
 
-- 
1.7.10.4

