0004-sync-metadata-load-AttributeConsumingService-section.patch
doc/sync-metadata_script.rst | ||
---|---|---|
15 | 15 | |
16 | 16 |
An example of such a file used in production is the global metadata file of |
17 | 17 |
the identity federation of French universities that can be found at http://... |
18 | 18 | |
19 | 19 |
Use the following command:: |
20 | 20 | |
21 | 21 |
path_to_project/authentic2$ python manage.py sync-metadata file_name [options] |
22 | 22 | |
23 |
Configuration of attributes |
|
24 |
=========================== |
|
25 | ||
26 |
If a service provider has AttributeConsumingService nodes in its |
|
27 |
SPSSODescriptor then we create an attribute declaration for each declared |
|
28 |
attribute. If the attribute is optional, the attribute declaration is created |
|
29 |
disabled. |
|
30 | ||
31 |
Currently it only supports the LDAP and the LDAP attribute profile of SAML, |
|
32 |
i.e. SAML attribute names must be LDAP attributes oid, the NameFormat must be |
|
33 |
URI, and an LDAP server must declared so that LDAP attributes can be resolved. |
|
34 |
Authentic2 contains a databases of the more common LDAP schemas to help the |
|
35 |
resolution of attributes OIDs. |
|
36 | ||
37 |
Example of an AttributeConsumingService node:: |
|
38 | ||
39 |
<md:AttributeConsumingService index="0"> |
|
40 |
<md:ServiceName |
|
41 |
xml:lang="fr">Université Paris 1 - cours en ligne</md:ServiceName> |
|
42 | ||
43 |
<md:ServiceDescription xml:lang="fr">Cours en ligne de l'université |
|
44 |
Paris 1 Panthéon - Sorbonne (LMS Moodle) |
|
45 |
</md:ServiceDescription> |
|
46 | ||
47 | ||
48 |
<md:RequestedAttribute FriendlyName="sn" Name="urn:oid:2.5.4.4" |
|
49 |
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" |
|
50 |
isRequired="true"> |
|
51 | ||
52 |
</md:RequestedAttribute> |
|
53 | ||
54 |
<md:RequestedAttribute FriendlyName="mail" |
|
55 |
Name="urn:oid:0.9.2342.19200300.100.1.3" |
|
56 |
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" |
|
57 |
isRequired="true"> |
|
58 | ||
59 |
</md:RequestedAttribute> |
|
60 | ||
61 |
<md:RequestedAttribute FriendlyName="displayName" |
|
62 |
Name="urn:oid:2.16.840.1.113730.3.1.241" |
|
63 |
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" |
|
64 |
isRequired="true"> |
|
65 | ||
66 |
</md:RequestedAttribute> |
|
67 | ||
68 |
<md:RequestedAttribute FriendlyName="eduPersonPrincipalName" |
|
69 |
Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" |
|
70 |
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" |
|
71 |
isRequired="true"> |
|
72 | ||
73 |
</md:RequestedAttribute> |
|
74 | ||
75 |
<md:RequestedAttribute FriendlyName="eduPersonAffiliation" |
|
76 |
Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.1" |
|
77 |
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" |
|
78 |
isRequired="true"> |
|
79 | ||
80 |
</md:RequestedAttribute> |
|
81 | ||
82 |
<md:RequestedAttribute FriendlyName="givenName" Name="urn:oid:2.5.4.42" |
|
83 |
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" |
|
84 |
isRequired="true"> |
|
85 | ||
86 |
</md:RequestedAttribute> |
|
87 | ||
88 |
<md:RequestedAttribute FriendlyName="cn" Name="urn:oid:2.5.4.3" |
|
89 |
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" |
|
90 |
isRequired="true"> |
|
91 | ||
92 |
</md:RequestedAttribute> |
|
93 | ||
94 |
</md:AttributeConsumingService> |
|
95 | ||
96 |
If you do not want the attribute declarations to be automatically created pass |
|
97 |
the option `--dont-load-attribute-consuming-service` to the `sync-metadata` command. |
|
98 | ||
23 | 99 |
Options |
24 | 100 |
======= |
25 | 101 | |
26 | 102 |
* idp |
27 | 103 | |
28 | 104 |
Load only identity providers of the metadata file. |
29 | 105 | |
30 | 106 |
* sp |
... | ... | |
38 | 114 | |
39 | 115 |
Reloading a metadata file, when a provider with same entity is found, it is |
40 | 116 |
updated. If a provider in the metadata file does not exist it is created. |
41 | 117 |
If a provider exists in the system but not in the metadata file, it is |
42 | 118 |
removed. |
43 | 119 | |
44 | 120 |
**For reloading, a source can only be associated with a unique metadata |
45 | 121 |
file. This is due to the fact that all providers of a source not found in |
46 |
the metadata file are removed.** |
|
122 |
the metadata file are removed.** ::
|
|
47 | 123 | |
48 |
:: |
|
49 | ||
50 |
path_to_project/authentic2$ python manage.py sync-metadata file_name --source=french_federation |
|
124 |
path_to_project/authentic2$ python manage.py sync-metadata file_name --source=french_federation |
|
51 | 125 | |
52 | 126 |
* sp-policy |
53 | 127 | |
54 | 128 |
To configure the SAML2 parameters of service providers imported with the |
55 | 129 |
script, a policy of type SPOptionsIdPPolicy must be created in the |
56 | 130 |
the administration interface. |
57 | 131 |
Either it is a global policy 'Default' or 'All' or it is a regular policy. |
58 | 132 |
If it is a regular policy, the policy name can be specified in parameter |
... | ... | |
68 | 142 |
To configure the SAML2 parameters of identity providers imported with the |
69 | 143 |
script, a policy of type IdPOptionsSPPolicy must be created in the |
70 | 144 |
the administration interface. |
71 | 145 |
Either it is a global policy 'Default' or 'All' or it is a regular policy. |
72 | 146 |
If it is a regular policy, the policy name can be specified in parameter |
73 | 147 |
of the script with this option. |
74 | 148 |
The policy is then associated to all service providers created. |
75 | 149 | |
76 |
:: |
|
150 |
::
|
|
77 | 151 | |
78 |
path_to_project/authentic2$ python manage.py sync-metadata file_name --idp-policy=idp_policy_name |
|
152 |
path_to_project/authentic2$ python manage.py sync-metadata file_name --idp-policy=idp_policy_name
|
|
79 | 153 | |
80 | 154 |
* delete |
81 | 155 | |
82 | 156 |
With no options, all providers are deleted. |
83 | 157 | |
84 | 158 |
With the source option, only providers with the source name given are deleted. |
85 | 159 | |
86 | 160 |
**This option can not be combined with options idp and sp.** |
87 | 161 | |
88 | 162 |
* ignore-errors |
89 | 163 | |
90 | 164 |
If loading of one EntityDescriptor fails, continue loading |
165 | ||
166 |
* reset-atributes |
|
167 | ||
168 |
When loading shibboleth attribute filter policies, start by removing all |
|
169 |
existing SAML attributes for each provider, beware that it will delete any |
|
170 |
customization of the attribute policy for each service provider. |
|
171 | ||
172 |
* dont-load-attribute-consuming-service |
|
173 | ||
174 |
Prevent loading of the attribute policy from AttributeConsumingService nodes |
|
175 |
in the metadata file. |
|
176 | ||
177 |
* shibboleth-attribute-filter-policy |
|
178 | ||
179 |
Path to a file containing an Attribute Filter Policy for the |
|
180 |
Shibboleth IdP, that will be used to configure SAML attributes for |
|
181 |
each provider. The following schema is supported:: |
|
182 | ||
183 |
<AttributeFilterPolicy id="<whatever>"> |
|
184 |
<PolicyRequirementRule xsi:type="basic:AttributeRequesterString" value="<entityID>" > |
|
185 |
[ |
|
186 |
<AttributeRule attributeID="<attribute-name>"> |
|
187 |
<PermitValueRule xsi:type="basic:ANY"/> |
|
188 |
</AttributeRule> |
|
189 |
]* |
|
190 |
</AttributeFilterPolicy> |
|
191 | ||
192 |
Any other kind of attribute filter policy is unsupported. |
src/authentic2/saml/management/commands/sync-metadata.py | ||
---|---|---|
1 | 1 |
from optparse import make_option |
2 | 2 |
import sys |
3 | 3 |
import xml.etree.ElementTree as etree |
4 | 4 |
import os |
5 | 5 |
import requests |
6 | 6 |
from StringIO import StringIO |
7 |
import warnings |
|
7 | 8 | |
8 | 9 |
from django.core.management.base import BaseCommand, CommandError |
9 | 10 |
from django.template.defaultfilters import slugify |
10 | 11 |
from django.utils.translation import gettext as _ |
11 | 12 | |
12 | 13 |
from authentic2.compat import commit_on_success |
13 | 14 |
from authentic2.compat_lasso import lasso |
14 | 15 |
from authentic2.saml.models import * |
15 | 16 |
from authentic2.saml.shibboleth.afp_parser import parse_attribute_filters_file |
16 | 17 |
from authentic2.attribute_aggregator.core import (get_definition_from_alias, |
17 |
get_full_definition, get_def_name_from_alias) |
|
18 |
get_full_definition, get_def_name_from_alias, get_def_name_from_oid, |
|
19 |
get_definition_from_oid) |
|
18 | 20 | |
19 | 21 |
SAML2_METADATA_UI_HREF = 'urn:oasis:names:tc:SAML:metadata:ui' |
20 | 22 | |
21 | 23 |
def md_element_name(tag_name): |
22 | 24 |
return '{%s}%s' % (lasso.SAML2_METADATA_HREF, tag_name) |
23 | 25 | |
24 | 26 |
def mdui_element_name(tag_name): |
25 | 27 |
return '{%s}%s' % (SAML2_METADATA_UI_HREF, tag_name) |
... | ... | |
27 | 29 |
ENTITY_DESCRIPTOR_TN = md_element_name('EntityDescriptor') |
28 | 30 |
ENTITIES_DESCRIPTOR_TN = md_element_name('EntitiesDescriptor') |
29 | 31 |
IDP_SSO_DESCRIPTOR_TN = md_element_name('IDPSSODescriptor') |
30 | 32 |
SP_SSO_DESCRIPTOR_TN = md_element_name('SPSSODescriptor') |
31 | 33 |
ORGANIZATION_DISPLAY_NAME = md_element_name('OrganizationDisplayName') |
32 | 34 |
ORGANIZATION_NAME = md_element_name('OrganizationName') |
33 | 35 |
ORGANIZATION = md_element_name('Organization') |
34 | 36 |
EXTENSIONS = md_element_name('Extensions') |
37 |
ATTRIBUTE_CONSUMING_SERVICE = md_element_name('AttributeConsumingService') |
|
38 |
SERVICE_NAME = md_element_name('ServiceName') |
|
39 |
SERVICE_DESCRIPTION = md_element_name('ServiceDescription') |
|
40 |
REQUESTED_ATTRIBUTE = md_element_name('RequestedAttribute') |
|
41 | ||
35 | 42 |
UI_INFO = mdui_element_name('UIInfo') |
36 | 43 |
DISPLAY_NAME = mdui_element_name('DisplayName') |
44 | ||
37 | 45 |
ENTITY_ID = 'entityID' |
38 | 46 |
PROTOCOL_SUPPORT_ENUMERATION = 'protocolSupportEnumeration' |
47 |
IS_REQUIRED = 'isRequired' |
|
48 |
NAME_FORMAT = 'NameFormat' |
|
49 |
NAME = 'Name' |
|
50 |
FRIENDLY_NAME = 'FriendlyName' |
|
51 | ||
52 |
def resolve_urn_oid(urn_oid): |
|
53 |
if not urn_oid.startswith('urn:oid:'): |
|
54 |
return None, None |
|
55 |
oid = urn_oid[8:] |
|
56 |
return get_def_name_from_oid(oid), get_definition_from_oid(oid) |
|
39 | 57 | |
40 | 58 |
def build_saml_attribute_kwargs(provider, name): |
41 | 59 |
'''Build SAML attribute following the LDAP profile''' |
42 | 60 |
content_type = ContentType.objects.get_for_model(LibertyProvider) |
43 | 61 |
object_id = provider.pk |
44 | 62 |
attribute_name = name |
45 | 63 |
definition = get_full_definition(name) |
46 | 64 |
if not definition: |
... | ... | |
59 | 77 |
'friendly_name': name, |
60 | 78 |
} |
61 | 79 | |
62 | 80 |
def check_support_saml2(tree): |
63 | 81 |
if tree is not None and lasso.SAML2_PROTOCOL_HREF in tree.get(PROTOCOL_SUPPORT_ENUMERATION): |
64 | 82 |
return True |
65 | 83 |
return False |
66 | 84 | |
85 |
def text_child(tree, tag, default=''): |
|
86 |
elt = tree.find(tag) |
|
87 |
return elt.text if not elt is None else default |
|
88 | ||
89 |
def load_acs(tree, provider, pks, verbosity): |
|
90 |
acss = tree.iter(ATTRIBUTE_CONSUMING_SERVICE) |
|
91 |
for acs in acss: |
|
92 |
for ra in acs.iter(REQUESTED_ATTRIBUTE): |
|
93 |
oid = ra.get(NAME, '') |
|
94 |
name_format = ra.get(NAME_FORMAT, '') |
|
95 |
friendly_name = ra.get(FRIENDLY_NAME, '') |
|
96 |
is_required = ra.get(IS_REQUIRED, 'false') == 'true' |
|
97 |
if name_format != lasso.SAML2_ATTRIBUTE_NAME_FORMAT_URI: |
|
98 |
continue |
|
99 |
def_name, defn = resolve_urn_oid(oid) |
|
100 |
if def_name is None: |
|
101 |
warnings.warn('attribute %s/%s unsupported on service provider %s' % ( |
|
102 |
oid, name_format, provider.entity_id)) |
|
103 |
continue |
|
104 |
content_type = ContentType.objects.get_for_model(LibertyProvider) |
|
105 |
object_id = provider.pk |
|
106 |
kwargs = { |
|
107 |
'content_type': content_type, |
|
108 |
'object_id': object_id, |
|
109 |
'name_format': 'uri', |
|
110 |
'name': oid, |
|
111 |
} |
|
112 |
defaults = { |
|
113 |
'attribute_name': def_name.lower(), |
|
114 |
'friendly_name': friendly_name or def_name, |
|
115 |
'enabled': is_required, |
|
116 |
} |
|
117 | ||
118 |
try: |
|
119 |
attribute, created = SAMLAttribute.objects.get_or_create(defaults=defaults, |
|
120 |
**kwargs) |
|
121 |
if created and verbosity > 1: |
|
122 |
print _('Created new attribute %(name)s for %(provider)s') % \ |
|
123 |
{'name': oid, 'provider': provider} |
|
124 |
pks.append(attribute.pk) |
|
125 |
except SAMLAttribute.MultipleObjectsReturned: |
|
126 |
pks.extend(SAMLAttribute.objects.filter(**kwargs).values_list('pk', flat=True)) |
|
127 | ||
128 | ||
67 | 129 |
def load_one_entity(tree, options, sp_policy=None, idp_policy=None, afp=None): |
68 | 130 |
'''Load or update an EntityDescriptor into the database''' |
69 | 131 |
verbosity = int(options['verbosity']) |
70 | 132 |
entity_id = tree.get(ENTITY_ID) |
71 | 133 |
name = None |
72 | 134 |
# try mdui nodes |
73 | 135 |
display_name = tree.find('.//%s/%s/%s' % (EXTENSIONS, UI_INFO, DISPLAY_NAME)) |
74 | 136 |
if display_name is not None: |
... | ... | |
129 | 191 |
if sp: |
130 | 192 |
service_provider, created = LibertyServiceProvider.objects.get_or_create( |
131 | 193 |
liberty_provider=provider, |
132 | 194 |
defaults={'enabled': not options['create-disabled']}) |
133 | 195 |
if sp_policy: |
134 | 196 |
service_provider.sp_options_policy = sp_policy |
135 | 197 |
service_provider.save() |
136 | 198 |
pks = [] |
199 |
if options['load_attribute_consuming_service']: |
|
200 |
load_acs(tree, provider, pks, verbosity) |
|
137 | 201 |
if afp and provider.entity_id in afp: |
138 | 202 |
for name in afp[provider.entity_id]: |
139 | 203 |
kwargs, defaults = build_saml_attribute_kwargs(provider, name) |
140 | 204 |
if not kwargs: |
141 | 205 |
if verbosity > 1: |
142 | 206 |
print >>sys.stderr, _('Unable to find an LDAP definition for attribute %(name)s on %(provider)s') % \ |
143 | 207 |
{'name': name, 'provider': provider} |
144 | 208 |
continue |
... | ... | |
199 | 263 |
help='Tag the loaded providers with the given source string, \ |
200 | 264 |
existing providers with the same tag will be removed if they do not exist\ |
201 | 265 |
anymore in the metadata file.'), |
202 | 266 |
make_option('--reset-attributes', |
203 | 267 |
action='store_true', |
204 | 268 |
default=False, |
205 | 269 |
help='When loading shibboleth attribute filter policies, start by ' |
206 | 270 |
'removing all existing SAML attributes for each provider'), |
271 |
make_option('--dont-load-attribute-consuming-service', |
|
272 |
dest='load_attribute_consuming_service', |
|
273 |
default=True, |
|
274 |
action='store_false', |
|
275 |
help='Prevent loading of the attribute policy from ' |
|
276 |
'AttributeConsumingService nodes in the metadata file.'), |
|
207 | 277 |
make_option('--shibboleth-attribute-filter-policy', |
208 | 278 |
dest='attribute-filter-policy', |
209 | 279 |
default=None, |
210 | 280 |
help='''Path to a file containing an Attribute Filter Policy for the |
211 | 281 |
Shibboleth IdP, that will be used to configure SAML attributes for |
212 | 282 |
each provider. The following schema is supported: |
213 | 283 | |
214 | 284 |
<AttributeFilterPolicy id="<whatever>"> |
215 |
- |