Skip to content

Commit 650d2b9

Browse files
committed
Adapt IdP XML metadata parser to take care of multiple IdP certtificates and be able to inject the data obtained on the settings.
1 parent 701d65e commit 650d2b9

File tree

3 files changed

+266
-83
lines changed

3 files changed

+266
-83
lines changed

src/onelogin/saml2/idp_metadata_parser.py

Lines changed: 87 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
from onelogin.saml2.constants import OneLogin_Saml2_Constants
2020
from onelogin.saml2.xml_utils import OneLogin_Saml2_XML
21-
from onelogin.saml2.utils import OneLogin_Saml2_Utils
2221

2322

2423
class OneLogin_Saml2_IdPMetadataParser(object):
@@ -89,8 +88,7 @@ def parse(
8988
idp_metadata,
9089
required_sso_binding=OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT,
9190
required_slo_binding=OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT,
92-
entity_id=None,
93-
index=0):
91+
entity_id=None):
9492
"""
9593
Parses the Identity Provider metadata and return a dict with extracted data.
9694
@@ -121,99 +119,97 @@ def parse(
121119
that contains multiple EntityDescriptor.
122120
:type entity_id: string
123121
124-
:param index: If the metadata contains more than 1 certificate, use index to get the right certificate.
125-
:type index: number
126-
127122
:returns: settings dict with extracted data
128123
:rtype: dict
129124
"""
130125
data = {}
131126

132127
dom = OneLogin_Saml2_XML.to_etree(idp_metadata)
133-
idp_entity_id = want_authn_requests_signed = idp_name_id_format = idp_sso_url = idp_slo_url = idp_x509_cert = None
128+
idp_entity_id = want_authn_requests_signed = idp_name_id_format = idp_sso_url = idp_slo_url = certs = None
134129

135130
entity_desc_path = '//md:EntityDescriptor'
136131
if entity_id:
137132
entity_desc_path += "[@entityID='%s']" % entity_id
138133
entity_descriptor_nodes = OneLogin_Saml2_XML.query(dom, entity_desc_path)
139134

140135
if len(entity_descriptor_nodes) > 0:
141-
for entity_descriptor_node in entity_descriptor_nodes:
142-
idp_descriptor_nodes = OneLogin_Saml2_XML.query(entity_descriptor_node, './md:IDPSSODescriptor')
143-
if len(idp_descriptor_nodes) > 0:
144-
idp_descriptor_node = idp_descriptor_nodes[0]
145-
146-
idp_entity_id = entity_descriptor_node.get('entityID', None)
147-
148-
want_authn_requests_signed = entity_descriptor_node.get('WantAuthnRequestsSigned', None)
149-
150-
name_id_format_nodes = OneLogin_Saml2_XML.query(idp_descriptor_node, './md:NameIDFormat')
151-
if len(name_id_format_nodes) > 0:
152-
idp_name_id_format = name_id_format_nodes[0].text
153-
154-
sso_nodes = OneLogin_Saml2_XML.query(
155-
idp_descriptor_node,
156-
"./md:SingleSignOnService[@Binding='%s']" % required_sso_binding
157-
)
158-
159-
if len(sso_nodes) > 0:
160-
idp_sso_url = sso_nodes[0].get('Location', None)
161-
162-
slo_nodes = OneLogin_Saml2_XML.query(
163-
idp_descriptor_node,
164-
"./md:SingleLogoutService[@Binding='%s']" % required_slo_binding
165-
)
166-
167-
if len(slo_nodes) > 0:
168-
idp_slo_url = slo_nodes[0].get('Location', None)
169-
170-
# Attempt to extract the cert/public key to be used for
171-
# verifying signatures (as opposed to extracing a key to be
172-
# used for encryption), by specifying `use=signing` in the
173-
# XPath expression. If that does not yield a cert, retry
174-
# using a more relaxed XPath expression (the `use` attribute
175-
# is optional according to the saml-metadata-2.0-os spec).
176-
cert_nodes = OneLogin_Saml2_XML.query(
177-
idp_descriptor_node,
178-
"./md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate"
179-
)
180-
181-
if not cert_nodes:
182-
cert_nodes = OneLogin_Saml2_XML.query(
183-
idp_descriptor_node,
184-
"./md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate"
185-
)
186-
187-
if len(cert_nodes) > 0:
188-
idp_x509_cert = OneLogin_Saml2_Utils.format_cert(cert_nodes[index].text, False)
189-
190-
data['idp'] = {}
191-
192-
if idp_entity_id is not None:
193-
data['idp']['entityId'] = idp_entity_id
194-
195-
if idp_sso_url is not None:
196-
data['idp']['singleSignOnService'] = {}
197-
data['idp']['singleSignOnService']['url'] = idp_sso_url
198-
data['idp']['singleSignOnService']['binding'] = required_sso_binding
199-
200-
if idp_slo_url is not None:
201-
data['idp']['singleLogoutService'] = {}
202-
data['idp']['singleLogoutService']['url'] = idp_slo_url
203-
data['idp']['singleLogoutService']['binding'] = required_slo_binding
204-
205-
if idp_x509_cert is not None:
206-
data['idp']['x509cert'] = idp_x509_cert
207-
208-
if want_authn_requests_signed is not None:
209-
data['security'] = {}
210-
data['security']['authnRequestsSigned'] = want_authn_requests_signed
211-
212-
if idp_name_id_format:
213-
data['sp'] = {}
214-
data['sp']['NameIDFormat'] = idp_name_id_format
215-
216-
break
136+
entity_descriptor_node = entity_descriptor_nodes[0]
137+
idp_descriptor_nodes = OneLogin_Saml2_XML.query(entity_descriptor_node, './md:IDPSSODescriptor')
138+
if len(idp_descriptor_nodes) > 0:
139+
idp_descriptor_node = idp_descriptor_nodes[0]
140+
141+
idp_entity_id = entity_descriptor_node.get('entityID', None)
142+
143+
want_authn_requests_signed = entity_descriptor_node.get('WantAuthnRequestsSigned', None)
144+
145+
name_id_format_nodes = OneLogin_Saml2_XML.query(idp_descriptor_node, './md:NameIDFormat')
146+
if len(name_id_format_nodes) > 0:
147+
idp_name_id_format = name_id_format_nodes[0].text
148+
149+
sso_nodes = OneLogin_Saml2_XML.query(
150+
idp_descriptor_node,
151+
"./md:SingleSignOnService[@Binding='%s']" % required_sso_binding
152+
)
153+
154+
if len(sso_nodes) > 0:
155+
idp_sso_url = sso_nodes[0].get('Location', None)
156+
157+
slo_nodes = OneLogin_Saml2_XML.query(
158+
idp_descriptor_node,
159+
"./md:SingleLogoutService[@Binding='%s']" % required_slo_binding
160+
)
161+
162+
if len(slo_nodes) > 0:
163+
idp_slo_url = slo_nodes[0].get('Location', None)
164+
165+
signing_nodes = OneLogin_Saml2_XML.query(idp_descriptor_node, "./md:KeyDescriptor[not(contains(@use, 'encryption'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate")
166+
encryption_nodes = OneLogin_Saml2_XML.query(idp_descriptor_node, "./md:KeyDescriptor[not(contains(@use, 'signing'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate")
167+
168+
if len(signing_nodes) > 0 or len(encryption_nodes) > 0:
169+
certs = {}
170+
if len(signing_nodes) > 0:
171+
certs['signing'] = []
172+
for cert_node in signing_nodes:
173+
certs['signing'].append(''.join(cert_node.text.split()))
174+
if len(encryption_nodes) > 0:
175+
certs['encryption'] = []
176+
for cert_node in encryption_nodes:
177+
certs['encryption'].append(''.join(cert_node.text.split()))
178+
179+
data['idp'] = {}
180+
181+
if idp_entity_id is not None:
182+
data['idp']['entityId'] = idp_entity_id
183+
184+
if idp_sso_url is not None:
185+
data['idp']['singleSignOnService'] = {}
186+
data['idp']['singleSignOnService']['url'] = idp_sso_url
187+
data['idp']['singleSignOnService']['binding'] = required_sso_binding
188+
189+
if idp_slo_url is not None:
190+
data['idp']['singleLogoutService'] = {}
191+
data['idp']['singleLogoutService']['url'] = idp_slo_url
192+
data['idp']['singleLogoutService']['binding'] = required_slo_binding
193+
194+
if want_authn_requests_signed is not None:
195+
data['security'] = {}
196+
data['security']['authnRequestsSigned'] = want_authn_requests_signed
197+
198+
if idp_name_id_format:
199+
data['sp'] = {}
200+
data['sp']['NameIDFormat'] = idp_name_id_format
201+
202+
if certs is not None:
203+
if len(certs) == 1 or \
204+
(('signing' in certs and len(certs['signing']) == 1) and
205+
('encryption' in certs and len(certs['encryption']) == 1 and
206+
certs['signing'][0] == certs['encryption'][0])):
207+
if 'signing' in certs:
208+
data['idp']['x509cert'] = certs['signing'][0]
209+
else:
210+
data['idp']['x509cert'] = certs['encryption'][0]
211+
else:
212+
data['idp']['x509certMulti'] = certs
217213
return data
218214

219215
@staticmethod
@@ -234,6 +230,14 @@ def merge_settings(settings, new_metadata_settings):
234230
# Guarantee to not modify original data (`settings.copy()` would not
235231
# be sufficient, as it's just a shallow copy).
236232
result_settings = deepcopy(settings)
233+
234+
# previously I will take care of cert stuff
235+
if 'idp' in new_metadata_settings and 'idp' in result_settings:
236+
if new_metadata_settings['idp'].get('x509cert', None) and result_settings['idp'].get('x509certMulti', None):
237+
del result_settings['idp']['x509certMulti']
238+
if new_metadata_settings['idp'].get('x509certMulti', None) and result_settings['idp'].get('x509cert', None):
239+
del result_settings['idp']['x509cert']
240+
237241
# Merge `new_metadata_settings` into `result_settings`.
238242
dict_deep_merge(result_settings, new_metadata_settings)
239243
return result_settings
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?xml version="1.0"?>
2+
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://idp.examle.com/saml/metadata">
3+
<IDPSSODescriptor xmlns:ds="http://www.w3.org/2000/09/xmldsig#" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
4+
<KeyDescriptor use="signing">
5+
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
6+
<ds:X509Data>
7+
<ds:X509Certificate>MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF
8+
BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj
9+
aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW
10+
T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy
11+
MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz
12+
Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV
13+
BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB
14+
DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo
15+
3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw
16+
tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx
17+
VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5
18+
L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t
19+
1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/
20+
BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB
21+
pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD
22+
VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL
23+
DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC
24+
FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B
25+
AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM
26+
GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c
27+
hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB
28+
vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37
29+
MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ
30+
WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw==</ds:X509Certificate>
31+
</ds:X509Data>
32+
</ds:KeyInfo>
33+
</KeyDescriptor>
34+
<KeyDescriptor use="signing">
35+
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
36+
<ds:X509Data>
37+
<ds:X509Certificate>MIICZDCCAc2gAwIBAgIBADANBgkqhkiG9w0BAQ0FADBPMQswCQYDVQQGEwJ1czEUMBIGA1UECAwLZXhhbXBsZS5jb20xFDASBgNVBAoMC2V4YW1wbGUuY29tMRQwEgYDVQQDDAtleGFtcGxlLmNvbTAeFw0xNzA0MTUxNjMzMThaFw0xODA0MTUxNjMzMThaME8xCzAJBgNVBAYTAnVzMRQwEgYDVQQIDAtleGFtcGxlLmNvbTEUMBIGA1UECgwLZXhhbXBsZS5jb20xFDASBgNVBAMMC2V4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6GLkl5lDUZdHNDAojp5i24OoPlqrt5TGXJIPqAZYT1hQvJW5nv17MFDHrjmtEnmW4ACKEy0fAX80QWIcHunZSkbEGHb+NG/6oTi5RipXMvmHnfFnPJJ0AdtiLiPE478CV856gXekV4Xx5u3KrylcOgkpYsp0GMIQBDzleMUXlYQIDAQABo1AwTjAdBgNVHQ4EFgQUnP8vlYPGPL2n6ZzDYij2kMDC8wMwHwYDVR0jBBgwFoAUnP8vlYPGPL2n6ZzDYij2kMDC8wMwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQAlQGAl+b8Cpot1g+65lLLjVoY7APJPWLW0klKQNlMU0s4MU+71Y3ExUEOXDAZgKcFoavb1fEOGMwEf38NaJAy1e/l6VNuixXShffq20ymqHQxOG0q8ujeNkgZF9k6XDfn/QZ3AD0o/IrCT7UMc/0QsfgIjWYxwCvp2syApc5CYfQ==</ds:X509Certificate>
38+
</ds:X509Data>
39+
</ds:KeyInfo>
40+
</KeyDescriptor>
41+
<KeyDescriptor use="encryption">
42+
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
43+
<ds:X509Data>
44+
<ds:X509Certificate>MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF
45+
BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj
46+
aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW
47+
T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy
48+
MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz
49+
Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV
50+
BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB
51+
DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo
52+
3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw
53+
tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx
54+
VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5
55+
L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t
56+
1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/
57+
BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB
58+
pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD
59+
VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL
60+
DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC
61+
FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B
62+
AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM
63+
GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c
64+
hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB
65+
vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37
66+
MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ
67+
WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw==</ds:X509Certificate>
68+
</ds:X509Data>
69+
</ds:KeyInfo>
70+
</KeyDescriptor>
71+
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://idp.examle.com/saml/slo"/>
72+
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
73+
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://idp.examle.com/saml/sso"/>
74+
</IDPSSODescriptor>
75+
</EntityDescriptor>

0 commit comments

Comments
 (0)