Projet

Général

Profil

0004-sync-metadata-load-AttributeConsumingService-section.patch

Benjamin Dauvergne, 03 avril 2015 22:24

Télécharger (14,6 ko)

Voir les différences:

Subject: [PATCH 4/4] sync-metadata: load AttributeConsumingService sections of
 SAML 2.0 metadata files (fixes #6847)

 doc/sync-metadata_script.rst                       | 114 +++++++++++++++++++--
 .../saml/management/commands/sync-metadata.py      |  72 ++++++++++++-
 2 files changed, 179 insertions(+), 7 deletions(-)
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
-