Projet

Général

Profil

0001-support-federation-file-loading-19396.patch

Paul Marillonnet, 02 août 2018 09:59

Télécharger (101 ko)

Voir les différences:

Subject: [PATCH] support federation file loading (#19396)

 README                         |  13 +
 mellon/adapters.py             | 153 ++++++++--
 mellon/app_settings.py         |  16 +-
 mellon/federation_utils.py     | 310 +++++++++++++++++++
 mellon/middleware.py           |   2 +-
 mellon/utils.py                | 193 +++++++++---
 mellon/views.py                |  53 +++-
 setup.py                       |   1 +
 tests/conftest.py              |   7 +
 tests/dummy_md.xml             | 367 +++++++++++++++++++++++
 tests/federation-sample.xml    | 530 +++++++++++++++++++++++++++++++++
 tests/test_federation_utils.py |  52 ++++
 tests/test_sso_slo.py          |  88 +++++-
 tests/test_utils.py            | 114 +++++--
 tests/utils.py                 |  12 +-
 15 files changed, 1801 insertions(+), 110 deletions(-)
 create mode 100644 mellon/federation_utils.py
 create mode 100644 tests/dummy_md.xml
 create mode 100644 tests/federation-sample.xml
 create mode 100644 tests/test_federation_utils.py
README
82 82
the absolute path toward a metadata file. All other keys are override
83 83
of generic settings.
84 84

  
85
MELLON_FEDERATIONS
86
------------------
87

  
88
A list of dictionaries, only one key 'FEDERATION' is mandatory in those
89
dictionaries. It should contain the local path or the remote URL for the
90
metadata file describing the SAML-based federation to be loaded in mellon. Both
91
relative and absolute paths are supported.
92
Additional parameters can be given as key/value pairs in the dictionaries, on
93
a similar basis as the aforementioned MELLON_IDENTITY_PROVIDERS config.
94
For each dictionary describing a federation, these parameters will apply to
95
any successfully-loaded provider belonging to that federation.
96
These parameters also override the global settings.
97

  
85 98
MELLON_PUBLIC_KEYS
86 99
------------------
87 100

  
mellon/adapters.py
11 11
from django.contrib.auth.models import Group
12 12
from django.utils import six
13 13
from django.utils.encoding import force_text
14
from django.utils.text import slugify
14 15

  
15 16
from . import utils, app_settings, models
17
from mellon.federation_utils import idp_metadata_store, url2filename, \
18
        idp_metadata_extract_entity_id, idp_metadata_is_cached, \
19
        idp_metadata_load, idp_settings_store, idp_settings_load, \
20
        store_fingerprint
16 21

  
17 22

  
18 23
class UserCreationError(Exception):
......
25 30

  
26 31
    def get_idp(self, entity_id):
27 32
        '''Find the first IdP definition matching entity_id'''
28
        for idp in self.get_idps():
29
            if entity_id == idp['ENTITY_ID']:
30
                return idp
33
        idp = {}
34

  
35
        # First, check whether the provider is cached
36
        if idp_metadata_is_cached(entity_id):
37
            metadata_content = idp_metadata_load(entity_id)
38
            idp.update({'METADATA': metadata_content,
39
                   'ENTITY_ID': entity_id})
40
            # Extra settings loaded if the provider comes from a federation
41
            idp.update(idp_settings_load(entity_id) or {})
42

  
43
        # If not, try to fetch it from the mellon settings
44
        else:
45
            for idp in self.get_identity_providers_setting():
46
                if not idp.get('METADATA_URL') and not idp.get('METADATA'):
47
                    self.logger.error(u'missing METADATA or METADATA_URL in idp %s', idp or '')
48
                    continue
49

  
50
                elif 'METADATA_URL' in idp and 'METADATA' not in idp:
51
                    metadata = utils.get_metadata_from_url(idp)
52
                    if not metadata:
53
                        continue
54
                    idp['METADATA'] = metadata
55

  
56
                if 'ENTITY_ID' not in idp:
57
                    if idp['METADATA'].startswith('/') or idp['METADATA'].startswith('./'):
58
                    # In case the entity ID isn't provided in the settings, it
59
                    # needs to be fetched from the content of the metadata file
60
                        metadata_path = idp['METADATA']
61
                        if 'FEDERATION' in idp:
62
                            metadata_path = default_storage.path(metadata_path)
63
                        content = open(metadata_path, 'r').read()
64
                    else:
65
                        content = idp['METADATA']
66
                    idp['ENTITY_ID'] = idp_metadata_extract_entity_id(content)
67

  
68
                if idp['ENTITY_ID'] == entity_id:
69
                    break
70

  
71
        return idp.copy()
31 72

  
32 73
    def get_identity_providers_setting(self):
33
        return app_settings.IDENTITY_PROVIDERS
74
        # First, providers from federation as declared in the mellon settings
75
        for federation_data in self.get_federations():
76
            if not isinstance(federation_data, dict) or \
77
                    'FEDERATION' not in federation_data:
78
                continue
79
            fed_extra_attrs = federation_data.copy()
80
            # Federation can be declared as URLs. If so, their content needs
81
            # to be fetched and cached
82
            fed_filepath, _ = utils.get_federation_metadata(federation_data.get('FEDERATION'))
83

  
84
            try:
85
                tree = ET.parse(fed_filepath)
86
                root = tree.getroot()
87
                for child in root:
88
                    provider = {}
89
                    entity_id = idp_metadata_extract_entity_id(ET.tostring(child))
90
                    if not entity_id:
91
                        # The XML tag wasn't an IDPSSODescriptor
92
                        continue
93
                    # Store the metadata content in cache
94
                    provider['METADATA'] = idp_metadata_store(ET.tostring(child).decode('utf-8'))
95
                    provider['ENTITY_ID'] = entity_id
96
                    # Add in each provider the federation-wise configuration
97
                    provider.update(fed_extra_attrs)
98
                    idp_settings_store(provider)
99
                    if entity_id:
100
                        store_fingerprint(entity_id)
101
                    yield provider
102
            except:
103
                self.logger.error('Couldn\'t load federation metadata file %r',
104
                                  fed_filepath)
105
                continue
106

  
107
        # Then, the non-federated providers
108
        for extra_provider in app_settings.IDENTITY_PROVIDERS:
109
            if 'ENTITY_ID' in extra_provider:
110
                entity_id = extra_provider.get('ENTITY_ID')
111
            else:
112
                if 'METADATA' in extra_provider:
113
                    metadata = extra_provider.get('METADATA')
114
                elif 'METADATA_URL' in extra_provider:
115
                    metadata = utils.get_metadata_from_url(extra_provider)
116
                else:
117
                    continue
118
                entity_id = idp_metadata_extract_entity_id(metadata)
119

  
120
            if entity_id:
121
                store_fingerprint(entity_id)
122

  
123
            yield extra_provider
124

  
125
    def get_federations(self):
126
        for federation in getattr(app_settings, 'FEDERATIONS', []):
127
            yield federation
34 128

  
35 129
    def get_idps(self):
36 130
        for i, idp in enumerate(self.get_identity_providers_setting()):
37 131
            if 'METADATA_URL' in idp and 'METADATA' not in idp:
38
                verify_ssl_certificate = utils.get_setting(
39
                    idp, 'VERIFY_SSL_CERTIFICATE')
40
                try:
41
                    response = requests.get(idp['METADATA_URL'], verify=verify_ssl_certificate)
42
                    response.raise_for_status()
43
                except requests.exceptions.RequestException as e:
44
                    self.logger.error(
45
                        u'retrieval of metadata URL %r failed with error %s for %d-th idp',
46
                        idp['METADATA_URL'], e, i)
132
                md_content = utils.get_metadata_from_url(idp)
133

  
134
                if not md_content:
47 135
                    continue
48
                idp['METADATA'] = response.text
49
            elif 'METADATA' in idp:
50
                if idp['METADATA'].startswith('/'):
51
                    idp['METADATA'] = open(idp['METADATA']).read()
52
            else:
136

  
137
                if 'FEDERATION' in idp:
138
                    # IdPs from federation are cached on filesystem
139
                    # only the filename is kept in memory
140
                    idp['METADATA'] = idp_metadata_store(md_content)
141
                    entity_id = idp.get('ENTITY_ID')
142
                    if not entity_id:
143
                        idp['ENTITY_ID'] = idp_metadata_extract_entity_id(md_content)
144
                    # load federation-specific configuration
145
                    idp.update(idp_settings_load(idp.get('ENTITY_ID')))
146
                else:
147
                    idp['METADATA'] = md_content
148

  
149
            elif idp.get('METADATA', '').startswith('/') or \
150
                    idp.get('METADATA', '').startswith('./') and \
151
                    'FEDERATION' not in idp:
152
                idp['METADATA'] = open(idp['METADATA'], 'r').read()
153

  
154
            elif not idp.get('METADATA'):
53 155
                self.logger.error(u'missing METADATA or METADATA_URL in %d-th idp', i)
54 156
                continue
55
            if 'ENTITY_ID' not in idp:
56
                try:
57
                    doc = ET.fromstring(idp['METADATA'])
58
                except (TypeError, ET.ParseError):
59
                    self.logger.error(u'METADATA of %d-th idp is invalid', i)
60
                    continue
61
                if doc.tag != '{%s}EntityDescriptor' % lasso.SAML2_METADATA_HREF:
62
                    self.logger.error(u'METADATA of %d-th idp has no EntityDescriptor root tag', i)
63
                    continue
64

  
65
                if not 'entityID' in doc.attrib:
66
                    self.logger.error(
67
                        u'METADATA of %d-th idp has no entityID attribute on its root tag', i)
68
                    continue
69
                idp['ENTITY_ID'] = doc.attrib['entityID']
70 157
            yield idp
71 158

  
72 159
    def authorize(self, idp, saml_attributes):
mellon/app_settings.py
38 38
        'LOGIN_URL': 'mellon_login',
39 39
        'LOGOUT_URL': 'mellon_logout',
40 40
        'ARTIFACT_RESOLVE_TIMEOUT': 10.0,
41
        'FEDERATIONS': [],
41 42
    }
42 43

  
44
    @property
45
    def FEDERATIONS(self):
46
        from django.conf import settings
47
        if settings.hasattr('MELLON_FEDERATIONS'):
48
            federations = settings.MELLON_FEDERATIONS
49
        if isinstance(federations, dict):
50
            federations = [federations]
51
        return federations
52

  
43 53
    @property
44 54
    def IDENTITY_PROVIDERS(self):
45 55
        from django.conf import settings
56
        idps = []
46 57
        try:
47
            idps = settings.MELLON_IDENTITY_PROVIDERS
58
            if hasattr(settings, 'MELLON_IDENTITY_PROVIDERS'):
59
                idps = settings.MELLON_IDENTITY_PROVIDERS
60
            elif not hasattr(settings, 'MELLON_FEDERATIONS'):
61
                raise AttributeError
48 62
        except AttributeError:
49 63
            return []
50 64
        if isinstance(idps, dict):
mellon/federation_utils.py
1
import base64
2
import fcntl
3
import hashlib
4
import json
5
import lasso
6
import logging
7
import os
8
import os.path
9
import requests
10
import tempfile
11

  
12

  
13
from datetime import datetime, timedelta
14
from django.core.files.storage import default_storage
15
from django.utils.text import slugify
16
from hashlib import sha1
17
from xml.etree import ElementTree as ET
18

  
19

  
20
def truncate_unique(s, length=250):
21
    if len(s) < length:
22
        return s
23
    md5 = hashlib.md5(s.encode('ascii')).hexdigest()
24
    # we should be the first and last characters from the URL
25
    l = int((length - len(md5)) / 2 - 2)  # four additional characters
26
    assert l > 20
27
    return s[:l] + '...' + s[-l:] + '_' + md5
28

  
29

  
30
def url2filename(url):
31
    return truncate_unique(slugify(url), 230)
32

  
33

  
34
def get_entity_id_from_fingerprint(fingerprint):
35
    from .utils import reload_cache
36
    logger = logging.getLogger(__name__)
37
    fingerprint_b32 = base64.b32encode(fingerprint)
38
    main_md_dir = default_storage.path('metadata-cache')
39
    artifact_dir = os.path.join(main_md_dir, 'fingerprints')
40
    artifact_path = os.path.join(artifact_dir, fingerprint_b32.decode('utf-8'))
41

  
42
    if not os.path.exists(artifact_path) or \
43
            not os.path.exists(artifact_dir) or \
44
            not os.path.exists(main_md_dir) or \
45
            os.stat(artifact_dir).st_mtime > 3600:
46
        reload_cache()
47

  
48
    if not os.path.exists(artifact_path):
49
        logger.info('no entity ID found for artifact %s', fingerprint)
50
        return
51

  
52
    try:
53
        mapping = dict()
54
        with open(artifact_path, 'r+') as f:
55
            try:
56
                # Shared yet blocking lock, as obtaining the entity id is critical
57
                # to the SAML artifact resolution.
58
                fcntl.lockf(f, fcntl.LOCK_SH)
59
            except:
60
                logger.exception(u"failed to acquire shared blocking lock on "
61
                    "the entity_id fingerprint cache file")
62
            else:
63
                entity_id = f.read()
64
            finally:
65
                fcntl.lockf(f, fcntl.LOCK_UN)
66
    except:
67
        logger.exception(u"could read the entity_id fingerprint cache file")
68

  
69
    return entity_id
70

  
71

  
72
def store_fingerprint(entity_id):
73
    """Adds an entry in the <sha1(entity_id), entity_id> mapping.
74
    The mapping cache file may be created in not already existing.
75
    """
76
    logger = logging.getLogger(__name__)
77
    m = sha1()
78
    m.update(entity_id.encode('utf-8'))
79
    fingerprint = m.digest()
80
    fingerprint_b32 = base64.b32encode(fingerprint)
81
    unix_path = default_storage.path(os.path.join('metadata-cache/fingerprints/', fingerprint_b32.decode('utf-8')))
82
    dirname = os.path.dirname(unix_path)
83

  
84
    try:
85
        if not os.path.exists(dirname):
86
            os.makedirs(dirname)
87
        with open(unix_path, 'w') as f:
88
            try:
89
                fcntl.lockf(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
90
            except:
91
                logger.exception(u"failed to acquire the exclusive non blocking "
92
                    "lock on the entity_id fingerprint cache file")
93
                return
94
            else:
95
                with tempfile.NamedTemporaryFile(mode='w', dir=os.path.dirname(unix_path), delete=False) as temp:
96
                    try:
97
                        temp.write(entity_id)
98
                        temp.flush()
99
                        os.rename(temp.name, unix_path)
100
                        pass
101
                    except:
102
                        logger.error('Could\'nt store fingerprint for entity ID %s', entity_id)
103
                        os.unlink(temp.name)
104
                    finally:
105
                        fcntl.lockf(f, fcntl.LOCK_UN)
106
    except:
107
        logger.exception(u"could create the intermediate metadata cache "
108
                         "folder")
109

  
110

  
111
def load_federation_cache(url):
112
    logger = logging.getLogger(__name__)
113
    try:
114
        filename = url2filename(url)
115
        path = os.path.join('metadata-cache', filename)
116

  
117
        unix_path = default_storage.path(path)
118
        dirname = os.path.dirname(unix_path)
119
        if not os.path.exists(dirname):
120
            os.makedirs(dirname)
121
        f = open(unix_path, 'w')
122
        try:
123
            fcntl.lockf(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
124
        except IOError:
125
            return
126
        else:
127
            with tempfile.NamedTemporaryFile(dir=os.path.dirname(unix_path), delete=False) as temp:
128
                try:
129
                    # increase modified time by one hour to prevent too many updates
130
                    st = os.stat(unix_path)
131
                    os.utime(unix_path, (st.st_atime, st.st_mtime + 3600))
132
                    response = requests.get(url)
133
                    response.raise_for_status()
134
                    temp.write(response.content)
135
                    temp.flush()
136
                    os.rename(temp.name, unix_path)
137
                except:
138
                    logger.error('Could\'nt fetch %r', url)
139
                    os.unlink(temp.name)
140
                finally:
141
                    fcntl.lockf(f, fcntl.LOCK_UN)
142
        finally:
143
            f.close()
144
    except OSError:
145
        logger.exception(u"could create the intermediary 'metadata-cache' "
146
                         "folder")
147
        return
148
    except:
149
        logger.exception(u'failed to load federation from %s', url)
150

  
151

  
152
def get_federation_from_url(url, update_cache=False):
153
    logger = logging.getLogger(__name__)
154
    filename = url2filename(url)
155
    filepath = os.path.join('metadata-cache', filename)
156
    if not default_storage.exists(filepath) or update_cache or \
157
            default_storage.created_time(filepath) < datetime.now() - timedelta(days=1):
158
        load_federation_cache(url)
159
    else:
160
        logger.warning('federation %s has not been loaded', url)
161
    return default_storage.path(filepath)
162

  
163

  
164
def idp_metadata_filepath(entity_id):
165
    filename = url2filename(entity_id)
166
    filepath = os.path.join('./metadata-cache', filename)
167
    return filepath
168

  
169

  
170
def idp_settings_filepath(entity_id):
171
    filename = url2filename(entity_id) + "_settings.json"
172
    filepath = os.path.join('./metadata-cache', filename)
173
    return filepath
174

  
175

  
176
def idp_metadata_is_cached(entity_id):
177
    filepath = idp_metadata_filepath(entity_id)
178
    if not default_storage.exists(filepath):
179
        return False
180
    return True
181

  
182

  
183
def idp_metadata_is_file(metadata):
184
    # XXX too restrictive (e.g. 'metadata/http-somemetadataserver-com-md00.xml'
185
    # could be a file too...)
186
    # On the opposite, `if "http://" in metadata or "https://" in metadata:" is
187
    # equally restrictive.
188
    # Using a URLValidator doesn't seem adequate either.
189
    if metadata.startswith('/') or metadata.startswith('./'):
190
        return True
191

  
192

  
193
def idp_metadata_needs_refresh(entity_id, update_cache=False):
194
    filepath = idp_metadata_filepath(entity_id)
195
    if not default_storage.exists(filepath) or update_cache or \
196
            default_storage.created_time(filepath) < datetime.now() - timedelta(days=1):
197
        return True
198
    return False
199

  
200

  
201
def idp_settings_needs_refresh(entity_id, update_cache=False):
202
    filepath = idp_settings_filepath(entity_id)
203
    if not default_storage.exists(filepath) or update_cache or \
204
            default_storage.created_time(filepath) < datetime.now() - timedelta(days=1):
205
        return True
206
    return False
207

  
208

  
209
def idp_metadata_store(metadata_content):
210
    entity_id = idp_metadata_extract_entity_id(metadata_content)
211
    if not entity_id:
212
        return
213
    logger = logging.getLogger(__name__)
214
    filepath = idp_metadata_filepath(entity_id)
215

  
216
    dirname = os.path.dirname(filepath)
217
    if not default_storage.exists(dirname):
218
        os.makedirs(default_storage.path(dirname))
219

  
220
    if idp_metadata_needs_refresh(entity_id):
221
        with open(default_storage.path(filepath), 'w') as f:
222
            try:
223
                fcntl.lockf(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
224
                f.write(metadata_content)
225
                fcntl.lockf(f, fcntl.LOCK_UN)
226
            except:
227
                logger.error('Couldn\'t store metadata for EntityID %r',
228
                        entity_id)
229
                return
230
    return default_storage.path(filepath)
231

  
232

  
233
def idp_metadata_load(entity_id):
234
    logger = logging.getLogger(__name__)
235
    filepath = idp_metadata_filepath(entity_id)
236
    if default_storage.exists(filepath):
237
        logger.info('Loading metadata for EntityID %r', entity_id)
238
        with open(default_storage.path(filepath), 'r') as f:
239
            return f.read()
240
    else:
241
        logger.warning('No metadata file for EntityID %r', entity_id)
242

  
243

  
244
def idp_settings_store(idp):
245
    """
246
    Stores an IDP settings when loaded from a federation.
247
    """
248
    logger = logging.getLogger(__name__)
249
    entity_id = idp.get('ENTITY_ID')
250
    filepath = idp_settings_filepath(entity_id)
251
    idp_settings = {}
252

  
253
    if not entity_id:
254
        return
255

  
256
    dirname = os.path.dirname(filepath)
257
    if not default_storage.exists(dirname):
258
        os.makedirs(default_storage.path(dirname))
259

  
260
    for key, value in idp.items():
261
        if key not in ('METADATA', 'ENTITY_ID'):
262
            idp_settings.update({key: value})
263

  
264
    if idp_settings_needs_refresh(entity_id) and idp_settings:
265
        with open(default_storage.path(filepath), 'w') as f:
266
            try:
267
                fcntl.lockf(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
268
                f.write(json.dumps(idp_settings))
269
                fcntl.lockf(f, fcntl.LOCK_UN)
270
            except:
271
                logger.error('Couldn\'t store settings for EntityID %r',
272
                        entity_id)
273

  
274

  
275
def idp_settings_load(entity_id):
276
    logger = logging.getLogger(__name__)
277
    filepath = idp_settings_filepath(entity_id)
278
    if default_storage.exists(filepath):
279
        logger.info('Loading JSON settings for EntityID %r', entity_id)
280
        with open(default_storage.path(filepath), 'r') as f:
281
            try:
282
                idp_settings = json.loads(f.read())
283
            except:
284
                logger.warning('Couldn\'t load JSON settings for EntityID %r',
285
                        entity_id)
286
            else:
287
                return idp_settings
288
    else:
289
        logger.warning('No JSON settings file for EntityID %r', entity_id)
290

  
291
    return {}
292

  
293

  
294
def idp_metadata_extract_entity_id(metadata_content):
295
    logger = logging.getLogger(__name__)
296
    try:
297
        doc = ET.fromstring(metadata_content)
298
    except (TypeError, ET.ParseError):
299
        logger.error(u'METADATA of idp %r is invalid', metadata_content)
300
        return
301
    if doc.tag != '{%s}EntityDescriptor' % lasso.SAML2_METADATA_HREF:
302
        logger.error(u'METADATA of idp %r has no EntityDescriptor root tag',
303
                metadata_content)
304
        return
305
    if not 'entityID' in doc.attrib:
306
        logger.error(
307
                u'METADATA of idp %r has no entityID attribute on its root tag',
308
                metadata_content)
309
        return
310
    return doc.attrib['entityID']
mellon/middleware.py
23 23
        # Skip mellon views
24 24
        if request.resolver_match.url_name and request.resolver_match.url_name.startswith('mellon_'):
25 25
            return
26
        if not any(utils.get_idps()):
26
        if not app_settings.FEDERATIONS and not app_settings.IDENTITY_PROVIDERS:
27 27
            return
28 28
        if not app_settings.OPENED_SESSION_COOKIE_NAME:
29 29
            return
mellon/utils.py
3 3
import importlib
4 4
from functools import wraps
5 5
import isodate
6
import requests
7
import requests.exceptions
6 8
from xml.parsers import expat
9
import os
7 10

  
8 11
from django.contrib import auth
12
from django.core.exceptions import ValidationError
13
from django.core.files.storage import default_storage
9 14
from django.core.urlresolvers import reverse
15
from django.core.validators import URLValidator
10 16
from django.template.loader import render_to_string
11 17
from django.utils.timezone import make_aware, now, make_naive, is_aware, get_default_timezone
12 18
from django.conf import settings
......
14 20
import lasso
15 21

  
16 22
from . import app_settings
23
from .federation_utils import get_federation_from_url, idp_metadata_is_file, \
24
        idp_metadata_load, idp_metadata_extract_entity_id, \
25
        get_entity_id_from_fingerprint
17 26

  
18 27

  
19 28
def create_metadata(request):
......
47 56

  
48 57

  
49 58
def create_server(request):
59
    metadata = create_metadata(request)
60
    if app_settings.PRIVATE_KEY:
61
        private_key = app_settings.PRIVATE_KEY
62
        private_key_password = app_settings.PRIVATE_KEY_PASSWORD
63
    elif app_settings.PRIVATE_KEYS:
64
        private_key = app_settings.PRIVATE_KEYS[0]
65
        private_key_password = None
66
        if isinstance(private_key, (tuple, list)):
67
            private_key_password = private_key[1]
68
            private_key = private_key[0]
69
    else:  # no signature
70
        private_key = None
71
        private_key_password = None
72
    server = lasso.Server.newFromBuffers(metadata, private_key_content=private_key,
73
                                         private_key_password=private_key_password)
74
    server.setEncryptionPrivateKeyWithPassword(private_key, private_key_password)
75
    private_keys = app_settings.PRIVATE_KEYS
76
    # skip first key if it is already loaded
77
    if not app_settings.PRIVATE_KEY:
78
        private_keys = app_settings.PRIVATE_KEYS[1:]
79
    for key in private_keys:
80
        password = None
81
        if isinstance(key, (tuple, list)):
82
            password = key[1]
83
            key = key[0]
84
        server.setEncryptionPrivateKeyWithPassword(key, password)
85
    return server
86

  
87

  
88
def get_federation_metadata(federation):
50 89
    logger = logging.getLogger(__name__)
51
    root = request.build_absolute_uri('/')
52
    cache = getattr(settings, '_MELLON_SERVER_CACHE', {})
53
    if root not in cache:
54
        metadata = create_metadata(request)
55
        if app_settings.PRIVATE_KEY:
56
            private_key = app_settings.PRIVATE_KEY
57
            private_key_password = app_settings.PRIVATE_KEY_PASSWORD
58
        elif app_settings.PRIVATE_KEYS:
59
            private_key = app_settings.PRIVATE_KEYS[0]
60
            private_key_password = None
61
            if isinstance(private_key, (tuple, list)):
62
                private_key_password = private_key[1]
63
                private_key = private_key[0]
64
        else:  # no signature
65
            private_key = None
66
            private_key_password = None
67
        server = lasso.Server.newFromBuffers(metadata, private_key_content=private_key,
68
                                             private_key_password=private_key_password)
69
        server.setEncryptionPrivateKeyWithPassword(private_key, private_key_password)
70
        private_keys = app_settings.PRIVATE_KEYS
71
        # skip first key if it is already loaded
72
        if not app_settings.PRIVATE_KEY:
73
            private_keys = app_settings.PRIVATE_KEYS[1:]
74
        for key in private_keys:
75
            password = None
76
            if isinstance(key, (tuple, list)):
77
                password = key[1]
78
                key = key[0]
79
            server.setEncryptionPrivateKeyWithPassword(key, password)
80
        for idp in get_idps():
81
            try:
82
                server.addProviderFromBuffer(lasso.PROVIDER_ROLE_IDP, idp['METADATA'])
83
            except lasso.Error as e:
84
                logger.error(u'bad metadata in idp %r', idp['ENTITY_ID'])
85
                logger.debug(u'lasso error: %s', e)
86
                continue
87
        cache[root] = server
88
        settings._MELLON_SERVER_CACHE = cache
89
    return settings._MELLON_SERVER_CACHE.get(root)
90

  
91

  
92
def create_login(request):
93
    server = create_server(request)
90
    fedmd = None
91
    pemcert = None
92
    if (isinstance(federation, tuple) and len(federation) == 2):
93
        logger.info('Loading local cert-based federation %r',
94
                    federation)
95
        if federation[1].endswith('.pem'):
96
            fedmd = federation[0]
97
            pemcert = federation[1]
98
    else:
99
        urlval = URLValidator()
100
        try:
101
            urlval(federation)
102
        except ValidationError:
103
            logger.info('Loading file-based federation %s',
104
                        federation)
105
            fedmd = federation
106
        else:
107
            logger.info('Fetching and loading url-based federation %s',
108
                        federation)
109
            fedmd = get_federation_from_url(federation)
110
    return (fedmd, pemcert)
111

  
112

  
113
def create_login(request, server=None):
114
    if not server:
115
        server = create_server(request)
94 116
    login = lasso.Login(server)
95 117
    if not app_settings.PRIVATE_KEY and not app_settings.PRIVATE_KEYS:
96 118
        login.setSignatureHint(lasso.PROFILE_SIGNATURE_HINT_FORBID)
......
113 135
                yield idp
114 136

  
115 137

  
138
def get_federations():
139
    for adapter in get_adapters():
140
        if hasattr(adapter, 'get_federations'):
141
            for federation in adapter.get_federations():
142
                yield federation
143

  
144

  
116 145
def flatten_datetime(d):
117 146
    d = d.copy()
118 147
    for key, value in d.items():
......
180 209
    return idp.get(name) or getattr(app_settings, name, default)
181 210

  
182 211

  
183
def create_logout(request):
212
def create_logout(request, server=None):
184 213
    logger = logging.getLogger(__name__)
185
    server = create_server(request)
214
    if not server:
215
        server = create_server(request)
186 216
    mellon_session = request.session.get('mellon_session', {})
187 217
    entity_id = mellon_session.get('issuer')
188 218
    session_index = mellon_session.get('session_index')
......
259 289
    parser.XmlDeclHandler = xmlDeclHandler
260 290
    parser.Parse(content, True)
261 291
    return xml_encoding
292

  
293

  
294
def add_service_provider(server, entity_id):
295
    logger = logging.getLogger(__name__)
296

  
297
    metadata_dir = default_storage.path('metadata-cache')
298

  
299
    if not os.path.exists(metadata_dir) or \
300
            os.stat(metadata_dir).st_mtime > 3600:
301
        reload_cache()
302

  
303
    idp = get_idp(entity_id)
304
    if not idp or not idp.get('METADATA'):
305
        logger.error('no valid idp found for entity ID %r', entity_id)
306
        return
307

  
308
    metadata = idp['METADATA']
309
    try:
310
        if metadata.startswith('/') or metadata.startswith('./') or \
311
                ('FEDERATION' in idp and idp_metadata_is_file(metadata)):
312
            # Simply call the adequate built-in lasso routine
313
            server.addProvider(lasso.PROVIDER_ROLE_IDP, metadata)
314
        else:
315
            # The metadata supplied is directly the content buffer:
316
            server.addProviderFromBuffer(lasso.PROVIDER_ROLE_IDP, metadata)
317
    except lasso.ServerAddProviderFailedError as e:
318
        logger.error('Error %s: Failed to load idp %s', e, metadata)
319

  
320

  
321
def create_loaded_server(request, entity_id = None):
322
    payload = None
323

  
324
    if not entity_id:
325
        if request.method == 'GET':
326
            if 'SAMLResponse' in request.GET or 'SAMLRequest' in request.GET:
327
                payload = request.META['QUERY_STRING']
328
            elif 'SAMLart' in request.GET:
329
                entity_id = get_entity_id_from_fingerprint(request.GET['SAMLart'].encode('utf-8'))
330
        elif request.method == 'POST':
331
            if 'SAMLResponse' in request.POST:
332
                payload = request.POST['SAMLResponse']
333
            elif 'SAMLRequest' in request.POST:
334
                payload = request.POST['SAMLRequest']
335

  
336
    if not entity_id and payload:
337
        entity_id = lasso.profileGetIssuer(payload)
338

  
339
    server = create_server(request)
340
    add_service_provider(server, entity_id)
341

  
342
    return server
343

  
344

  
345
def get_metadata_from_url(idp):
346
    logger = logging.getLogger(__name__)
347

  
348
    verify_ssl_certificate = get_setting(
349
        idp, 'VERIFY_SSL_CERTIFICATE')
350

  
351
    try:
352
        response = requests.get(idp['METADATA_URL'], verify=verify_ssl_certificate)
353
        response.raise_for_status()
354
    except requests.exceptions.RequestException as e:
355
        logger.error(
356
                u'retrieval of metadata URL %r failed with error %s',
357
                idp['METADATA_URL'], e)
358
    else:
359
        return response.content.decode('utf-8')
360

  
361

  
362
def reload_cache():
363
    for idp in get_idps():
364
        pass
mellon/views.py
1
import base64
2
import binascii
1 3
import logging
2 4
import requests
3 5
import lasso
......
19 21
from django.db import transaction
20 22
from django.utils.translation import ugettext as _
21 23

  
22
from . import app_settings, utils
24
from . import app_settings, utils, federation_utils
25
from .federation_utils import idp_metadata_load, get_entity_id_from_fingerprint
23 26

  
24 27

  
25 28
lasso.setFlag('thin-sessions')
......
111 114
        if not utils.is_nonnull(request.POST['SAMLResponse']):
112 115
            return HttpResponseBadRequest('SAMLResponse contains a null character')
113 116
        self.log.info('Got SAML Response', extra={'saml_response': request.POST['SAMLResponse']})
114
        self.profile = login = utils.create_login(request)
117

  
118
        server = utils.create_loaded_server(request)
119
        self.profile = login = utils.create_login(request, server)
120

  
115 121
        idp_message = None
116 122
        status_codes = []
123

  
117 124
        # prevent null characters in SAMLResponse
118 125
        try:
119 126
            login.processAuthnResponseMsg(request.POST['SAMLResponse'])
......
233 240

  
234 241
    def continue_sso_artifact(self, request, method):
235 242
        idp_message = None
243
        entity_id = None
236 244
        status_codes = []
237 245

  
238 246
        if method == lasso.HTTP_METHOD_ARTIFACT_GET:
......
244 252
            artifact = request.POST['SAMLart']
245 253
            relay_state = request.POST.get('RelayState')
246 254

  
247
        self.profile = login = utils.create_login(request)
255
        try:
256
            decoded_artifact = base64.b64decode(artifact)
257
        except:
258
            self.log.warning(u'artifact %r is not base64-encoded', artifact)
259
            # let error be caught later, during request initialization
260
        else:
261
            fingerprint = decoded_artifact[4:24]
262
            entity_id = get_entity_id_from_fingerprint(fingerprint)
263

  
264
        if entity_id:
265
            server = utils.create_server(request)
266
            idp = utils.get_idp(entity_id)
267
            utils.add_service_provider(server, idp)
268
        else:
269
            self.log.warning('no entity id found for artifact %s', artifact)
270
            self.log.warning('loading full set of providers')
271
            server = utils.create_loaded_server(request)
272

  
273
        self.profile = login = utils.create_login(request, server)
274

  
248 275
        if relay_state and utils.is_nonnull(relay_state):
249 276
            login.msgRelayState = relay_state
277

  
250 278
        try:
251 279
            login.initRequest(message, method)
252 280
        except lasso.ProfileInvalidArtifactError:
......
349 377
        idp = self.get_idp(request)
350 378
        if idp is None:
351 379
            return HttpResponseBadRequest('no idp found')
352
        self.profile = login = utils.create_login(request)
353
        self.log.debug('authenticating to %r', idp['ENTITY_ID'])
380
        entity_id = idp.get('ENTITY_ID') or federation_utils.idp_metadata_extract_entity_id(idp.get('METADATA'))
381
        self.log.debug('authenticating to %r', entity_id)
382

  
383
        server = utils.create_loaded_server(request, entity_id)
384
        self.profile = login = utils.create_login(request, server)
385

  
354 386
        try:
355
            login.initAuthnRequest(idp['ENTITY_ID'], lasso.HTTP_METHOD_REDIRECT)
387
            login.initAuthnRequest(entity_id, lasso.HTTP_METHOD_REDIRECT)
356 388
            authn_request = login.request
357 389
            # configure NameID policy
358 390
            policy = authn_request.nameIdPolicy
......
409 441

  
410 442
    def idp_logout(self, request):
411 443
        '''Handle logout request emitted by the IdP'''
412
        self.profile = logout = utils.create_logout(request)
444
        server = utils.create_loaded_server(request)
445
        self.profile = logout = utils.create_logout(request, server)
413 446
        try:
414 447
            logout.processRequestMsg(request.META['QUERY_STRING'])
415 448
        except lasso.Error as e:
......
438 471
                try:
439 472
                    issuer = request.session.get('mellon_session', {}).get('issuer')
440 473
                    if issuer:
441
                        self.profile = logout = utils.create_logout(request)
474
                        server = utils.create_loaded_server(request)
475
                        self.profile = logout = utils.create_logout(request, server)
442 476
                        try:
443 477
                            if 'lasso_session_dump' in request.session:
444 478
                                logout.setSessionFromDump(request.session['lasso_session_dump'])
......
464 498

  
465 499
    def sp_logout_response(self, request):
466 500
        '''Launch a logout request to the identity provider'''
467
        self.profile = logout = utils.create_logout(request)
501
        server = utils.create_loaded_server(request)
502
        self.profile = logout = utils.create_logout(request, server)
468 503
        # the user shouldn't be logged anymore at this point but it may happen
469 504
        # that a concurrent SSO happened in the meantime, so we do another
470 505
        # logout to make sure.
setup.py
94 94
          'django>=1.5,<2.0',
95 95
          'requests',
96 96
          'isodate',
97
          'pytz',
97 98
      ],
98 99
      setup_requires=[
99 100
          'django>=1.5,<2.0',
tests/conftest.py
42 42
    caplog.handler.stream = py.io.TextIO()
43 43
    caplog.handler.records = []
44 44
    return caplog
45

  
46

  
47
# XXX temporary workaround
48
#     non-federated IdPs shouldn't have their MD cached
49
@pytest.fixture(autouse=True)
50
def mellon_settings(settings, tmpdir):
51
        settings.MEDIA_ROOT = str(tmpdir)
tests/dummy_md.xml
1
<?xml version="1.0" encoding="UTF-8" standalone="no"?><md:EntitiesDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute" xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi" xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui" xmlns:pyff="http://pyff.io/NS" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:shibmd="urn:mace:shibboleth:metadata:1.0" xmlns:xrd="http://docs.oasis-open.org/ns/xri/xrd-1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ID="_20171018T113001Z" Name="https://federation.renater.fr/" cacheDuration="PT1H" validUntil="2017-10-27T11:30:01Z"><ds:Signature>
2
<ds:SignedInfo>
3
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
4
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5
<ds:Reference URI="">
6
<ds:Transforms>
7
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
8
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
9
</ds:Transforms>
10
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
11
<ds:DigestValue>JKdLdd5yGvkFdb1fCAByMMnurIKYhZepRouZfOjIUrg=</ds:DigestValue>
12
</ds:Reference>
13
</ds:SignedInfo>
14
<ds:SignatureValue>
15
OTexfi8c63TsP1V9j5m6digA2NomUfqBtT8pPKhwdqEDQS5qLh6fxvT+wWkP6JaIhkP8nxwpbArl
16
7cUHkRv5ibZzcknIAjXYMhsSTtFQUq89OMcDHtZHG54jiKyHPhu2+XEbvv6DsAYanYC6SHEnGjNG
17
opnOEUB2XqeycsvvTQQIuWZEoABTVcKYyk2CW7Ij5EUmPOAPiidtbt8lzrtkV6dwLbkyoEbChAyj
18
emrL/oS01aJgT9sQoJxR8lyRMGiZ/BwQqYTareiKwOXLPdGThzsfZXD8de9T1xuysILaAM7sHPJV
19
QfrQJm80Zo2MM/GnhJTO9rc4m3kRnRhqmA6qMw==
20
</ds:SignatureValue>
21
<ds:KeyInfo>
22
<ds:KeyValue>
23
<ds:RSAKeyValue>
24
<ds:Modulus>
25
71+vTf66BPgYUF7sm4T++W69qMVyGQn9wNqpBLc6sp53eq/JRTOUD26Yehjsld5qN52Bv2r5QG7o
26
4VU123akXUYzupvq1f+tmF9NwYa7MPEPFzCzJHhNXjZNRxcsW1WLW34fhQCm0oak3oSPoNo5qeGi
27
jNsTSkgSt1mPH0P8d95af2VJnT6zbrclxvH4emqpT9oGLsWqKWLlIbZ7u1PUjuNVwLHuj909/apm
28
C13RBIpV52fey4qey34bnRHdCTknZeN/TJLTJ9hMWzz9TbdjfIFaiF7MeY+OYRXzUJeQuHHMu/2I
29
emkoR26mYi6irvmx8AdPcPCwcRKw2Ca4xLhbNw==
30
</ds:Modulus>
31
<ds:Exponent>AQAB</ds:Exponent>
32
</ds:RSAKeyValue>
33
</ds:KeyValue>
34
<ds:X509Data>
35
<ds:X509Certificate>
36
MIIC9zCCAd+gAwIBAgIEfe6j3jANBgkqhkiG9w0BAQsFADAsMSowKAYDVQQDEyFTQU1MIE1ldGFk
37
YXRhIFNpZ25pbmcgQ2VydGlmaWNhdGUwHhcNMTYwNzI5MDczNjM4WhcNMjYwNjA3MDczNjM4WjAs
38
MSowKAYDVQQDEyFTQU1MIE1ldGFkYXRhIFNpZ25pbmcgQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3
39
DQEBAQUAA4IBDwAwggEKAoIBAQDvX69N/roE+BhQXuybhP75br2oxXIZCf3A2qkEtzqynnd6r8lF
40
M5QPbph6GOyV3mo3nYG/avlAbujhVTXbdqRdRjO6m+rV/62YX03Bhrsw8Q8XMLMkeE1eNk1HFyxb
41
VYtbfh+FAKbShqTehI+g2jmp4aKM2xNKSBK3WY8fQ/x33lp/ZUmdPrNutyXG8fh6aqlP2gYuxaop
42
YuUhtnu7U9SO41XAse6P3T39qmYLXdEEilXnZ97Lip7LfhudEd0JOSdl439MktMn2ExbPP1Nt2N8
43
gVqIXsx5j45hFfNQl5C4ccy7/Yh6aShHbqZiLqKu+bHwB09w8LBxErDYJrjEuFs3AgMBAAGjITAf
44
MB0GA1UdDgQWBBTT88iZzWO+hN9SBUkpx871lmTuLTANBgkqhkiG9w0BAQsFAAOCAQEABoPpODry
45
XwiM5jjtqk6veR02FevCKHpZP6Od7Kqcfs6lg5LcQmGUOgpmW3Gg4UMjBYkgARsT2Nsnah1CJqa8
46
cjvv8p5KEIhY0hVS8iMJnrb3PDeiFSeP4xSfct/6z/ebV4+QFl22bsm2zpAC6BpFz8+IJ/jAmQzT
47
Vob4MAUeQPnwwzm3xz6yanLZx7BK5cfrTCa+hrarNQCboRjXPwiejF8WRCxpgRHH6yNs5QH/Z6o5
48
e3tUP7uEpn2Ob+kcLsEMGb9DghkoDAgkHCOZeTy+7hgxt+/T94cLTa58gVtvEOnd0GuL7Vfd+IVd
49
XgSard8RfR3OyZlf6M4aSGQA73sskQ==
50
</ds:X509Certificate>
51
</ds:X509Data>
52
</ds:KeyInfo>
53
</ds:Signature><md:EntityDescriptor entityID="https://aishib.agropolis.fr/idp/shibboleth">
54
			<md:Extensions>
55
				<mdrpi:RegistrationInfo registrationAuthority="https://federation.renater.fr/" registrationInstant="2013-06-06T11:49:20Z">
56
					<mdrpi:RegistrationPolicy xml:lang="en">https://services.renater.fr/federation/en/metadata_registration_practice_statement</mdrpi:RegistrationPolicy>
57
				</mdrpi:RegistrationInfo>
58
			</md:Extensions>
59
		<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:2.0:protocol">
60
			<md:Extensions>
61
    				<shibmd:Scope regexp="false">agropolis.fr</shibmd:Scope>
62

  
63
			    <mdui:UIInfo>
64

  
65
			      <mdui:DisplayName xml:lang="en">Agropolis International</mdui:DisplayName>
66

  
67
			      <mdui:Logo height="16" width="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAAAQAAAAEABcxq3DAAACPUlEQVQoz21SPW8TQRR8u7d3ju34Er6UCHACRqK4Bjk0oDSEjiYSBQVS8k9AkWgp+AuUASGEaCASkoPcgARVMF8iTnxJwMFJfL47397u3u6jsGVFwLzmFTN6ozdDEBEAAOD4kiRJsVgkhMA/YMfZzc3NVstnzGq1/KXlJfgf2Ii9XquVZ2YWbi5Io4MsbfPeZK4wRm0AOH6KICIivq2tV6/O5cYLHztbW9FvYTKuZaLk7PjpW+UrJXtspCHGmGazCQBnZ8svtj/0tbAINYhcy1DyThpRhHtztyedwkBDAWBnZ6dSqbzZ+7THu0IraTJplDSZ0JnQ2Y+482jj1cgSRUTHdrhW9fa3SKaxErFKIyUiKaIsjVQaKf5s631HxP1+X2vNwjB0J9zd+OgnD044Ra4lI5ZBw7XqyeRQREciaqe9xoE/K/OFfIENnqBM1uZBqlWBOYxQjYZnqieTThru8zCU3AASIAjIXNellGLePkjCUPICy1mEDAShSg7SqCsTBtQ7WfY3vlarVUYpdUsuAs5PXV7dfpe3HEYtjSbNZJwJoRUi3rl4fSrvfufctm0ySA0RD0V84+WDL1EbCCDCYIDApeKZ+uKK6faDIPA8jw7jIORUbry2eH+5Mu8ARTSAkCPW3QvX6osrE+B8bjQ8zxu2aACDaIwxxuzzsLbbePj88a8k0Mb4vr/2ei3LMoOIiGRU0iGGVuDp6pNz5fNSiKnpac/zgAABMuzSXwIggIjdbrdUKjHGYGQaAAD+AJBJecXxnEvWAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTA3LTIwVDEwOjQ4OjE1KzAyOjAwpWiGMQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNS0wNy0yMFQxMDo0ODoxNSswMjowMNQ1Po0AAAAASUVORK5CYII=</mdui:Logo>
68
			      <mdui:InformationURL xml:lang="fr">http://www.agropolis.fr</mdui:InformationURL>
69

  
70
          		      <mdui:DisplayName xml:lang="fr">Agropolis International</mdui:DisplayName>
71

  
72
      			    </mdui:UIInfo>
73
			</md:Extensions>
74
				<md:KeyDescriptor use="signing">
75
<ds:KeyInfo>
76

  
77
					  <ds:X509Data>
78
					    <ds:X509Certificate>
79
					      MIIDNzCCAh+gAwIBAgIUYY3sGXwChkj2CRy6QFDvkdj2zlAwDQYJKoZIhvcNAQEF
80
BQAwHjEcMBoGA1UEAxMTYWlzaGliLmFncm9wb2xpcy5mcjAeFw0xMzA1MTUxMzM3
81
MTJaFw0zMzA1MTUxMzM3MTJaMB4xHDAaBgNVBAMTE2Fpc2hpYi5hZ3JvcG9saXMu
82
ZnIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxrDy6lrhIBjcxv16n
83
4UJ2cEMYPO4wSmfDwhO6feoSIEuIblYRHE2nQKirMokwD6seF4rbDHyxLXg/ColL
84
VLv+0CJteIOZjSCgSN90WzQRrC1Ex5sJfPu6yPEXvW8H1906gEg6ok8rlCIHRGfE
85
15pHK5eqxQS5f2n8c2t/Uk33/FBj79/hb3Cd7vE4mdlvReD3AFswC0lV4bPmj3Ka
86
KUuMj9xwipwnfWCu6p2/ZJF4M3ADU5grXHJ2Vqmd8DWm5raaObKjYwJddbRBByI8
87
bJJLIwAQQmX4Dh4hf1QKlf2oqWPWVQxLQp0erL1U8IWmj1RG8TTH9xOJl6kkEhYq
88
Z2gfAgMBAAGjbTBrMEoGA1UdEQRDMEGCE2Fpc2hpYi5hZ3JvcG9saXMuZnKGKmh0
89
dHBzOi8vYWlzaGliLmFncm9wb2xpcy5mci9pZHAvc2hpYmJvbGV0aDAdBgNVHQ4E
90
FgQU9A7iQ8Qo+t2JCpKuOOV9YBoYs4MwDQYJKoZIhvcNAQEFBQADggEBAG0LOW6I
91
F+M8n2NpzyQjfVCJCA6QhWjbXrfemiPJFZGZZb2dVmHof4yCpCUYgHOBoZaXPOlB
92
nLYsUWvFZ6V2GELZpLHzHSSrYidieW07qQkh1DwcIYpvtZgLviOtT/tCEGsk925f
93
DUoGdeIqpqt54WZcW9+TbKicvjg3JT4BFOQ17bFNwPW+YjTbvsWYxen+e0mRp4vM
94
V0yMu2f3bccVhePASSZGL3yod3sJ1dPvlrJO9c35BekhtirolVjZqMQ0AYPVifua
95
yIU0dWXsZkAOcBL9kZFbJcYRUIxMgvp8U2Zdv1+ZlwOyXnnWDOOh9wjuT7FAyObU
96
ChvjHlgZHkvLwJI=
97
					    </ds:X509Certificate>
98
					  </ds:X509Data>
99

  
100
					</ds:KeyInfo>
101
				</md:KeyDescriptor>
102

  
103

  
104

  
105

  
106

  
107
			<md:NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</md:NameIDFormat>
108
			<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
109

  
110

  
111
			<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://aishib.agropolis.fr/idp/profile/SAML2/POST/SSO"/>
112

  
113
	        <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://aishib.agropolis.fr/idp/profile/SAML2/Redirect/SSO"/>
114

  
115

  
116
			<md:SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest" Location="https://aishib.agropolis.fr/idp/profile/Shibboleth/SSO"/>
117

  
118

  
119
		</md:IDPSSODescriptor>
120

  
121

  
122

  
123
		<md:Organization>
124

  
125
			<md:OrganizationName xml:lang="en">Agropolis International</md:OrganizationName>
126
			<md:OrganizationDisplayName xml:lang="en">Agropolis International</md:OrganizationDisplayName>
127
			<md:OrganizationURL xml:lang="en">http://www.agropolis.fr</md:OrganizationURL>
128

  
129
		</md:Organization>
130

  
131

  
132

  
133
			    <md:ContactPerson contactType="technical">
134
				 <md:SurName>Jean Cerda</md:SurName>
135
				 <md:EmailAddress>cerda@agropolis.fr</md:EmailAddress>
136
		        </md:ContactPerson>
137

  
138

  
139

  
140
			    <md:ContactPerson contactType="technical">
141
				 <md:SurName>Jean-Pierre  Allano</md:SurName>
142
				 <md:EmailAddress>allano@agropolis.fr</md:EmailAddress>
143
		        </md:ContactPerson>
144

  
145

  
146

  
147

  
148
	</md:EntityDescriptor><md:EntityDescriptor entityID="https://ambre.vetagro-sup.fr/idp/shibboleth">
149
			<md:Extensions>
150
				<mdrpi:RegistrationInfo registrationAuthority="https://federation.renater.fr/" registrationInstant="2013-01-14T16:11:53Z">
151
					<mdrpi:RegistrationPolicy xml:lang="en">https://services.renater.fr/federation/en/metadata_registration_practice_statement</mdrpi:RegistrationPolicy>
152
				</mdrpi:RegistrationInfo>
153
			</md:Extensions>
154
		<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:2.0:protocol">
155
			<md:Extensions>
156
    				<shibmd:Scope regexp="false">vetagro-sup.fr</shibmd:Scope>
157

  
158
			    <mdui:UIInfo>
159

  
160
			      <mdui:DisplayName xml:lang="en">Vetagro Sup</mdui:DisplayName>
161

  
162
			      <mdui:Logo height="16" width="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAAAQAAAAEABcxq3DAAAB/klEQVQ4y2NkQAP///+X+/jxI/PO7dujP3/7kf7v778ffDzciw2MDFaqq6t/Y2RkfIysngndAEZGxkezZ8/25xEQqVDTs5eRVrNU4RQUL7x9+7YtumasBjAwMDBISIgbcvBKct998p7h1fvfDKyc4nz//jF6yjAwSBNlwM+fP3/9/PmT4dbl4wysrCwMf//9Z/r7959UcFISD1EGPH107xgfD9sbTg52hrcvHjDw87Aw/Pz+7tLEefNuEmWAm6ffxvu3Ll4zMjZleHz/KsO7lw+f3bh+czs2tVgNsLS0fPfs4b3VnKy//wrycW84e2L/uobmtg0MpIKurt6N////Z8jPzxcnWTMDAwPD79//tbq7+9bhU8OMTfD//8cMP79L1X/8tjxMQYHXT0Uh88aBgyuvEW3Aly+8ntw8jLU3bx20cnH/xPz2jTyvk9OGpTt3NhIOxP//GRgkJGR8tPVeyElIvWA4dfIqg57Bff0nD+eHExULIb5dqkYmYgYm5r8YPH3vMzx//omBk+edpI4+vxVRBnz5zPqbi4vt7a9fv//9+cvAYG79jIGZmZWBg4NJiCgDdh4sfHD/7u9j714Z/BYWiGBQVkxjePNK8+erV5+uYDOABTMM/nP09y9Z8+OHgNjli9ahf//++8vGwrlAS0tp/v///7kYGRm/IasHAIVTy8DG/VpJAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTA3LTIwVDEwOjUwOjA5KzAyOjAwfDtz7wAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNS0wNy0yMFQxMDo1MDowOSswMjowMA1my1MAAAAASUVORK5CYII=</mdui:Logo>
163
			      <mdui:InformationURL xml:lang="fr">http://www.vetagro-sup.fr</mdui:InformationURL>
164

  
165
          		      <mdui:DisplayName xml:lang="fr">Vetagro Sup</mdui:DisplayName>
166

  
167
      			    </mdui:UIInfo>
168
			</md:Extensions>
169
				<md:KeyDescriptor use="signing">
170
<ds:KeyInfo>
171

  
172
					  <ds:X509Data>
173
					    <ds:X509Certificate>
174
					      MIIDPDCCAiSgAwIBAgIVAL9PsuadPSIZcMHNxlK/oevezmzWMA0GCSqGSIb3DQEB
175
BQUAMB8xHTAbBgNVBAMTFGFtYnJlLnZldGFncm8tc3VwLmZyMB4XDTEyMTEwODEw
176
MTQwNFoXDTMyMTEwODEwMTQwNFowHzEdMBsGA1UEAxMUYW1icmUudmV0YWdyby1z
177
dXAuZnIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc/ptfpmkomwmT
178
4RsID+1Ce1dX0eUjcLgSOZN8hVpHWLag2ERWkpmvB5aK7BAFcI5i//Gk80tAiasu
179
JtlZhBnEw54aTJRGpyL2CVkHyl6SMRxprIi1Ji67IoGqEgUeGaheAxo+tG5e1WSc
180
bIbldcSKdwvjAV+7HSB4C6NqLsAzJH25++yaRH2uf2LTD0TDzNR9Q2hVj/VyYWR+
181
K3HWI1Snjn/i7aFfZZhYmBkwHuQOaPhwCM+khikg5XicMsxUhHCMi93UgHGIsdkr
182
IEGj4xydBTUKsLaykeuFS8EgXbWwCLGkeX76w8xDoFIpnppU/yFd9v7Zg3EBfn4p
183
kTW3GdIjAgMBAAGjbzBtMEwGA1UdEQRFMEOCFGFtYnJlLnZldGFncm8tc3VwLmZy
184
hitodHRwczovL2FtYnJlLnZldGFncm8tc3VwLmZyL2lkcC9zaGliYm9sZXRoMB0G
185
A1UdDgQWBBTPTqWkVHrHXFjmxMWkNt/sp2h5ozANBgkqhkiG9w0BAQUFAAOCAQEA
186
FvXMtfBUmRZCzz8CjanGzr1TBUPmnkrKci5AtkseKw9YlfUmBXTHB01y697nYq6m
187
RB6KhvfW212h9CF0IOEEjoadgDhXqGYhq8PnAOtT4Ty3XDy8SbRh8aQWfvnfSngv
188
FdpHRiSpj5UXXuT5zTtkf59h58XKtEfCkMbUzvdOgUobJzpD0WISmQHPQnx+Neg6
189
9j7oMRrDiZjS39Om8Imu9xvsnddDM3PlsDBIsvrr1o7K5iLkEdR1YYX0ZNDbiFuw
190
QXXl2dwQPB8KrScPUvCe57slU2gFQvvIBzjQysxC6V6TPSuM3A/ee56lACuB3jKj
191
oYkHQc5Gj/1rSMLmu9aLMg==
192
					    </ds:X509Certificate>
193
					  </ds:X509Data>
194

  
195
					</ds:KeyInfo>
196
				</md:KeyDescriptor>
197

  
198

  
199

  
200

  
201

  
202
			<md:NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</md:NameIDFormat>
203
			<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
204

  
205

  
206
			<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ambre.vetagro-sup.fr/idp/profile/SAML2/POST/SSO"/>
207

  
208
	        <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://ambre.vetagro-sup.fr/idp/profile/SAML2/Redirect/SSO"/>
209

  
210

  
211
			<md:SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest" Location="https://ambre.vetagro-sup.fr/idp/profile/Shibboleth/SSO"/>
212

  
213

  
214
		</md:IDPSSODescriptor>
215

  
216

  
217

  
218
		<md:Organization>
219

  
220
			<md:OrganizationName xml:lang="en">Vetagro Sup</md:OrganizationName>
221
			<md:OrganizationDisplayName xml:lang="en">Vetagro Sup</md:OrganizationDisplayName>
222
			<md:OrganizationURL xml:lang="en">http://www.vetagro-sup.fr</md:OrganizationURL>
223

  
224
		</md:Organization>
225

  
226

  
227

  
228
			    <md:ContactPerson contactType="technical">
229
				 <md:SurName>Nicolas Aulas</md:SurName>
230
				 <md:EmailAddress>nicolas.aulas@vetagro-sup.fr</md:EmailAddress>
231
		        </md:ContactPerson>
232

  
233

  
234

  
235

  
236

  
237

  
238
	</md:EntityDescriptor><md:EntityDescriptor entityID="https://antimoine.insa-strasbourg.fr/idp/shibboleth">
239
			<md:Extensions>
240
				<mdrpi:RegistrationInfo registrationAuthority="https://federation.renater.fr/" registrationInstant="2014-02-11T08:44:08Z">
241
					<mdrpi:RegistrationPolicy xml:lang="en">https://services.renater.fr/federation/en/metadata_registration_practice_statement</mdrpi:RegistrationPolicy>
242
				</mdrpi:RegistrationInfo>
243
			</md:Extensions>
244
		<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:2.0:protocol">
245
			<md:Extensions>
246
    				<shibmd:Scope regexp="false">insa-strasbourg.fr</shibmd:Scope>
247

  
248
			    <mdui:UIInfo>
249

  
250
			      <mdui:DisplayName xml:lang="en">INSA Strasbourg</mdui:DisplayName>
251

  
252
			      <mdui:Logo height="16" width="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAq1BMVEXeAACEhoTWz87nQTn3oqXnIBjvgnvWlpT/5+fnEAjvZWOltrXnNDH3mpT/9/fnCAD/4+fvioz3x8bnVVL3qq3nKCnvkpTnHBjvcXP3sq3vgoT/7+/nFBDnODH3mpz////nDAj3z87vlpTveXv3vr3nAACMmpznRUL3pqXnJCHnFAj/+//nDADvjoz3y87vXVr3rq3nLCnvdXP3srXvhoT/8/fvODH3npz3lpQaie6kAAAACXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAAAQAAAAEABcxq3DAAAAe0lEQVQY053OwQqCQBhF4VM22VSMMWrFGP2kkIpUQ1nz/m8Ws20TeFeHb3WRnzEBBjO8xRjZrURi0TFiy+UITjTpjIKaOndZuDSGxF0j9G1+e7CuLN1nE2F7VI28tAoqMMezP1ieFOOp7RPueBXKSqdnMq8Wg66nPP0LX4uIG4jEwJ0sAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTA3LTIwVDEwOjQ5OjA0KzAyOjAwIHfmJQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNS0wNy0yMFQxMDo0OTowNCswMjowMFEqXpkAAAAASUVORK5CYII=</mdui:Logo>
253
			      <mdui:InformationURL xml:lang="fr">http://www.insa-strasbourg.fr</mdui:InformationURL>
254

  
255
          		      <mdui:DisplayName xml:lang="fr">INSA Strasbourg</mdui:DisplayName>
256

  
257
      			    </mdui:UIInfo>
258
			</md:Extensions>
259
				<md:KeyDescriptor use="signing">
260
<ds:KeyInfo>
261

  
262
					  <ds:X509Data>
263
					    <ds:X509Certificate>
264
					      MIIDUDCCAjigAwIBAgIVAIbX8U0uAqAhuXm1jWxiFpggtDTDMA0GCSqGSIb3DQEB
265
CwUAMCQxIjAgBgNVBAMMGXNvdWZyZS5pbnNhLXN0cmFzYm91cmcuZnIwHhcNMTYw
266
OTI3MTIzNjIxWhcNMzYwOTI3MTIzNjIxWjAkMSIwIAYDVQQDDBlzb3VmcmUuaW5z
267
YS1zdHJhc2JvdXJnLmZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
268
sEE02sLRPAG5N81DMHEeGpI2MYF8yG/RiwH07cFIlLqgV80ewOmi0FWPYijxMb8A
269
bmx0RwUMvJBVI6WMxtT9fykhID20k8rWOuYOzvaynzVqCktqVgKoEAxP1PFE9b0n
270
iGKFprjjNl9ZD90GOUsxbAO7yXG9Q4WBa/eThl6XkUvNkSaZp5hcdWrgcAdsae3q
271
iD/uxFa38NXNNeRLGyfxjd2K5qYSzbwBza9s9TOq1+pfw7sxu3/4BnfQ0RLGO6co
272
4tH4Mufh0ome4cyYk4pvW5DOd1AznxDb8HpqvE0zwEsa69c/FDX0akgFZydmc77a
273
j6USn6JKjjbO49yGtG1gVQIDAQABo3kwdzAdBgNVHQ4EFgQUjzMsxZYiokPYxper
274
9zadM8J0F0kwVgYDVR0RBE8wTYIZc291ZnJlLmluc2Etc3RyYXNib3VyZy5mcoYw
275
aHR0cHM6Ly9zb3VmcmUuaW5zYS1zdHJhc2JvdXJnLmZyL2lkcC9zaGliYm9sZXRo
276
MA0GCSqGSIb3DQEBCwUAA4IBAQBFJKsiS3yfWuDB/E+iqQ0TuQJzL5+JIcloN0dw
277
BFxW3VZOju15zeQ7LwRBg9S4SGLMPJU+LM1lvr68cK9brut/FjF51SETIXEeCWo3
278
7+PIqgOCzraLNinmpU/OtN8ENalOPvpS6Jvbd23qB2t+IqOtZ+j15b0Yq4/on1E3
279
W2F9CVzKpe4EwmmtCPQbe7U1wvhgFylEx797pex8veWs79YSYwqvcKMh79dzl8Fo
280
/CgsO5pDrfKmc6SGMkByq75dZj+PqhZDzZ9EFTxbrXOTaS08VRN6a5Rh2iYRnGxq
281
yZl66tPcaIm5PHgOEmu5X4lPkUoY+Jt36Gj3SGCbYt8qH5S0
282
					    </ds:X509Certificate>
283
					  </ds:X509Data>
284

  
285
					</ds:KeyInfo>
286
				</md:KeyDescriptor>
287

  
288

  
289

  
290
				<md:KeyDescriptor use="signing">
291
				       <ds:KeyInfo>
292
					  <ds:X509Data>
293
					    <ds:X509Certificate>
294
					      MIIDXDCCAkSgAwIBAgIVAKI+qiqDCk9wTTqn7OVAoZrvj/CpMA0GCSqGSIb3DQEB
295
BQUAMCcxJTAjBgNVBAMTHGFudGltb2luZS5pbnNhLXN0cmFzYm91cmcuZnIwHhcN
296
MTQwMTEzMTAzOTU4WhcNMzQwMTEzMTAzOTU4WjAnMSUwIwYDVQQDExxhbnRpbW9p
297
bmUuaW5zYS1zdHJhc2JvdXJnLmZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
298
CgKCAQEAtuM8lRjlVjjmrHq9VtguaOMQL+Wd99BiOs56kL3Mbctg1FwH69LYThCW
299
6dOz6WJg/jU/naF7jEikXKc71xGyu7Ph7Iqa9S5hoXXAT8u/0q2nZDeTOraJqKe1
300
FMF2RzXhEEMyQO3CiKNK9b+tbKoNZS7FQCixMZklWZPt4EcEKd6jyRq1WYX3dpnb
301
r9I/aCdhtK/PGvGe5gKTDoTR2HKyWKJTc/obf8x/vlYIEwiaGgdlqI2KiBE0x48n
302
zQdP6XVi3T8ZWbnkLmCfgJtP2C8PtEJuwDRAy0Z9N4DSwvxn5YCVYgBLSi0TLa10
303
B/lUqqBezZrTrA9p9Lt8JtGXW5YGHwIDAQABo38wfTBcBgNVHREEVTBTghxhbnRp
304
bW9pbmUuaW5zYS1zdHJhc2JvdXJnLmZyhjNodHRwczovL2FudGltb2luZS5pbnNh
305
LXN0cmFzYm91cmcuZnIvaWRwL3NoaWJib2xldGgwHQYDVR0OBBYEFLFkjPZUc9JY
306
qrWjldJ/iGGkKAt4MA0GCSqGSIb3DQEBBQUAA4IBAQBSk/wU1mRn4VF2ifmy261K
307
DK7uX+t1H1hh8S38fKSFU7HoNXJTV3vQnmBOpYIGC1gtvmb+qjqpNtikU2zO84Gq
308
Q0bXHxYF2d9RUP89mKaFxE5uNcXFmlOA3ChZY3pMT5zwAPI/T60tGrex7zci7OLn
309
JDAQj/q4Yk9ejx6JTFggQSCCVh+oV/SDIMd2p5AY6H3mto3b6XCk7Lssa8a/D30k
310
pEkZnhTKdN82eRyynuOR7UDU4tasV4d7Mi/j53f5ihnRcsvwh/pYodjoVYY8cEcZ
311
JLnAXYF8coSwh8UN4D/0NHsvTuSOFQc85hGrqacMsvxiQiw9mv01AX5+A5YLEbVQ
312
					    </ds:X509Certificate>
313
					  </ds:X509Data>
314
					</ds:KeyInfo>
315
				</md:KeyDescriptor>
316

  
317

  
318

  
319
			   <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://antimoine.insa-strasbourg.fr/idp/profile/SAML2/Redirect/SLO"/>
320

  
321
			   <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://antimoine.insa-strasbourg.fr/idp/profile/SAML2/POST/SLO"/>
322

  
323
			   <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://antimoine.insa-strasbourg.fr/idp/profile/SAML2/SOAP/SLO"/>
324

  
325

  
326
			<md:NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</md:NameIDFormat>
327
			<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
328

  
329

  
330
			<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://antimoine.insa-strasbourg.fr/idp/profile/SAML2/POST/SSO"/>
331

  
332
	        <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://antimoine.insa-strasbourg.fr/idp/profile/SAML2/Redirect/SSO"/>
333

  
334

  
335
			<md:SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest" Location="https://antimoine.insa-strasbourg.fr/idp/profile/Shibboleth/SSO"/>
336

  
337

  
338
		</md:IDPSSODescriptor>
339

  
340

  
341

  
342
		<md:Organization>
343

  
344
			<md:OrganizationName xml:lang="en">INSA Strasbourg</md:OrganizationName>
345
			<md:OrganizationDisplayName xml:lang="en">INSA Strasbourg</md:OrganizationDisplayName>
346
			<md:OrganizationURL xml:lang="en">http://www.insa-strasbourg.fr</md:OrganizationURL>
347

  
348
		</md:Organization>
349

  
350

  
351

  
352
			    <md:ContactPerson contactType="technical">
353
				 <md:SurName>Lahsen BOUZID</md:SurName>
354
				 <md:EmailAddress>lahsen.bouzid@insa-strasbourg.fr</md:EmailAddress>
355
		        </md:ContactPerson>
356

  
357

  
358

  
359
			    <md:ContactPerson contactType="technical">
360
				 <md:SurName>Simon SCHERRER</md:SurName>
361
				 <md:EmailAddress>simon.scherrer@insa-strasbourg.fr</md:EmailAddress>
362
		        </md:ContactPerson>
363

  
364

  
365

  
366

  
367
	</md:EntityDescriptor></md:EntitiesDescriptor>
tests/federation-sample.xml
1
<?xml version="1.0" encoding="UTF-8" standalone="no"?><md:EntitiesDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute" xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi" xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui" xmlns:pyff="http://pyff.io/NS" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:shibmd="urn:mace:shibboleth:metadata:1.0" xmlns:xrd="http://docs.oasis-open.org/ns/xri/xrd-1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ID="_20171018T113001Z" Name="https://federation.renater.fr/" cacheDuration="PT1H" validUntil="2017-10-27T11:30:01Z"><ds:Signature>
2
<ds:SignedInfo>
3
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
4
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
5
<ds:Reference URI="">
6
<ds:Transforms>
7
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
8
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
9
</ds:Transforms>
10
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
11
<ds:DigestValue>JKdLdd5yGvkFdb1fCAByMMnurIKYhZepRouZfOjIUrg=</ds:DigestValue>
12
</ds:Reference>
13
</ds:SignedInfo>
14
<ds:SignatureValue>
15
OTexfi8c63TsP1V9j5m6digA2NomUfqBtT8pPKhwdqEDQS5qLh6fxvT+wWkP6JaIhkP8nxwpbArl
16
7cUHkRv5ibZzcknIAjXYMhsSTtFQUq89OMcDHtZHG54jiKyHPhu2+XEbvv6DsAYanYC6SHEnGjNG
17
opnOEUB2XqeycsvvTQQIuWZEoABTVcKYyk2CW7Ij5EUmPOAPiidtbt8lzrtkV6dwLbkyoEbChAyj
18
emrL/oS01aJgT9sQoJxR8lyRMGiZ/BwQqYTareiKwOXLPdGThzsfZXD8de9T1xuysILaAM7sHPJV
19
QfrQJm80Zo2MM/GnhJTO9rc4m3kRnRhqmA6qMw==
20
</ds:SignatureValue>
21
<ds:KeyInfo>
22
<ds:KeyValue>
23
<ds:RSAKeyValue>
24
<ds:Modulus>
25
71+vTf66BPgYUF7sm4T++W69qMVyGQn9wNqpBLc6sp53eq/JRTOUD26Yehjsld5qN52Bv2r5QG7o
26
4VU123akXUYzupvq1f+tmF9NwYa7MPEPFzCzJHhNXjZNRxcsW1WLW34fhQCm0oak3oSPoNo5qeGi
27
jNsTSkgSt1mPH0P8d95af2VJnT6zbrclxvH4emqpT9oGLsWqKWLlIbZ7u1PUjuNVwLHuj909/apm
28
C13RBIpV52fey4qey34bnRHdCTknZeN/TJLTJ9hMWzz9TbdjfIFaiF7MeY+OYRXzUJeQuHHMu/2I
29
emkoR26mYi6irvmx8AdPcPCwcRKw2Ca4xLhbNw==
30
</ds:Modulus>
31
<ds:Exponent>AQAB</ds:Exponent>
32
</ds:RSAKeyValue>
33
</ds:KeyValue>
34
<ds:X509Data>
35
<ds:X509Certificate>
36
MIIC9zCCAd+gAwIBAgIEfe6j3jANBgkqhkiG9w0BAQsFADAsMSowKAYDVQQDEyFTQU1MIE1ldGFk
37
YXRhIFNpZ25pbmcgQ2VydGlmaWNhdGUwHhcNMTYwNzI5MDczNjM4WhcNMjYwNjA3MDczNjM4WjAs
38
MSowKAYDVQQDEyFTQU1MIE1ldGFkYXRhIFNpZ25pbmcgQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3
39
DQEBAQUAA4IBDwAwggEKAoIBAQDvX69N/roE+BhQXuybhP75br2oxXIZCf3A2qkEtzqynnd6r8lF
40
M5QPbph6GOyV3mo3nYG/avlAbujhVTXbdqRdRjO6m+rV/62YX03Bhrsw8Q8XMLMkeE1eNk1HFyxb
41
VYtbfh+FAKbShqTehI+g2jmp4aKM2xNKSBK3WY8fQ/x33lp/ZUmdPrNutyXG8fh6aqlP2gYuxaop
42
YuUhtnu7U9SO41XAse6P3T39qmYLXdEEilXnZ97Lip7LfhudEd0JOSdl439MktMn2ExbPP1Nt2N8
43
gVqIXsx5j45hFfNQl5C4ccy7/Yh6aShHbqZiLqKu+bHwB09w8LBxErDYJrjEuFs3AgMBAAGjITAf
44
MB0GA1UdDgQWBBTT88iZzWO+hN9SBUkpx871lmTuLTANBgkqhkiG9w0BAQsFAAOCAQEABoPpODry
45
XwiM5jjtqk6veR02FevCKHpZP6Od7Kqcfs6lg5LcQmGUOgpmW3Gg4UMjBYkgARsT2Nsnah1CJqa8
46
cjvv8p5KEIhY0hVS8iMJnrb3PDeiFSeP4xSfct/6z/ebV4+QFl22bsm2zpAC6BpFz8+IJ/jAmQzT
47
Vob4MAUeQPnwwzm3xz6yanLZx7BK5cfrTCa+hrarNQCboRjXPwiejF8WRCxpgRHH6yNs5QH/Z6o5
48
e3tUP7uEpn2Ob+kcLsEMGb9DghkoDAgkHCOZeTy+7hgxt+/T94cLTa58gVtvEOnd0GuL7Vfd+IVd
49
XgSard8RfR3OyZlf6M4aSGQA73sskQ==
50
</ds:X509Certificate>
51
</ds:X509Data>
52
</ds:KeyInfo>
53
</ds:Signature><md:EntityDescriptor entityID="https://access-check.edugain.org/simplesaml/saml2/idp/metadata.php">
54
			<md:Extensions>
55
				<mdrpi:RegistrationInfo registrationAuthority="https://federation.renater.fr/" registrationInstant="2015-01-30T15:32:58Z">
56
					<mdrpi:RegistrationPolicy xml:lang="en">https://services.renater.fr/federation/en/metadata_registration_practice_statement</mdrpi:RegistrationPolicy>
57
				</mdrpi:RegistrationInfo>
58
			</md:Extensions>
59
		<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
60
			<md:Extensions>
61
    				<shibmd:Scope regexp="false">access-check.edugain.org</shibmd:Scope>
62

  
63
			    <mdui:UIInfo>
64

  
65
			      <mdui:DisplayName xml:lang="en">eduGAIN Access Check</mdui:DisplayName>
66

  
67
			      <mdui:Logo height="16" width="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAAAQAAAAEABcxq3DAAABbUlEQVQoz52RPW4UQRCFv6ru6fnZNSxCJuVKpFwAicR3IUI+AakvhQhI7JU8M/1T3QQzsg1sAhWV3qtPVaonfPrCX9W+fpTP3y7otzceGJKJxCiujpVUkCv0+m3K967hixUfWgWSHwAPxG6UqtIFbEFHGoCqR721BX+wirO8LVGg+a6amEXXjqxhM5ZxNFPvX1Odocn5ZwB7HCn4hWI42wxD6KWUBVfIZxXd9J2zNhHFmro2Gx3QPKSReGCiV40ESM/A1NcxTDnn7HvLFViXBRZ6MKJnmwaktca/lFzO4fZG7n5e0D9cK4DjFI69yrGbwvhqP7KWp7kR99QrQAn3cW5N5/SQ1rivDgdUCUegFfkDkKF26TG7FijzZoS0sM7kM7D6gncvgG5d7cEdu6wN+v3R/cRwQgaAdnA2vACQvn9Tk6NNqjtwjis1Iyp4JOpafwvu3ZAYa3TV2fBjBnjfe8uLKErOZLk6fU//lcMvhuSrh7MzMwcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMDctMjBUMTA6NTA6MTErMDI6MDCDfj0WAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE1LTA3LTIwVDEwOjUwOjExKzAyOjAw8iOFqgAAAABJRU5ErkJggg==</mdui:Logo>
68
			      <mdui:InformationURL xml:lang="fr">http://www.renater.fr</mdui:InformationURL>
69
			      <mdui:Description xml:lang="en">eduGAIN Access Check allows administrators of a Service Provider (SP) registered in eduGAIN to create test accounts with different profiles to validate the behaviour and test federated login. The test accounts can only be used to access own services.</mdui:Description>
70
          		      <mdui:DisplayName xml:lang="fr">eduGAIN Access Check</mdui:DisplayName>
71
          		      <mdui:Description xml:lang="fr">eduGAIN Access Check allows administrators of a Service Provider (SP) registered in eduGAIN to create test accounts with different profiles to validate the behaviour and test federated login. The test accounts can only be used to access own services.</mdui:Description>
72
      			    </mdui:UIInfo>
73
			</md:Extensions>
74
				<md:KeyDescriptor use="signing">
75
<ds:KeyInfo>
76

  
77
					  <ds:X509Data>
78
					    <ds:X509Certificate>
79
					      MIID2zCCAsOgAwIBAgIJAJpdV2MFitUqMA0GCSqGSIb3DQEBBQUAMIGDMQswCQYD
80
VQQGEwJGUjEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MQ4wDAYDVQQKDAVHRUFOVDEd
81
MBsGA1UEAwwUdGVzdC1pZHAuZWR1Z2Fpbi5vcmcxLjAsBgkqhkiG9w0BCQEWH3Rl
82
c3RpZHBhY2NvdW50bWFuYWdlckBnZWFudC5uZXQwHhcNMTQxMjE4MTAxODU5WhcN
83
MjQxMjE3MTAxODU5WjCBgzELMAkGA1UEBhMCRlIxFTATBgNVBAcMDERlZmF1bHQg
84
Q2l0eTEOMAwGA1UECgwFR0VBTlQxHTAbBgNVBAMMFHRlc3QtaWRwLmVkdWdhaW4u
85
b3JnMS4wLAYJKoZIhvcNAQkBFh90ZXN0aWRwYWNjb3VudG1hbmFnZXJAZ2VhbnQu
86
bmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo48FFP0P/81e3WHb
87
U91F/TYDZC/JypEqO2XQNH50baXpk2JrJFVFOWdgdK6qWHsLznuxngRsfOasAaVA
88
Ob1Bf3g2xgPUd2htSLxds+o/Y24DOM6ZairxbWJk2rOvLhJFchlrcNWCpMtUCkfJ
89
xmqGmeo93XAud5byj3wQ1NuH2o8rjTPAkMgQdr8D2b8EG1NYEH00AqRlXZTFCWGL
90
KDEuZwyta6vgMQYT4K6UF/F+HWF2wzbmVgRTHguJ0rzNqz6t+9CtLkhyZO+/57Ro
91
4U0ikshVWkUOENPKCnB1t+ebs/AsNozbIGA/HcdtwUwDgIowv/K0hdnLDC1vz6/S
92
F3rnGQIDAQABo1AwTjAdBgNVHQ4EFgQUgWN9jmJxOEHYU5m8D0atl895HxowHwYD
93
VR0jBBgwFoAUgWN9jmJxOEHYU5m8D0atl895HxowDAYDVR0TBAUwAwEB/zANBgkq
94
hkiG9w0BAQUFAAOCAQEAXvlBHMaBK6m0PQNanTqGBRdRAFt8Xkr5texD5mPTmS/7
95
nqnxlN0orqYWGCaARmQE+T77EB2a2n9g2s130pUXwJxcbUwIOdPKH6CMKEHT/512
96
bndJXQ3DyhkuVSLtRFOdfleIhi8qUkNC9FWxM4jDHDTTQtNEHnCjFxlhxw+ri5QJ
97
AVKpH9MkcuIkM6Jx+QhNwTDwCRIJffoDOH420yR5EWx/sQ4tjKQGiFOPv/WHFjXd
98
LqHU+X8ErzxeNmUHHST6pHePWRCMtoPTdCPhEroJhou6NMHh8ylQOIVHt6gggc7r
99
kUWMUybDUxPp49qMeNkdKqFPby2aW7ouKRoOXuxZhg==
100
					    </ds:X509Certificate>
101
					  </ds:X509Data>
102

  
103
					</ds:KeyInfo>
104
				</md:KeyDescriptor>
105

  
106

  
107

  
108

  
109

  
110

  
111
			<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
112

  
113

  
114

  
115
	        <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://access-check.edugain.org/simplesaml/saml2/idp/SSOService.php"/>
116

  
117

  
118

  
119
		</md:IDPSSODescriptor>
120

  
121

  
122

  
123
		<md:Organization>
124

  
125
			<md:OrganizationName xml:lang="en">eduGAIN Access Check</md:OrganizationName>
126
			<md:OrganizationDisplayName xml:lang="en">eduGAIN Access Check</md:OrganizationDisplayName>
127
			<md:OrganizationURL xml:lang="en">http://www.renater.fr</md:OrganizationURL>
128

  
129
		</md:Organization>
130

  
131

  
132
			    <md:ContactPerson contactType="technical">
133
			     <md:EmailAddress>edugain-integration@geant.net</md:EmailAddress>
134
		        </md:ContactPerson>
135

  
136

  
137
	</md:EntityDescriptor><md:EntityDescriptor entityID="https://aishib.agropolis.fr/idp/shibboleth">
138
			<md:Extensions>
139
				<mdrpi:RegistrationInfo registrationAuthority="https://federation.renater.fr/" registrationInstant="2013-06-06T11:49:20Z">
140
					<mdrpi:RegistrationPolicy xml:lang="en">https://services.renater.fr/federation/en/metadata_registration_practice_statement</mdrpi:RegistrationPolicy>
141
				</mdrpi:RegistrationInfo>
142
			</md:Extensions>
143
		<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:2.0:protocol">
144
			<md:Extensions>
145
    				<shibmd:Scope regexp="false">agropolis.fr</shibmd:Scope>
146

  
147
			    <mdui:UIInfo>
148

  
149
			      <mdui:DisplayName xml:lang="en">Agropolis International</mdui:DisplayName>
150

  
151
			      <mdui:Logo height="16" width="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAAAQAAAAEABcxq3DAAACPUlEQVQoz21SPW8TQRR8u7d3ju34Er6UCHACRqK4Bjk0oDSEjiYSBQVS8k9AkWgp+AuUASGEaCASkoPcgARVMF8iTnxJwMFJfL47397u3u6jsGVFwLzmFTN6ozdDEBEAAOD4kiRJsVgkhMA/YMfZzc3NVstnzGq1/KXlJfgf2Ii9XquVZ2YWbi5Io4MsbfPeZK4wRm0AOH6KICIivq2tV6/O5cYLHztbW9FvYTKuZaLk7PjpW+UrJXtspCHGmGazCQBnZ8svtj/0tbAINYhcy1DyThpRhHtztyedwkBDAWBnZ6dSqbzZ+7THu0IraTJplDSZ0JnQ2Y+482jj1cgSRUTHdrhW9fa3SKaxErFKIyUiKaIsjVQaKf5s631HxP1+X2vNwjB0J9zd+OgnD044Ra4lI5ZBw7XqyeRQREciaqe9xoE/K/OFfIENnqBM1uZBqlWBOYxQjYZnqieTThru8zCU3AASIAjIXNellGLePkjCUPICy1mEDAShSg7SqCsTBtQ7WfY3vlarVUYpdUsuAs5PXV7dfpe3HEYtjSbNZJwJoRUi3rl4fSrvfufctm0ySA0RD0V84+WDL1EbCCDCYIDApeKZ+uKK6faDIPA8jw7jIORUbry2eH+5Mu8ARTSAkCPW3QvX6osrE+B8bjQ8zxu2aACDaIwxxuzzsLbbePj88a8k0Mb4vr/2ei3LMoOIiGRU0iGGVuDp6pNz5fNSiKnpac/zgAABMuzSXwIggIjdbrdUKjHGYGQaAAD+AJBJecXxnEvWAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTA3LTIwVDEwOjQ4OjE1KzAyOjAwpWiGMQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNS0wNy0yMFQxMDo0ODoxNSswMjowMNQ1Po0AAAAASUVORK5CYII=</mdui:Logo>
152
			      <mdui:InformationURL xml:lang="fr">http://www.agropolis.fr</mdui:InformationURL>
153

  
154
          		      <mdui:DisplayName xml:lang="fr">Agropolis International</mdui:DisplayName>
155

  
156
      			    </mdui:UIInfo>
157
			</md:Extensions>
158
				<md:KeyDescriptor use="signing">
159
<ds:KeyInfo>
160

  
161
					  <ds:X509Data>
162
					    <ds:X509Certificate>
163
					      MIIDNzCCAh+gAwIBAgIUYY3sGXwChkj2CRy6QFDvkdj2zlAwDQYJKoZIhvcNAQEF
164
BQAwHjEcMBoGA1UEAxMTYWlzaGliLmFncm9wb2xpcy5mcjAeFw0xMzA1MTUxMzM3
165
MTJaFw0zMzA1MTUxMzM3MTJaMB4xHDAaBgNVBAMTE2Fpc2hpYi5hZ3JvcG9saXMu
166
ZnIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxrDy6lrhIBjcxv16n
167
4UJ2cEMYPO4wSmfDwhO6feoSIEuIblYRHE2nQKirMokwD6seF4rbDHyxLXg/ColL
168
VLv+0CJteIOZjSCgSN90WzQRrC1Ex5sJfPu6yPEXvW8H1906gEg6ok8rlCIHRGfE
169
15pHK5eqxQS5f2n8c2t/Uk33/FBj79/hb3Cd7vE4mdlvReD3AFswC0lV4bPmj3Ka
170
KUuMj9xwipwnfWCu6p2/ZJF4M3ADU5grXHJ2Vqmd8DWm5raaObKjYwJddbRBByI8
171
bJJLIwAQQmX4Dh4hf1QKlf2oqWPWVQxLQp0erL1U8IWmj1RG8TTH9xOJl6kkEhYq
172
Z2gfAgMBAAGjbTBrMEoGA1UdEQRDMEGCE2Fpc2hpYi5hZ3JvcG9saXMuZnKGKmh0
173
dHBzOi8vYWlzaGliLmFncm9wb2xpcy5mci9pZHAvc2hpYmJvbGV0aDAdBgNVHQ4E
174
FgQU9A7iQ8Qo+t2JCpKuOOV9YBoYs4MwDQYJKoZIhvcNAQEFBQADggEBAG0LOW6I
175
F+M8n2NpzyQjfVCJCA6QhWjbXrfemiPJFZGZZb2dVmHof4yCpCUYgHOBoZaXPOlB
176
nLYsUWvFZ6V2GELZpLHzHSSrYidieW07qQkh1DwcIYpvtZgLviOtT/tCEGsk925f
177
DUoGdeIqpqt54WZcW9+TbKicvjg3JT4BFOQ17bFNwPW+YjTbvsWYxen+e0mRp4vM
178
V0yMu2f3bccVhePASSZGL3yod3sJ1dPvlrJO9c35BekhtirolVjZqMQ0AYPVifua
179
yIU0dWXsZkAOcBL9kZFbJcYRUIxMgvp8U2Zdv1+ZlwOyXnnWDOOh9wjuT7FAyObU
180
ChvjHlgZHkvLwJI=
181
					    </ds:X509Certificate>
182
					  </ds:X509Data>
183

  
184
					</ds:KeyInfo>
185
				</md:KeyDescriptor>
186

  
187

  
188

  
189

  
190

  
191
			<md:NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</md:NameIDFormat>
192
			<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
193

  
194

  
195
			<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://aishib.agropolis.fr/idp/profile/SAML2/POST/SSO"/>
196

  
197
	        <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://aishib.agropolis.fr/idp/profile/SAML2/Redirect/SSO"/>
198

  
199

  
200
			<md:SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest" Location="https://aishib.agropolis.fr/idp/profile/Shibboleth/SSO"/>
201

  
202

  
203
		</md:IDPSSODescriptor>
204

  
205

  
206

  
207
		<md:Organization>
208

  
209
			<md:OrganizationName xml:lang="en">Agropolis International</md:OrganizationName>
210
			<md:OrganizationDisplayName xml:lang="en">Agropolis International</md:OrganizationDisplayName>
211
			<md:OrganizationURL xml:lang="en">http://www.agropolis.fr</md:OrganizationURL>
212

  
213
		</md:Organization>
214

  
215

  
216

  
217
			    <md:ContactPerson contactType="technical">
218
				 <md:SurName>Jean Cerda</md:SurName>
219
				 <md:EmailAddress>cerda@agropolis.fr</md:EmailAddress>
220
		        </md:ContactPerson>
221

  
222

  
223

  
224
			    <md:ContactPerson contactType="technical">
225
				 <md:SurName>Jean-Pierre  Allano</md:SurName>
226
				 <md:EmailAddress>allano@agropolis.fr</md:EmailAddress>
227
		        </md:ContactPerson>
228

  
229

  
230

  
231

  
232
	</md:EntityDescriptor><md:EntityDescriptor entityID="https://ambre.vetagro-sup.fr/idp/shibboleth">
233
			<md:Extensions>
234
				<mdrpi:RegistrationInfo registrationAuthority="https://federation.renater.fr/" registrationInstant="2013-01-14T16:11:53Z">
235
					<mdrpi:RegistrationPolicy xml:lang="en">https://services.renater.fr/federation/en/metadata_registration_practice_statement</mdrpi:RegistrationPolicy>
236
				</mdrpi:RegistrationInfo>
237
			</md:Extensions>
238
		<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:2.0:protocol">
239
			<md:Extensions>
240
    				<shibmd:Scope regexp="false">vetagro-sup.fr</shibmd:Scope>
241

  
242
			    <mdui:UIInfo>
243

  
244
			      <mdui:DisplayName xml:lang="en">Vetagro Sup</mdui:DisplayName>
245

  
246
			      <mdui:Logo height="16" width="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAAAQAAAAEABcxq3DAAAB/klEQVQ4y2NkQAP///+X+/jxI/PO7dujP3/7kf7v778ffDzciw2MDFaqq6t/Y2RkfIysngndAEZGxkezZ8/25xEQqVDTs5eRVrNU4RQUL7x9+7YtumasBjAwMDBISIgbcvBKct998p7h1fvfDKyc4nz//jF6yjAwSBNlwM+fP3/9/PmT4dbl4wysrCwMf//9Z/r7959UcFISD1EGPH107xgfD9sbTg52hrcvHjDw87Aw/Pz+7tLEefNuEmWAm6ffxvu3Ll4zMjZleHz/KsO7lw+f3bh+czs2tVgNsLS0fPfs4b3VnKy//wrycW84e2L/uobmtg0MpIKurt6N////Z8jPzxcnWTMDAwPD79//tbq7+9bhU8OMTfD//8cMP79L1X/8tjxMQYHXT0Uh88aBgyuvEW3Aly+8ntw8jLU3bx20cnH/xPz2jTyvk9OGpTt3NhIOxP//GRgkJGR8tPVeyElIvWA4dfIqg57Bff0nD+eHExULIb5dqkYmYgYm5r8YPH3vMzx//omBk+edpI4+vxVRBnz5zPqbi4vt7a9fv//9+cvAYG79jIGZmZWBg4NJiCgDdh4sfHD/7u9j714Z/BYWiGBQVkxjePNK8+erV5+uYDOABTMM/nP09y9Z8+OHgNjli9ahf//++8vGwrlAS0tp/v///7kYGRm/IasHAIVTy8DG/VpJAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTA3LTIwVDEwOjUwOjA5KzAyOjAwfDtz7wAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNS0wNy0yMFQxMDo1MDowOSswMjowMA1my1MAAAAASUVORK5CYII=</mdui:Logo>
247
			      <mdui:InformationURL xml:lang="fr">http://www.vetagro-sup.fr</mdui:InformationURL>
248

  
249
          		      <mdui:DisplayName xml:lang="fr">Vetagro Sup</mdui:DisplayName>
250

  
251
      			    </mdui:UIInfo>
252
			</md:Extensions>
253
				<md:KeyDescriptor use="signing">
254
<ds:KeyInfo>
255

  
256
					  <ds:X509Data>
257
					    <ds:X509Certificate>
258
					      MIIDPDCCAiSgAwIBAgIVAL9PsuadPSIZcMHNxlK/oevezmzWMA0GCSqGSIb3DQEB
259
BQUAMB8xHTAbBgNVBAMTFGFtYnJlLnZldGFncm8tc3VwLmZyMB4XDTEyMTEwODEw
260
MTQwNFoXDTMyMTEwODEwMTQwNFowHzEdMBsGA1UEAxMUYW1icmUudmV0YWdyby1z
261
dXAuZnIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc/ptfpmkomwmT
262
4RsID+1Ce1dX0eUjcLgSOZN8hVpHWLag2ERWkpmvB5aK7BAFcI5i//Gk80tAiasu
263
JtlZhBnEw54aTJRGpyL2CVkHyl6SMRxprIi1Ji67IoGqEgUeGaheAxo+tG5e1WSc
264
bIbldcSKdwvjAV+7HSB4C6NqLsAzJH25++yaRH2uf2LTD0TDzNR9Q2hVj/VyYWR+
265
K3HWI1Snjn/i7aFfZZhYmBkwHuQOaPhwCM+khikg5XicMsxUhHCMi93UgHGIsdkr
266
IEGj4xydBTUKsLaykeuFS8EgXbWwCLGkeX76w8xDoFIpnppU/yFd9v7Zg3EBfn4p
267
kTW3GdIjAgMBAAGjbzBtMEwGA1UdEQRFMEOCFGFtYnJlLnZldGFncm8tc3VwLmZy
268
hitodHRwczovL2FtYnJlLnZldGFncm8tc3VwLmZyL2lkcC9zaGliYm9sZXRoMB0G
269
A1UdDgQWBBTPTqWkVHrHXFjmxMWkNt/sp2h5ozANBgkqhkiG9w0BAQUFAAOCAQEA
270
FvXMtfBUmRZCzz8CjanGzr1TBUPmnkrKci5AtkseKw9YlfUmBXTHB01y697nYq6m
271
RB6KhvfW212h9CF0IOEEjoadgDhXqGYhq8PnAOtT4Ty3XDy8SbRh8aQWfvnfSngv
272
FdpHRiSpj5UXXuT5zTtkf59h58XKtEfCkMbUzvdOgUobJzpD0WISmQHPQnx+Neg6
273
9j7oMRrDiZjS39Om8Imu9xvsnddDM3PlsDBIsvrr1o7K5iLkEdR1YYX0ZNDbiFuw
274
QXXl2dwQPB8KrScPUvCe57slU2gFQvvIBzjQysxC6V6TPSuM3A/ee56lACuB3jKj
275
oYkHQc5Gj/1rSMLmu9aLMg==
276
					    </ds:X509Certificate>
277
					  </ds:X509Data>
278

  
279
					</ds:KeyInfo>
280
				</md:KeyDescriptor>
281

  
282

  
283

  
284

  
285

  
286
			<md:NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</md:NameIDFormat>
287
			<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
288

  
289

  
290
			<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ambre.vetagro-sup.fr/idp/profile/SAML2/POST/SSO"/>
291

  
292
	        <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://ambre.vetagro-sup.fr/idp/profile/SAML2/Redirect/SSO"/>
293

  
294

  
295
			<md:SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest" Location="https://ambre.vetagro-sup.fr/idp/profile/Shibboleth/SSO"/>
296

  
297

  
298
		</md:IDPSSODescriptor>
299

  
300

  
301

  
302
		<md:Organization>
303

  
304
			<md:OrganizationName xml:lang="en">Vetagro Sup</md:OrganizationName>
305
			<md:OrganizationDisplayName xml:lang="en">Vetagro Sup</md:OrganizationDisplayName>
306
			<md:OrganizationURL xml:lang="en">http://www.vetagro-sup.fr</md:OrganizationURL>
307

  
308
		</md:Organization>
309

  
310

  
311

  
312
			    <md:ContactPerson contactType="technical">
313
				 <md:SurName>Nicolas Aulas</md:SurName>
314
				 <md:EmailAddress>nicolas.aulas@vetagro-sup.fr</md:EmailAddress>
315
		        </md:ContactPerson>
316

  
317

  
318

  
319

  
320

  
321

  
322
	</md:EntityDescriptor><md:EntityDescriptor entityID="https://antimoine.insa-strasbourg.fr/idp/shibboleth">
323
			<md:Extensions>
324
				<mdrpi:RegistrationInfo registrationAuthority="https://federation.renater.fr/" registrationInstant="2014-02-11T08:44:08Z">
325
					<mdrpi:RegistrationPolicy xml:lang="en">https://services.renater.fr/federation/en/metadata_registration_practice_statement</mdrpi:RegistrationPolicy>
326
				</mdrpi:RegistrationInfo>
327
			</md:Extensions>
328
		<md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:2.0:protocol">
329
			<md:Extensions>
330
    				<shibmd:Scope regexp="false">insa-strasbourg.fr</shibmd:Scope>
331

  
332
			    <mdui:UIInfo>
333

  
334
			      <mdui:DisplayName xml:lang="en">INSA Strasbourg</mdui:DisplayName>
335

  
336
			      <mdui:Logo height="16" width="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAq1BMVEXeAACEhoTWz87nQTn3oqXnIBjvgnvWlpT/5+fnEAjvZWOltrXnNDH3mpT/9/fnCAD/4+fvioz3x8bnVVL3qq3nKCnvkpTnHBjvcXP3sq3vgoT/7+/nFBDnODH3mpz////nDAj3z87vlpTveXv3vr3nAACMmpznRUL3pqXnJCHnFAj/+//nDADvjoz3y87vXVr3rq3nLCnvdXP3srXvhoT/8/fvODH3npz3lpQaie6kAAAACXBIWXMAAABIAAAASABGyWs+AAAACXZwQWcAAAAQAAAAEABcxq3DAAAAe0lEQVQY053OwQqCQBhF4VM22VSMMWrFGP2kkIpUQ1nz/m8Ws20TeFeHb3WRnzEBBjO8xRjZrURi0TFiy+UITjTpjIKaOndZuDSGxF0j9G1+e7CuLN1nE2F7VI28tAoqMMezP1ieFOOp7RPueBXKSqdnMq8Wg66nPP0LX4uIG4jEwJ0sAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTA3LTIwVDEwOjQ5OjA0KzAyOjAwIHfmJQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNS0wNy0yMFQxMDo0OTowNCswMjowMFEqXpkAAAAASUVORK5CYII=</mdui:Logo>
337
			      <mdui:InformationURL xml:lang="fr">http://www.insa-strasbourg.fr</mdui:InformationURL>
338

  
339
          		      <mdui:DisplayName xml:lang="fr">INSA Strasbourg</mdui:DisplayName>
340

  
341
      			    </mdui:UIInfo>
342
			</md:Extensions>
343
				<md:KeyDescriptor use="signing">
344
<ds:KeyInfo>
345

  
346
					  <ds:X509Data>
347
					    <ds:X509Certificate>
348
					      MIIDUDCCAjigAwIBAgIVAIbX8U0uAqAhuXm1jWxiFpggtDTDMA0GCSqGSIb3DQEB
349
CwUAMCQxIjAgBgNVBAMMGXNvdWZyZS5pbnNhLXN0cmFzYm91cmcuZnIwHhcNMTYw
350
OTI3MTIzNjIxWhcNMzYwOTI3MTIzNjIxWjAkMSIwIAYDVQQDDBlzb3VmcmUuaW5z
351
YS1zdHJhc2JvdXJnLmZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
352
sEE02sLRPAG5N81DMHEeGpI2MYF8yG/RiwH07cFIlLqgV80ewOmi0FWPYijxMb8A
353
bmx0RwUMvJBVI6WMxtT9fykhID20k8rWOuYOzvaynzVqCktqVgKoEAxP1PFE9b0n
354
iGKFprjjNl9ZD90GOUsxbAO7yXG9Q4WBa/eThl6XkUvNkSaZp5hcdWrgcAdsae3q
355
iD/uxFa38NXNNeRLGyfxjd2K5qYSzbwBza9s9TOq1+pfw7sxu3/4BnfQ0RLGO6co
356
4tH4Mufh0ome4cyYk4pvW5DOd1AznxDb8HpqvE0zwEsa69c/FDX0akgFZydmc77a
357
j6USn6JKjjbO49yGtG1gVQIDAQABo3kwdzAdBgNVHQ4EFgQUjzMsxZYiokPYxper
358
9zadM8J0F0kwVgYDVR0RBE8wTYIZc291ZnJlLmluc2Etc3RyYXNib3VyZy5mcoYw
359
aHR0cHM6Ly9zb3VmcmUuaW5zYS1zdHJhc2JvdXJnLmZyL2lkcC9zaGliYm9sZXRo
360
MA0GCSqGSIb3DQEBCwUAA4IBAQBFJKsiS3yfWuDB/E+iqQ0TuQJzL5+JIcloN0dw
361
BFxW3VZOju15zeQ7LwRBg9S4SGLMPJU+LM1lvr68cK9brut/FjF51SETIXEeCWo3
362
7+PIqgOCzraLNinmpU/OtN8ENalOPvpS6Jvbd23qB2t+IqOtZ+j15b0Yq4/on1E3
363
W2F9CVzKpe4EwmmtCPQbe7U1wvhgFylEx797pex8veWs79YSYwqvcKMh79dzl8Fo
364
/CgsO5pDrfKmc6SGMkByq75dZj+PqhZDzZ9EFTxbrXOTaS08VRN6a5Rh2iYRnGxq
365
yZl66tPcaIm5PHgOEmu5X4lPkUoY+Jt36Gj3SGCbYt8qH5S0
366
					    </ds:X509Certificate>
367
					  </ds:X509Data>
368

  
369
					</ds:KeyInfo>
370
				</md:KeyDescriptor>
371

  
372

  
373

  
374
				<md:KeyDescriptor use="signing">
375
				       <ds:KeyInfo>
376
					  <ds:X509Data>
377
					    <ds:X509Certificate>
378
					      MIIDXDCCAkSgAwIBAgIVAKI+qiqDCk9wTTqn7OVAoZrvj/CpMA0GCSqGSIb3DQEB
379
BQUAMCcxJTAjBgNVBAMTHGFudGltb2luZS5pbnNhLXN0cmFzYm91cmcuZnIwHhcN
380
MTQwMTEzMTAzOTU4WhcNMzQwMTEzMTAzOTU4WjAnMSUwIwYDVQQDExxhbnRpbW9p
381
bmUuaW5zYS1zdHJhc2JvdXJnLmZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
382
CgKCAQEAtuM8lRjlVjjmrHq9VtguaOMQL+Wd99BiOs56kL3Mbctg1FwH69LYThCW
383
6dOz6WJg/jU/naF7jEikXKc71xGyu7Ph7Iqa9S5hoXXAT8u/0q2nZDeTOraJqKe1
384
FMF2RzXhEEMyQO3CiKNK9b+tbKoNZS7FQCixMZklWZPt4EcEKd6jyRq1WYX3dpnb
385
r9I/aCdhtK/PGvGe5gKTDoTR2HKyWKJTc/obf8x/vlYIEwiaGgdlqI2KiBE0x48n
386
zQdP6XVi3T8ZWbnkLmCfgJtP2C8PtEJuwDRAy0Z9N4DSwvxn5YCVYgBLSi0TLa10
387
B/lUqqBezZrTrA9p9Lt8JtGXW5YGHwIDAQABo38wfTBcBgNVHREEVTBTghxhbnRp
388
bW9pbmUuaW5zYS1zdHJhc2JvdXJnLmZyhjNodHRwczovL2FudGltb2luZS5pbnNh
389
LXN0cmFzYm91cmcuZnIvaWRwL3NoaWJib2xldGgwHQYDVR0OBBYEFLFkjPZUc9JY
390
qrWjldJ/iGGkKAt4MA0GCSqGSIb3DQEBBQUAA4IBAQBSk/wU1mRn4VF2ifmy261K
391
DK7uX+t1H1hh8S38fKSFU7HoNXJTV3vQnmBOpYIGC1gtvmb+qjqpNtikU2zO84Gq
392
Q0bXHxYF2d9RUP89mKaFxE5uNcXFmlOA3ChZY3pMT5zwAPI/T60tGrex7zci7OLn
393
JDAQj/q4Yk9ejx6JTFggQSCCVh+oV/SDIMd2p5AY6H3mto3b6XCk7Lssa8a/D30k
394
pEkZnhTKdN82eRyynuOR7UDU4tasV4d7Mi/j53f5ihnRcsvwh/pYodjoVYY8cEcZ
395
JLnAXYF8coSwh8UN4D/0NHsvTuSOFQc85hGrqacMsvxiQiw9mv01AX5+A5YLEbVQ
396
					    </ds:X509Certificate>
397
					  </ds:X509Data>
398
					</ds:KeyInfo>
399
				</md:KeyDescriptor>
400

  
401

  
402

  
403
			   <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://antimoine.insa-strasbourg.fr/idp/profile/SAML2/Redirect/SLO"/>
404

  
405
			   <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://antimoine.insa-strasbourg.fr/idp/profile/SAML2/POST/SLO"/>
406

  
407
			   <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://antimoine.insa-strasbourg.fr/idp/profile/SAML2/SOAP/SLO"/>
408

  
409

  
410
			<md:NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</md:NameIDFormat>
411
			<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
412

  
413

  
414
			<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://antimoine.insa-strasbourg.fr/idp/profile/SAML2/POST/SSO"/>
415

  
416
	        <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://antimoine.insa-strasbourg.fr/idp/profile/SAML2/Redirect/SSO"/>
417

  
418

  
419
			<md:SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest" Location="https://antimoine.insa-strasbourg.fr/idp/profile/Shibboleth/SSO"/>
420

  
421

  
422
		</md:IDPSSODescriptor>
423

  
424

  
425

  
426
		<md:Organization>
427

  
428
			<md:OrganizationName xml:lang="en">INSA Strasbourg</md:OrganizationName>
429
			<md:OrganizationDisplayName xml:lang="en">INSA Strasbourg</md:OrganizationDisplayName>
430
			<md:OrganizationURL xml:lang="en">http://www.insa-strasbourg.fr</md:OrganizationURL>
431

  
432
		</md:Organization>
433

  
434

  
435

  
436
			    <md:ContactPerson contactType="technical">
437
				 <md:SurName>Lahsen BOUZID</md:SurName>
438
				 <md:EmailAddress>lahsen.bouzid@insa-strasbourg.fr</md:EmailAddress>
439
		        </md:ContactPerson>
440

  
441

  
442

  
443
			    <md:ContactPerson contactType="technical">
444
				 <md:SurName>Simon SCHERRER</md:SurName>
445
				 <md:EmailAddress>simon.scherrer@insa-strasbourg.fr</md:EmailAddress>
446
		        </md:ContactPerson>
447

  
448

  
449

  
450

  
451
        </md:EntityDescriptor>
452
    
453
<md:EntityDescriptor entityID="http://idp5/metadata">
454
<md:IDPSSODescriptor
455
    WantAuthnRequestsSigned="true"
456
    protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
457
<md:KeyDescriptor use="signing">
458
    <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
459
      <ds:X509Data><ds:X509Certificate>
460
MIIDnjCCAoagAwIBAgIBATANBgkqhkiG9w0BAQUFADBUMQswCQYDVQQGEwJGUjEP
461
MA0GA1UECBMGRnJhbmNlMQ4wDAYDVQQHEwVQYXJpczETMBEGA1UEChMKRW50cm91
462
dmVydDEPMA0GA1UEAxMGRGFtaWVuMB4XDTA2MTAyNzA5MDc1NFoXDTExMTAyNjA5
463
MDc1NFowVDELMAkGA1UEBhMCRlIxDzANBgNVBAgTBkZyYW5jZTEOMAwGA1UEBxMF
464
UGFyaXMxEzARBgNVBAoTCkVudHJvdXZlcnQxDzANBgNVBAMTBkRhbWllbjCCASIw
465
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM06Hx6VgHYR9wUf/tZVVTRkVWNq
466
h9x+PvHA2qH4OYMuqGs4Af6lU2YsZvnrmRdcFWv0+UkdAgXhReCWAZgtB1pd/W9m
467
6qDRldCCyysow6xPPKRz/pOTwRXm/fM0QGPeXzwzj34BXOIOuFu+n764vKn18d+u
468
uVAEzk1576pxTp4pQPzJfdNLrLeQ8vyCshoFU+MYJtp1UA+h2JoO0Y8oGvywbUxH
469
ioHN5PvnzObfAM4XaDQohmfxM9Uc7Wp4xKAc1nUq5hwBrHpjFMRSz6UCfMoJSGIi
470
+3xJMkNCjL0XEw5NKVc5jRKkzSkN5j8KTM/k1jPPsDHPRYzbWWhnNtd6JlkCAwEA
471
AaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0
472
ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFP2WWMDShux3iF74+SoO1xf6qhqaMB8G
473
A1UdIwQYMBaAFGjl6TRXbQDHzSlZu+e8VeBaZMB5MA0GCSqGSIb3DQEBBQUAA4IB
474
AQAZ/imK7UMognXbs5RfSB8cMW6iNAI+JZqe9XWjvtmLfIIPbHM96o953SiFvrvQ
475
BZjGmmPMK3UH29cjzDx1R/RQaYTyMrHyTePLh3BMd5mpJ/9eeJCSxPzE2ECqWRUa
476
pkjukecFXqmRItwgTxSIUE9QkpzvuQRb268PwmgroE0mwtiREADnvTFkLkdiEMew
477
fiYxZfJJLPBqwlkw/7f1SyzXoPXnz5QbNwDmrHelga6rKSprYKb3pueqaIe8j/AP
478
NC1/bzp8cGOcJ88BD5+Ny6qgPVCrMLE5twQumJ12V3SvjGNtzFBvg2c/9S5OmVqR
479
LlTxKnCrWAXftSm1rNtewTsF
480
</ds:X509Certificate></ds:X509Data>
481
    </ds:KeyInfo>
482
  </md:KeyDescriptor>
483
  <md:ArtifactResolutionService isDefault="true" index="0"
484
    Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
485
    Location="http://idp5/artifact" />
486
  <md:SingleLogoutService
487
    Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
488
    Location="http://idp5/singleLogoutSOAP" />
489
  <md:SingleLogoutService
490
    Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
491
    Location="http://idp5/singleLogout"
492
    ResponseLocation="http://idp5/singleLogoutReturn" />
493
  <md:ManageNameIDService
494
    Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
495
    Location="http://idp5/manageNameIdSOAP" />
496
  <md:ManageNameIDService
497
    Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
498
    Location="http://idp5/manageNameId"
499
    ResponseLocation="http://idp5/manageNameIdReturn" />
500
  <md:SingleSignOnService
501
    Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
502
    Location="http://idp5/singleSignOn" />
503
  <md:SingleSignOnService
504
    Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
505
    Location="http://idp5/singleSignOnSOAP" />
506
</md:IDPSSODescriptor>
507
<md:AuthnAuthorityDescriptor
508
    protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
509
	<md:AuthnQueryService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://idp6/authnQueryService"/>
510
	<md:AssertionIDRequestService Binding="urn:oasis:names:tc:SAML:2.0:bindings:URI" Location="http://idp6/authnAuthAssertionIDRequestService"/>
511
	<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
512
</md:AuthnAuthorityDescriptor>
513
<md:PDPDescriptor
514
    protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
515
	<md:AuthzService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://idp6/authzService"/>
516
	<md:AssertionIDRequestService Binding="urn:oasis:names:tc:SAML:2.0:bindings:URI" Location="http://idp6/PDPAuthAssertionIDRequestService"/>
517
	<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:kerberos</md:NameIDFormat>
518
</md:PDPDescriptor>
519
<md:AttributeAuthorityDescriptor
520
    protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
521
	<md:AttributeService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="http://idp6/attributeService"/>
522
	<md:AssertionIDRequestService Binding="urn:oasis:names:tc:SAML:2.0:bindings:URI" Location="http://idp6/AttributeAuthAssertionIDRequestService"/>
523
	<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName</md:NameIDFormat>
524
</md:AttributeAuthorityDescriptor>
525
<md:Organization>
526
   <md:OrganizationName xml:lang="en">Entr'ouvert</md:OrganizationName>
527
</md:Organization>
528

  
529
</md:EntityDescriptor>
530
    </md:EntitiesDescriptor>
tests/test_federation_utils.py
1
import base64
2
import os
3
import time
4

  
5
from django.core.files.storage import default_storage
6
from django.utils.text import slugify
7
from hashlib import sha1
8
from httmock import HTTMock
9
from mellon.federation_utils import get_federation_from_url, truncate_unique, \
10
        store_fingerprint, get_entity_id_from_fingerprint
11
from utils import sample_federation_response
12

  
13

  
14
def test_federation_metadata_cache():
15
    url = u'https://dummy.mdserver/metadata.xml'
16
    filepath = default_storage.path(os.path.join('metadata-cache/', truncate_unique(slugify(url))))
17

  
18
    with HTTMock(sample_federation_response):
19
        tmp = get_federation_from_url(url)
20

  
21
    assert default_storage.path(tmp) == filepath
22

  
23
    st = os.stat(filepath)
24

  
25
    assert os.path.isfile(filepath)
26
    assert st.st_mtime < time.time() + 3600
27

  
28
    with HTTMock(sample_federation_response):
29
        get_federation_from_url(url)
30
    stnew = os.stat(filepath)
31

  
32
    assert stnew.st_ctime == st.st_ctime
33
    assert stnew.st_mtime == st.st_mtime
34

  
35
    storig = os.stat(os.path.join('tests', 'federation-sample.xml'))
36

  
37
    assert storig.st_size == st.st_size
38

  
39

  
40
def test_artifact_mapping_cache():
41
    entity_id = u'https://dummy.provider.nowhere/'
42

  
43
    store_fingerprint(entity_id)
44

  
45
    m = sha1()
46
    m.update(entity_id.encode('utf-8'))
47
    fingerprint = m.digest()
48
    fingerprint_b32 = base64.b32encode(fingerprint)
49
    unix_path = default_storage.path(os.path.join('metadata-cache/fingerprints/', fingerprint_b32.decode('utf-8')))
50

  
51
    assert os.path.exists(unix_path)
52
    assert entity_id == get_entity_id_from_fingerprint(fingerprint)
tests/test_sso_slo.py
9 9
from django.utils import six
10 10
from django.utils.six.moves.urllib import parse as urlparse
11 11

  
12
from mellon.utils import create_metadata
12
from mellon.utils import create_metadata, create_server
13
from django.utils.http import urlencode
13 14

  
14 15
from httmock import all_requests, HTTMock, response as mock_response
15 16

  
......
21 22
    return open('tests/metadata.xml').read()
22 23

  
23 24

  
25
@fixture
26
def federation_metadata():
27
    return './tests/federation-sample.xml'
28

  
29

  
24 30
@fixture
25 31
def idp_private_key():
26 32
    return open('tests/idp-private-key.pem').read()
......
48 54
    return private_settings
49 55

  
50 56

  
57
@fixture
58
def federated_sp_settings(private_settings, federation_metadata, sp_private_key, public_key):
59
    private_settings.MELLON_FEDERATIONS = [{
60
        'FEDERATION': federation_metadata,
61
    }]
62
    private_settings.MELLON_PUBLIC_KEYS = [public_key]
63
    private_settings.MELLON_PRIVATE_KEYS = [sp_private_key]
64
    private_settings.MELLON_NAME_ID_POLICY_FORMAT = lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT
65
    private_settings.LOGIN_REDIRECT_URL = '/'
66
    return private_settings
67

  
68

  
51 69
@fixture
52 70
def sp_metadata(sp_settings, rf):
53 71
    request = rf.get('/')
54 72
    return create_metadata(request)
55 73

  
56 74

  
75
@fixture
76
def federated_sp_metadata(federated_sp_settings, rf):
77
    request = rf.get('/')
78
    return create_metadata(request)
79

  
80

  
57 81
class MockIdp(object):
58 82
    def __init__(self, idp_metadata, private_key, sp_metadata):
59 83
        self.server = server = lasso.Server.newFromBuffers(idp_metadata, private_key)
......
120 144
    return MockIdp(idp_metadata, idp_private_key, sp_metadata)
121 145

  
122 146

  
147
@fixture
148
def federated_idp(federated_sp_settings, idp_metadata, idp_private_key, federated_sp_metadata):
149
    return MockIdp(idp_metadata, idp_private_key, federated_sp_metadata)
150

  
151

  
123 152
def test_sso_slo(db, app, idp, caplog, sp_settings):
124 153
    response = app.get(reverse('mellon_login') + '?next=/whatever/')
125 154
    url, body, relay_state = idp.process_authn_request_redirect(response['Location'])
......
210 239
    assert 'created new user' in caplog.text
211 240
    assert 'logged in using SAML' in caplog.text
212 241
    assert response['Location'].endswith('/whatever/')
242

  
243

  
244
def test_login_federation(db, app, federated_idp, caplog, federated_sp_settings):
245
    qs = urlencode({
246
        'entityID': 'http://idp5/metadata',
247
    })
248
    response = app.get('/login/?' + qs)
249
    url, body, _ = federated_idp.process_authn_request_redirect(response['Location'])
250
    assert url.endswith(reverse('mellon_login'))
251
    response = app.post(reverse('mellon_login'), params={'SAMLResponse': body})
252
    assert 'created new user' in caplog.text
253
    assert 'logged in using SAML' in caplog.text
254
    assert response['Location'].endswith(federated_sp_settings.LOGIN_REDIRECT_URL)
255

  
256

  
257
def test_sso_artifact_federation(db, app, caplog, federated_sp_settings, idp_metadata, idp_private_key, rf):
258
    qs = urlencode({
259
        'entityID': 'http://idp5/metadata',
260
    })
261
    federated_sp_settings.MELLON_DEFAULT_ASSERTION_CONSUMER_BINDING = 'artifact'
262
    request = rf.get('/')
263
    federated_sp_metadata = create_metadata(request)
264
    idp = MockIdp(idp_metadata, idp_private_key, federated_sp_metadata)
265
    response = app.get('/login/?' + qs)
266
    url, body, _ = idp.process_authn_request_redirect(response['Location'])
267
    assert body is None
268
    assert reverse('mellon_login') in url
269
    assert 'SAMLart' in url
270
    acs_artifact_url = url.split('testserver', 1)[1]
271
    with HTTMock(idp.mock_artifact_resolver()):
272
        response = app.get(acs_artifact_url)
273
    assert 'created new user' in caplog.text
274
    assert 'logged in using SAML' in caplog.text
275
    assert response['Location'].endswith(federated_sp_settings.LOGIN_REDIRECT_URL)
276
    # force delog
277
    app.session.flush()
278
    assert 'dead artifact' not in caplog.text
279
    with HTTMock(idp.mock_artifact_resolver()):
280
        response = app.get(acs_artifact_url)
281
    # verify retry login was asked
282
    assert 'dead artifact' in caplog.text
283
    assert response.status_code == 302
284
    assert reverse('mellon_login') in url
285
    response = response.follow()
286
    url, body, _ = idp.process_authn_request_redirect(response['Location'])
287
    reset_caplog(caplog)
288
    # verify caplog has been cleaned
289
    assert 'created new user' not in caplog.text
290
    assert body is None
291
    assert reverse('mellon_login') in url
292
    assert 'SAMLart' in url
293
    acs_artifact_url = url.split('testserver', 1)[1]
294
    with HTTMock(idp.mock_artifact_resolver()):
295
        response = app.get(acs_artifact_url)
296
    assert 'created new user' in caplog.text
297
    assert 'logged in using SAML' in caplog.text
298
    assert response['Location'].endswith(federated_sp_settings.LOGIN_REDIRECT_URL)
tests/test_utils.py
1
import re
2 1
import datetime
2
import logging
3
import os
4
import re
3 5

  
4 6
import mock
5 7
import lasso
6 8
import requests.exceptions
7 9
from httmock import HTTMock
8 10

  
9
from mellon.utils import create_server, create_metadata, iso8601_to_datetime, flatten_datetime
11
from mellon.utils import create_server, create_metadata, iso8601_to_datetime, \
12
        flatten_datetime, get_idp, create_loaded_server
10 13
import mellon.utils
11 14
from xml_utils import assert_xml_constraints
12 15

  
13
from utils import error_500, metadata_response
16
from utils import error_500, metadata_response, sample_federation_response, \
17
        html_response, dummy_md_response
14 18

  
15 19

  
16
def test_create_server_connection_error(mocker, rf, private_settings, caplog):
20
def test_create_server_connection_error_lazy(mocker, rf, private_settings, caplog):
17 21
    mocker.patch('requests.get',
18 22
                 side_effect=requests.exceptions.ConnectionError('connection error'))
19 23
    private_settings.MELLON_IDENTITY_PROVIDERS = [
......
23 27
    ]
24 28
    request = rf.get('/')
25 29
    create_server(request)
26
    assert 'connection error' in caplog.text
30
    assert 'failed with error' not in caplog.text
31
    create_loaded_server(request)
32
    assert 'failed with error' in caplog.text
27 33

  
28 34

  
29
def test_create_server_internal_server_error(mocker, rf, private_settings, caplog):
35
def test_create_server_internal_server_error_lazy(mocker, rf, private_settings, caplog):
30 36
    private_settings.MELLON_IDENTITY_PROVIDERS = [
31 37
        {
32 38
            'METADATA_URL': 'http://example.com/metadata',
33 39
        }
34 40
    ]
35 41
    request = rf.get('/')
36
    assert not 'failed with error' in caplog.text
42
    assert 'failed with error' not in caplog.text
37 43
    with HTTMock(error_500):
38 44
        create_server(request)
45
    assert 'failed with error' not in caplog.text
46
    with HTTMock(error_500):
47
        create_loaded_server(request)
39 48
    assert 'failed with error' in caplog.text
40 49

  
41 50

  
42
def test_create_server_invalid_metadata(mocker, rf, private_settings, caplog):
51
def test_load_federation_file_lazy(mocker, rf, private_settings, caplog, tmpdir):
52
    private_settings.MELLON_FEDERATIONS = [
53
            {'FEDERATION': 'tests/federation-sample.xml'},
54
    ]
55
    request = rf.get('/')
56
    assert 'failed with error' not in caplog.text
57
    with HTTMock(html_response):
58
        server = create_server(request)
59
    assert len(server.providers) == 0
60
    with HTTMock(html_response):
61
        server = create_loaded_server(request)
62
    assert len(server.providers) == 1
63

  
64

  
65
def test_load_federation_url_lazy(mocker, rf, private_settings, caplog, tmpdir):
66
    private_settings.MELLON_FEDERATIONS = [
67
            {'FEDERATION': 'https://dummy.server/metadata.xml'},
68
    ]
69
    request = rf.get('/')
70
    assert 'failed with error' not in caplog.text
71
    with HTTMock(dummy_md_response):
72
        server = create_server(request)
73
    assert len(server.providers) == 0
74
    with HTTMock(dummy_md_response):
75
        server = create_loaded_server(request)
76
    assert len(server.providers) == 1
77

  
78

  
79
def test_federation_parameters_lazy(mocker, rf, private_settings, caplog, tmpdir):
80
    private_settings.MELLON_FEDERATIONS = [{
81
            'FEDERATION': 'tests/federation-sample.xml',
82
            'VERIFY_SSL_CERTIFICATE': False,
83
            'ERROR_REDIRECT_AFTER_TIMEOUT': 150,
84
            'PROVISION': True
85
    }]
86
    request = rf.get('/')
87
    assert 'failed with error' not in caplog.text
88
    with HTTMock(html_response):
89
        server = create_server(request)
90
    assert len(server.providers) == 0
91
    with HTTMock(dummy_md_response):
92
        server = create_loaded_server(request)
93
    assert len(server.providers) == 1
94
    for entity_id in server.providers.keys():
95
        idp = get_idp(entity_id)
96
        assert idp
97
        assert idp['VERIFY_SSL_CERTIFICATE'] is False
98
        assert idp['ERROR_REDIRECT_AFTER_TIMEOUT'] == 150
99
        assert idp['PROVISION'] is True
100

  
101

  
102
def test_create_server_invalid_metadata_lazy(mocker, rf, private_settings, caplog):
103
    caplog.set_level(logging.DEBUG)
43 104
    private_settings.MELLON_IDENTITY_PROVIDERS = [
44 105
        {
45 106
            'METADATA': 'xxx',
......
49 110
    assert not 'failed with error' in caplog.text
50 111
    with HTTMock(error_500):
51 112
        create_server(request)
52
    assert len(caplog.records) == 1
53
    assert re.search('METADATA.*is invalid', caplog.text)
113
    assert len(caplog.records) == 0
114
    assert not re.search('METADATA.*is invalid|bad metadata in idp|Failed to add new provider.', caplog.text)
115

  
116
    # Server created for one single provider:
117
    with HTTMock(error_500):
118
        create_loaded_server(request)
119
    assert len(caplog.records) == 5
120
    assert re.search('METADATA.*is invalid|bad metadata in idp|Failed to add new provider.', caplog.text)
54 121

  
55 122

  
56 123
def test_create_server_invalid_metadata_file(mocker, rf, private_settings, caplog):
......
67 134
    assert len(server.providers) == 0
68 135

  
69 136

  
70
def test_create_server_good_metadata_file(mocker, rf, private_settings, caplog):
137
def test_create_server_good_metadata_file_lazy(mocker, rf, private_settings, caplog):
71 138
    private_settings.MELLON_IDENTITY_PROVIDERS = [
72 139
        {
73
            'METADATA': '/xxx',
140
            'METADATA': './tests/metadata.xml',
74 141
        }
75 142
    ]
76 143
    request = rf.get('/')
77
    with mock.patch(
78
        'mellon.adapters.open', mock.mock_open(read_data=open('tests/metadata.xml').read()),
79
            create=True):
144
    with HTTMock(html_response):
80 145
        server = create_server(request)
81 146
    assert 'ERROR' not in caplog.text
147
    assert len(server.providers) == 0
148
    with HTTMock(html_response):
149
        server = create_loaded_server(request)
82 150
    assert len(server.providers) == 1
83 151

  
84 152

  
85
def test_create_server_good_metadata(mocker, rf, private_settings, caplog):
153
def test_create_server_good_metadata_lazy(mocker, rf, private_settings, caplog):
86 154
    private_settings.MELLON_IDENTITY_PROVIDERS = [
87 155
        {
88 156
            'METADATA': open('tests/metadata.xml').read(),
......
92 160
    assert not 'failed with error' in caplog.text
93 161
    server = create_server(request)
94 162
    assert 'ERROR' not in caplog.text
163
    assert len(server.providers) == 0
164
    server = create_loaded_server(request)
95 165
    assert len(server.providers) == 1
96 166

  
97 167

  
98
def test_create_server_invalid_idp_dict(mocker, rf, private_settings, caplog):
168
def test_create_server_invalid_idp_dict_lazy(mocker, rf, private_settings, caplog):
99 169
    private_settings.MELLON_IDENTITY_PROVIDERS = [
100 170
        {
101 171
        }
......
103 173
    request = rf.get('/')
104 174
    assert not 'failed with error' in caplog.text
105 175
    create_server(request)
106
    assert 'missing METADATA' in caplog.text
176
    assert 'missing METADATA' not in caplog.text
177
    server = create_loaded_server(request)
178
    assert not len(server.providers)
107 179

  
108 180

  
109
def test_create_server_good_metadata_url(mocker, rf, private_settings, caplog):
181
def test_create_server_good_metadata_url_lazy(mocker, rf, private_settings, caplog):
110 182
    private_settings.MELLON_IDENTITY_PROVIDERS = [
111 183
        {
112 184
            'METADATA_URL': 'http://example.com/metadata',
......
118 190
    with HTTMock(metadata_response):
119 191
        server = create_server(request)
120 192
    assert 'ERROR' not in caplog.text
193
    assert len(server.providers) == 0
194

  
195
    with HTTMock(dummy_md_response):
196
        server = create_loaded_server(request)
121 197
    assert len(server.providers) == 1
122 198

  
123 199

  
tests/utils.py
13 13

  
14 14
@all_requests
15 15
def metadata_response(url, request):
16
    return response(200, content=open('tests/metadata.xml').read())
16
    return response(200, content=open('tests/metadata.xml', 'r').read())
17

  
18

  
19
@all_requests
20
def dummy_md_response(url, request):
21
    return response(200, content=open('tests/dummy_md.xml', 'r').read())
22

  
23

  
24
@all_requests
25
def sample_federation_response(url, request):
26
    return response(200, content=open('tests/federation-sample.xml', 'r').read())
17 27

  
18 28

  
19 29
def reset_caplog(cap):
20
-