sync-metadata: load AttributeConsumingService sections of SAML 2.0 metadata files (fixes #6847)
This commit is contained in:
parent
713701d553
commit
ac04801dcd
|
@ -20,6 +20,82 @@ Use the following command::
|
|||
|
||||
path_to_project/authentic2$ python manage.py sync-metadata file_name [options]
|
||||
|
||||
Configuration of attributes
|
||||
===========================
|
||||
|
||||
If a service provider has AttributeConsumingService nodes in its
|
||||
SPSSODescriptor then we create an attribute declaration for each declared
|
||||
attribute. If the attribute is optional, the attribute declaration is created
|
||||
disabled.
|
||||
|
||||
Currently it only supports the LDAP and the LDAP attribute profile of SAML,
|
||||
i.e. SAML attribute names must be LDAP attributes oid, the NameFormat must be
|
||||
URI, and an LDAP server must declared so that LDAP attributes can be resolved.
|
||||
Authentic2 contains a databases of the more common LDAP schemas to help the
|
||||
resolution of attributes OIDs.
|
||||
|
||||
Example of an AttributeConsumingService node::
|
||||
|
||||
<md:AttributeConsumingService index="0">
|
||||
<md:ServiceName
|
||||
xml:lang="fr">Université Paris 1 - cours en ligne</md:ServiceName>
|
||||
|
||||
<md:ServiceDescription xml:lang="fr">Cours en ligne de l'université
|
||||
Paris 1 Panthéon - Sorbonne (LMS Moodle)
|
||||
</md:ServiceDescription>
|
||||
|
||||
|
||||
<md:RequestedAttribute FriendlyName="sn" Name="urn:oid:2.5.4.4"
|
||||
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
|
||||
isRequired="true">
|
||||
|
||||
</md:RequestedAttribute>
|
||||
|
||||
<md:RequestedAttribute FriendlyName="mail"
|
||||
Name="urn:oid:0.9.2342.19200300.100.1.3"
|
||||
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
|
||||
isRequired="true">
|
||||
|
||||
</md:RequestedAttribute>
|
||||
|
||||
<md:RequestedAttribute FriendlyName="displayName"
|
||||
Name="urn:oid:2.16.840.1.113730.3.1.241"
|
||||
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
|
||||
isRequired="true">
|
||||
|
||||
</md:RequestedAttribute>
|
||||
|
||||
<md:RequestedAttribute FriendlyName="eduPersonPrincipalName"
|
||||
Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
|
||||
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
|
||||
isRequired="true">
|
||||
|
||||
</md:RequestedAttribute>
|
||||
|
||||
<md:RequestedAttribute FriendlyName="eduPersonAffiliation"
|
||||
Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.1"
|
||||
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
|
||||
isRequired="true">
|
||||
|
||||
</md:RequestedAttribute>
|
||||
|
||||
<md:RequestedAttribute FriendlyName="givenName" Name="urn:oid:2.5.4.42"
|
||||
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
|
||||
isRequired="true">
|
||||
|
||||
</md:RequestedAttribute>
|
||||
|
||||
<md:RequestedAttribute FriendlyName="cn" Name="urn:oid:2.5.4.3"
|
||||
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
|
||||
isRequired="true">
|
||||
|
||||
</md:RequestedAttribute>
|
||||
|
||||
</md:AttributeConsumingService>
|
||||
|
||||
If you do not want the attribute declarations to be automatically created pass
|
||||
the option `--dont-load-attribute-consuming-service` to the `sync-metadata` command.
|
||||
|
||||
Options
|
||||
=======
|
||||
|
||||
|
@ -43,11 +119,9 @@ Options
|
|||
|
||||
**For reloading, a source can only be associated with a unique metadata
|
||||
file. This is due to the fact that all providers of a source not found in
|
||||
the metadata file are removed.**
|
||||
the metadata file are removed.** ::
|
||||
|
||||
::
|
||||
|
||||
path_to_project/authentic2$ python manage.py sync-metadata file_name --source=french_federation
|
||||
path_to_project/authentic2$ python manage.py sync-metadata file_name --source=french_federation
|
||||
|
||||
* sp-policy
|
||||
|
||||
|
@ -73,9 +147,9 @@ Options
|
|||
of the script with this option.
|
||||
The policy is then associated to all service providers created.
|
||||
|
||||
::
|
||||
::
|
||||
|
||||
path_to_project/authentic2$ python manage.py sync-metadata file_name --idp-policy=idp_policy_name
|
||||
path_to_project/authentic2$ python manage.py sync-metadata file_name --idp-policy=idp_policy_name
|
||||
|
||||
* delete
|
||||
|
||||
|
@ -88,3 +162,31 @@ Options
|
|||
* ignore-errors
|
||||
|
||||
If loading of one EntityDescriptor fails, continue loading
|
||||
|
||||
* reset-atributes
|
||||
|
||||
When loading shibboleth attribute filter policies, start by removing all
|
||||
existing SAML attributes for each provider, beware that it will delete any
|
||||
customization of the attribute policy for each service provider.
|
||||
|
||||
* dont-load-attribute-consuming-service
|
||||
|
||||
Prevent loading of the attribute policy from AttributeConsumingService nodes
|
||||
in the metadata file.
|
||||
|
||||
* shibboleth-attribute-filter-policy
|
||||
|
||||
Path to a file containing an Attribute Filter Policy for the
|
||||
Shibboleth IdP, that will be used to configure SAML attributes for
|
||||
each provider. The following schema is supported::
|
||||
|
||||
<AttributeFilterPolicy id="<whatever>">
|
||||
<PolicyRequirementRule xsi:type="basic:AttributeRequesterString" value="<entityID>" >
|
||||
[
|
||||
<AttributeRule attributeID="<attribute-name>">
|
||||
<PermitValueRule xsi:type="basic:ANY"/>
|
||||
</AttributeRule>
|
||||
]*
|
||||
</AttributeFilterPolicy>
|
||||
|
||||
Any other kind of attribute filter policy is unsupported.
|
||||
|
|
|
@ -4,6 +4,7 @@ import xml.etree.ElementTree as etree
|
|||
import os
|
||||
import requests
|
||||
from StringIO import StringIO
|
||||
import warnings
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.template.defaultfilters import slugify
|
||||
|
@ -14,7 +15,8 @@ from authentic2.compat_lasso import lasso
|
|||
from authentic2.saml.models import *
|
||||
from authentic2.saml.shibboleth.afp_parser import parse_attribute_filters_file
|
||||
from authentic2.attribute_aggregator.core import (get_definition_from_alias,
|
||||
get_full_definition, get_def_name_from_alias)
|
||||
get_full_definition, get_def_name_from_alias, get_def_name_from_oid,
|
||||
get_definition_from_oid)
|
||||
|
||||
SAML2_METADATA_UI_HREF = 'urn:oasis:names:tc:SAML:metadata:ui'
|
||||
|
||||
|
@ -32,10 +34,26 @@ ORGANIZATION_DISPLAY_NAME = md_element_name('OrganizationDisplayName')
|
|||
ORGANIZATION_NAME = md_element_name('OrganizationName')
|
||||
ORGANIZATION = md_element_name('Organization')
|
||||
EXTENSIONS = md_element_name('Extensions')
|
||||
ATTRIBUTE_CONSUMING_SERVICE = md_element_name('AttributeConsumingService')
|
||||
SERVICE_NAME = md_element_name('ServiceName')
|
||||
SERVICE_DESCRIPTION = md_element_name('ServiceDescription')
|
||||
REQUESTED_ATTRIBUTE = md_element_name('RequestedAttribute')
|
||||
|
||||
UI_INFO = mdui_element_name('UIInfo')
|
||||
DISPLAY_NAME = mdui_element_name('DisplayName')
|
||||
|
||||
ENTITY_ID = 'entityID'
|
||||
PROTOCOL_SUPPORT_ENUMERATION = 'protocolSupportEnumeration'
|
||||
IS_REQUIRED = 'isRequired'
|
||||
NAME_FORMAT = 'NameFormat'
|
||||
NAME = 'Name'
|
||||
FRIENDLY_NAME = 'FriendlyName'
|
||||
|
||||
def resolve_urn_oid(urn_oid):
|
||||
if not urn_oid.startswith('urn:oid:'):
|
||||
return None, None
|
||||
oid = urn_oid[8:]
|
||||
return get_def_name_from_oid(oid), get_definition_from_oid(oid)
|
||||
|
||||
def build_saml_attribute_kwargs(provider, name):
|
||||
'''Build SAML attribute following the LDAP profile'''
|
||||
|
@ -64,6 +82,50 @@ def check_support_saml2(tree):
|
|||
return True
|
||||
return False
|
||||
|
||||
def text_child(tree, tag, default=''):
|
||||
elt = tree.find(tag)
|
||||
return elt.text if not elt is None else default
|
||||
|
||||
def load_acs(tree, provider, pks, verbosity):
|
||||
acss = tree.iter(ATTRIBUTE_CONSUMING_SERVICE)
|
||||
for acs in acss:
|
||||
for ra in acs.iter(REQUESTED_ATTRIBUTE):
|
||||
oid = ra.get(NAME, '')
|
||||
name_format = ra.get(NAME_FORMAT, '')
|
||||
friendly_name = ra.get(FRIENDLY_NAME, '')
|
||||
is_required = ra.get(IS_REQUIRED, 'false') == 'true'
|
||||
if name_format != lasso.SAML2_ATTRIBUTE_NAME_FORMAT_URI:
|
||||
continue
|
||||
def_name, defn = resolve_urn_oid(oid)
|
||||
if def_name is None:
|
||||
warnings.warn('attribute %s/%s unsupported on service provider %s' % (
|
||||
oid, name_format, provider.entity_id))
|
||||
continue
|
||||
content_type = ContentType.objects.get_for_model(LibertyProvider)
|
||||
object_id = provider.pk
|
||||
kwargs = {
|
||||
'content_type': content_type,
|
||||
'object_id': object_id,
|
||||
'name_format': 'uri',
|
||||
'name': oid,
|
||||
}
|
||||
defaults = {
|
||||
'attribute_name': def_name.lower(),
|
||||
'friendly_name': friendly_name or def_name,
|
||||
'enabled': is_required,
|
||||
}
|
||||
|
||||
try:
|
||||
attribute, created = SAMLAttribute.objects.get_or_create(defaults=defaults,
|
||||
**kwargs)
|
||||
if created and verbosity > 1:
|
||||
print _('Created new attribute %(name)s for %(provider)s') % \
|
||||
{'name': oid, 'provider': provider}
|
||||
pks.append(attribute.pk)
|
||||
except SAMLAttribute.MultipleObjectsReturned:
|
||||
pks.extend(SAMLAttribute.objects.filter(**kwargs).values_list('pk', flat=True))
|
||||
|
||||
|
||||
def load_one_entity(tree, options, sp_policy=None, idp_policy=None, afp=None):
|
||||
'''Load or update an EntityDescriptor into the database'''
|
||||
verbosity = int(options['verbosity'])
|
||||
|
@ -134,6 +196,8 @@ def load_one_entity(tree, options, sp_policy=None, idp_policy=None, afp=None):
|
|||
service_provider.sp_options_policy = sp_policy
|
||||
service_provider.save()
|
||||
pks = []
|
||||
if options['load_attribute_consuming_service']:
|
||||
load_acs(tree, provider, pks, verbosity)
|
||||
if afp and provider.entity_id in afp:
|
||||
for name in afp[provider.entity_id]:
|
||||
kwargs, defaults = build_saml_attribute_kwargs(provider, name)
|
||||
|
@ -204,6 +268,12 @@ existing providers with the same tag will be removed if they do not exist\
|
|||
default=False,
|
||||
help='When loading shibboleth attribute filter policies, start by '
|
||||
'removing all existing SAML attributes for each provider'),
|
||||
make_option('--dont-load-attribute-consuming-service',
|
||||
dest='load_attribute_consuming_service',
|
||||
default=True,
|
||||
action='store_false',
|
||||
help='Prevent loading of the attribute policy from '
|
||||
'AttributeConsumingService nodes in the metadata file.'),
|
||||
make_option('--shibboleth-attribute-filter-policy',
|
||||
dest='attribute-filter-policy',
|
||||
default=None,
|
||||
|
|
Loading…
Reference in New Issue