Skip to content

Commit 745cda2

Browse files
committed
Add shibmd_scopes metadata extractor
Signed-off-by: Ivan Kanakarakis <[email protected]>
1 parent c63f108 commit 745cda2

File tree

3 files changed

+107
-16
lines changed

3 files changed

+107
-16
lines changed

src/saml2/mdstore.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from hashlib import sha1
1111
from os.path import isfile
1212
from os.path import join
13+
from re import compile as regex_compile
1314

1415
import requests
1516

@@ -58,6 +59,8 @@
5859
from saml2.extension.mdrpi import NAMESPACE as NS_MDRPI
5960
from saml2.extension.mdrpi import RegistrationInfo
6061
from saml2.extension.mdrpi import RegistrationPolicy
62+
from saml2.extension.shibmd import NAMESPACE as NS_SHIBMD
63+
from saml2.extension.shibmd import Scope
6164

6265

6366
logger = logging.getLogger(__name__)
@@ -83,6 +86,7 @@
8386
"service_nameid_mapping": "{ns}&{tag}".format(ns=NS_MD, tag=NameIDMappingService.c_tag),
8487
"mdrpi_registration_info": "{ns}&{tag}".format(ns=NS_MDRPI, tag=RegistrationInfo.c_tag),
8588
"mdrpi_registration_policy": "{ns}&{tag}".format(ns=NS_MDRPI, tag=RegistrationPolicy.c_tag),
89+
"shibmd_scope": "{ns}&{tag}".format(ns=NS_SHIBMD, tag=Scope.c_tag)
8690
}
8791

8892
ENTITY_CATEGORY = "http://macedir.org/entity-category"
@@ -1479,6 +1483,41 @@ def _lookup_elements_by_key(self, root, key):
14791483
)
14801484
return elements
14811485

1486+
def sbibmd_scopes(self, entity_id, typ=None):
1487+
try:
1488+
md = self[entity_id]
1489+
except KeyError:
1490+
md = {}
1491+
1492+
descriptor_scopes = (
1493+
{
1494+
"regexp": is_regexp,
1495+
"text": regex_compile(text) if is_regexp else text,
1496+
}
1497+
for elem in md.get("extensions", {}).get("extension_elements", [])
1498+
if elem.get("__class__") == classnames["shibmd_scope"]
1499+
for is_regexp, text in [
1500+
(elem.get("regexp", "").lower() == "true", elem.get("text", "")),
1501+
]
1502+
)
1503+
1504+
services_of_type = md.get(typ) or []
1505+
services_of_type_scopes = (
1506+
{
1507+
"regexp": is_regexp,
1508+
"text": regex_compile(text) if is_regexp else text,
1509+
}
1510+
for srv in services_of_type
1511+
for elem in srv.get("extensions", {}).get("extension_elements", [])
1512+
if elem.get("__class__") == classnames["shibmd_scope"]
1513+
for is_regexp, text in [
1514+
(elem.get("regexp", "").lower() == "true", elem.get("text", "")),
1515+
]
1516+
)
1517+
1518+
scopes = chain(descriptor_scopes, services_of_type_scopes)
1519+
return scopes
1520+
14821521
def mdui_uiinfo(self, entity_id):
14831522
try:
14841523
data = self[entity_id]

tests/idp_uiinfo.xml

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
<?xml version='1.0' encoding='UTF-8'?>
2-
<ns0:EntitiesDescriptor xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ns1="urn:mace:shibboleth:metadata:1.0" xmlns:ns2="urn:oasis:names:tc:SAML:metadata:ui" xmlns:ns3="http://www.w3.org/2000/09/xmldsig#"><ns0:EntityDescriptor entityID="http://example.com/saml2/idp.xml"><ns0:IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><ns0:Extensions><ns1:Scope regexp="false">example.org</ns1:Scope><ns2:UIInfo><ns2:Keywords xml:lang="en">foo bar</ns2:Keywords><ns2:Logo height="40" width="30">http://example.com/logo.jpg</ns2:Logo><ns2:InformationURL>http://example.com/saml2/info.html</ns2:InformationURL><ns2:DisplayName>Example Co.</ns2:DisplayName><ns2:Description xml:lang="se">Exempel bolag</ns2:Description><ns2:PrivacyStatementURL>http://example.com/saml2/privacyStatement.html</ns2:PrivacyStatementURL></ns2:UIInfo></ns0:Extensions><ns0:KeyDescriptor><ns3:KeyInfo><ns3:X509Data><ns3:X509Certificate>MIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
3-
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
4-
aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF
5-
MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
6-
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
7-
gQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8vQdzkihscQMXy
8-
3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2PdTZcvDBKGbiaN
9-
efiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQABo4GnMIGkMB0G
10-
A1UdDgQWBBRePsKHKYJsiojE78ZWXccK9K4aJTB1BgNVHSMEbjBsgBRePsKHKYJs
11-
iojE78ZWXccK9K4aJaFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt
12-
U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrzqSSw
13-
mDY9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJSrKOEzHO7TL5cy6
14-
h3qh+3+JAk8HbGBW+cbX6KBCAw/mzU8flK25vnWwXS3dv2FF3Aod0/S7AWNfKib5
15-
U/SA9nJaz/mWeF9S0farz9AQFc8/NSzAzaVq7YbM4F6f6N2FRl7GikdXRCed45j6
16-
mrPzGzk3ECbupFnqyREH3+ZPSdk=
17-
</ns3:X509Certificate></ns3:X509Data></ns3:KeyInfo></ns0:KeyDescriptor><ns0:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://example.com/saml2/" /></ns0:IDPSSODescriptor></ns0:EntityDescriptor></ns0:EntitiesDescriptor>
2+
<ns0:EntitiesDescriptor xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ns1="urn:mace:shibboleth:metadata:1.0" xmlns:ns2="urn:oasis:names:tc:SAML:metadata:ui" xmlns:ns3="http://www.w3.org/2000/09/xmldsig#">
3+
<ns0:EntityDescriptor entityID="http://example.com/saml2/idp.xml">
4+
<ns0:Extensions>
5+
<ns1:Scope regexp="false">descriptor-example.org</ns1:Scope>
6+
<ns1:Scope regexp="true">descriptor-example[^0-9]*\.org</ns1:Scope>
7+
</ns0:Extensions>
8+
<ns0:IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
9+
<ns0:Extensions>
10+
<ns1:Scope regexp="false">idpssodescriptor-example.org</ns1:Scope>
11+
<ns2:UIInfo><ns2:Keywords xml:lang="en">foo bar</ns2:Keywords><ns2:Logo height="40" width="30">http://example.com/logo.jpg</ns2:Logo><ns2:InformationURL>http://example.com/saml2/info.html</ns2:InformationURL><ns2:DisplayName>Example Co.</ns2:DisplayName><ns2:Description xml:lang="se">Exempel bolag</ns2:Description><ns2:PrivacyStatementURL>http://example.com/saml2/privacyStatement.html</ns2:PrivacyStatementURL></ns2:UIInfo>
12+
</ns0:Extensions>
13+
<ns0:KeyDescriptor>
14+
<ns3:KeyInfo><ns3:X509Data><ns3:X509Certificate>MIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8vQdzkihscQMXy3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2PdTZcvDBKGbiaNefiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQABo4GnMIGkMB0GA1UdDgQWBBRePsKHKYJsiojE78ZWXccK9K4aJTB1BgNVHSMEbjBsgBRePsKHKYJsiojE78ZWXccK9K4aJaFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrzqSSwmDY9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJSrKOEzHO7TL5cy6h3qh+3+JAk8HbGBW+cbX6KBCAw/mzU8flK25vnWwXS3dv2FF3Aod0/S7AWNfKib5U/SA9nJaz/mWeF9S0farz9AQFc8/NSzAzaVq7YbM4F6f6N2FRl7GikdXRCed45j6mrPzGzk3ECbupFnqyREH3+ZPSdk=</ns3:X509Certificate></ns3:X509Data></ns3:KeyInfo>
15+
</ns0:KeyDescriptor>
16+
<ns0:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://example.com/saml2/" />
17+
</ns0:IDPSSODescriptor>
18+
</ns0:EntityDescriptor>
19+
</ns0:EntitiesDescriptor>

tests/test_30_mdstore.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import datetime
44
import os
55
import re
6+
from re import compile as regex_compile
67
from collections import OrderedDict
78
from unittest.mock import Mock
89
from unittest.mock import patch
@@ -163,6 +164,10 @@
163164
"class": "saml2.mdstore.MetaDataFile",
164165
"metadata": [(full_path("invalid_metadata_file.xml"),)],
165166
}],
167+
"15": [{
168+
"class": "saml2.mdstore.MetaDataFile",
169+
"metadata": [(full_path("idp_uiinfo.xml"),)],
170+
}],
166171
}
167172

168173

@@ -608,5 +613,50 @@ def test_extension():
608613
assert mds.extension("entity2", "idpsso_descriptor", "test")
609614

610615

616+
def test_shibmd_scope_no_regex_no_descriptor_type():
617+
mds = MetadataStore(ATTRCONV, sec_config, disable_ssl_certificate_validation=True)
618+
mds.imp(METADATACONF["15"])
619+
620+
scopes = mds.sbibmd_scopes(entity_id='http://example.com/saml2/idp.xml')
621+
all_scopes = list(scopes)
622+
623+
expected = [
624+
{
625+
"regexp": False,
626+
"text": "descriptor-example.org",
627+
},
628+
{
629+
"regexp": True,
630+
"text": regex_compile("descriptor-example[^0-9]*\.org"),
631+
},
632+
]
633+
assert len(all_scopes) == 2
634+
assert all_scopes == expected
635+
636+
637+
def test_shibmd_scope_no_regex_all_descriptors():
638+
mds = MetadataStore(ATTRCONV, sec_config, disable_ssl_certificate_validation=True)
639+
mds.imp(METADATACONF["15"])
640+
641+
scopes = mds.sbibmd_scopes(entity_id='http://example.com/saml2/idp.xml', typ="idpsso_descriptor")
642+
all_scopes = list(scopes)
643+
expected = [
644+
{
645+
"regexp": False,
646+
"text": "descriptor-example.org",
647+
},
648+
{
649+
"regexp": True,
650+
"text": regex_compile("descriptor-example[^0-9]*\.org"),
651+
},
652+
{
653+
"regexp": False,
654+
"text": "idpssodescriptor-example.org",
655+
},
656+
]
657+
assert len(all_scopes) == 3
658+
assert all_scopes == expected
659+
660+
611661
if __name__ == "__main__":
612662
test_metadata_extension_algsupport()

0 commit comments

Comments
 (0)