diff options
author | Ivan Kanakarakis <ivan.kanak@gmail.com> | 2021-05-18 13:51:43 +0300 |
---|---|---|
committer | Ivan Kanakarakis <ivan.kanak@gmail.com> | 2021-05-18 13:51:43 +0300 |
commit | 745cda2e0222dafe7eee3053ca133ccd012de0a7 (patch) | |
tree | 594af0ed66d4687d1d34e66c2ab6ccba29578fa1 | |
parent | c63f1082a022c00ed1cab47119b053e5c38ff2b2 (diff) | |
download | pysaml2-745cda2e0222dafe7eee3053ca133ccd012de0a7.tar.gz |
Add shibmd_scopes metadata extractor
Signed-off-by: Ivan Kanakarakis <ivan.kanak@gmail.com>
-rw-r--r-- | src/saml2/mdstore.py | 39 | ||||
-rw-r--r-- | tests/idp_uiinfo.xml | 34 | ||||
-rw-r--r-- | tests/test_30_mdstore.py | 50 |
3 files changed, 107 insertions, 16 deletions
diff --git a/src/saml2/mdstore.py b/src/saml2/mdstore.py index 44930773..af142e79 100644 --- a/src/saml2/mdstore.py +++ b/src/saml2/mdstore.py @@ -10,6 +10,7 @@ from warnings import warn as _warn from hashlib import sha1 from os.path import isfile from os.path import join +from re import compile as regex_compile import requests @@ -58,6 +59,8 @@ from saml2.extension.mdui import Logo from saml2.extension.mdrpi import NAMESPACE as NS_MDRPI from saml2.extension.mdrpi import RegistrationInfo from saml2.extension.mdrpi import RegistrationPolicy +from saml2.extension.shibmd import NAMESPACE as NS_SHIBMD +from saml2.extension.shibmd import Scope logger = logging.getLogger(__name__) @@ -83,6 +86,7 @@ classnames = { "service_nameid_mapping": "{ns}&{tag}".format(ns=NS_MD, tag=NameIDMappingService.c_tag), "mdrpi_registration_info": "{ns}&{tag}".format(ns=NS_MDRPI, tag=RegistrationInfo.c_tag), "mdrpi_registration_policy": "{ns}&{tag}".format(ns=NS_MDRPI, tag=RegistrationPolicy.c_tag), + "shibmd_scope": "{ns}&{tag}".format(ns=NS_SHIBMD, tag=Scope.c_tag) } ENTITY_CATEGORY = "http://macedir.org/entity-category" @@ -1479,6 +1483,41 @@ class MetadataStore(MetaData): ) return elements + def sbibmd_scopes(self, entity_id, typ=None): + try: + md = self[entity_id] + except KeyError: + md = {} + + descriptor_scopes = ( + { + "regexp": is_regexp, + "text": regex_compile(text) if is_regexp else text, + } + for elem in md.get("extensions", {}).get("extension_elements", []) + if elem.get("__class__") == classnames["shibmd_scope"] + for is_regexp, text in [ + (elem.get("regexp", "").lower() == "true", elem.get("text", "")), + ] + ) + + services_of_type = md.get(typ) or [] + services_of_type_scopes = ( + { + "regexp": is_regexp, + "text": regex_compile(text) if is_regexp else text, + } + for srv in services_of_type + for elem in srv.get("extensions", {}).get("extension_elements", []) + if elem.get("__class__") == classnames["shibmd_scope"] + for is_regexp, text in [ + (elem.get("regexp", "").lower() == "true", elem.get("text", "")), + ] + ) + + scopes = chain(descriptor_scopes, services_of_type_scopes) + return scopes + def mdui_uiinfo(self, entity_id): try: data = self[entity_id] diff --git a/tests/idp_uiinfo.xml b/tests/idp_uiinfo.xml index fa37d703..7d531fd1 100644 --- a/tests/idp_uiinfo.xml +++ b/tests/idp_uiinfo.xml @@ -1,17 +1,19 @@ <?xml version='1.0' encoding='UTF-8'?> -<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 -BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX -aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF -MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 -ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB -gQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8vQdzkihscQMXy -3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2PdTZcvDBKGbiaN -efiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQABo4GnMIGkMB0G -A1UdDgQWBBRePsKHKYJsiojE78ZWXccK9K4aJTB1BgNVHSMEbjBsgBRePsKHKYJs -iojE78ZWXccK9K4aJaFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt -U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrzqSSw -mDY9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJSrKOEzHO7TL5cy6 -h3qh+3+JAk8HbGBW+cbX6KBCAw/mzU8flK25vnWwXS3dv2FF3Aod0/S7AWNfKib5 -U/SA9nJaz/mWeF9S0farz9AQFc8/NSzAzaVq7YbM4F6f6N2FRl7GikdXRCed45j6 -mrPzGzk3ECbupFnqyREH3+ZPSdk= -</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> +<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:Extensions> + <ns1:Scope regexp="false">descriptor-example.org</ns1:Scope> + <ns1:Scope regexp="true">descriptor-example[^0-9]*\.org</ns1:Scope> + </ns0:Extensions> + <ns0:IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> + <ns0:Extensions> + <ns1:Scope regexp="false">idpssodescriptor-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>MIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8vQdzkihscQMXy3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2PdTZcvDBKGbiaNefiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQABo4GnMIGkMB0GA1UdDgQWBBRePsKHKYJsiojE78ZWXccK9K4aJTB1BgNVHSMEbjBsgBRePsKHKYJsiojE78ZWXccK9K4aJaFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrzqSSwmDY9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJSrKOEzHO7TL5cy6h3qh+3+JAk8HbGBW+cbX6KBCAw/mzU8flK25vnWwXS3dv2FF3Aod0/S7AWNfKib5U/SA9nJaz/mWeF9S0farz9AQFc8/NSzAzaVq7YbM4F6f6N2FRl7GikdXRCed45j6mrPzGzk3ECbupFnqyREH3+ZPSdk=</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> diff --git a/tests/test_30_mdstore.py b/tests/test_30_mdstore.py index bfe261dd..bf54594e 100644 --- a/tests/test_30_mdstore.py +++ b/tests/test_30_mdstore.py @@ -3,6 +3,7 @@ import datetime import os import re +from re import compile as regex_compile from collections import OrderedDict from unittest.mock import Mock from unittest.mock import patch @@ -163,6 +164,10 @@ METADATACONF = { "class": "saml2.mdstore.MetaDataFile", "metadata": [(full_path("invalid_metadata_file.xml"),)], }], + "15": [{ + "class": "saml2.mdstore.MetaDataFile", + "metadata": [(full_path("idp_uiinfo.xml"),)], + }], } @@ -608,5 +613,50 @@ def test_extension(): assert mds.extension("entity2", "idpsso_descriptor", "test") +def test_shibmd_scope_no_regex_no_descriptor_type(): + mds = MetadataStore(ATTRCONV, sec_config, disable_ssl_certificate_validation=True) + mds.imp(METADATACONF["15"]) + + scopes = mds.sbibmd_scopes(entity_id='http://example.com/saml2/idp.xml') + all_scopes = list(scopes) + + expected = [ + { + "regexp": False, + "text": "descriptor-example.org", + }, + { + "regexp": True, + "text": regex_compile("descriptor-example[^0-9]*\.org"), + }, + ] + assert len(all_scopes) == 2 + assert all_scopes == expected + + +def test_shibmd_scope_no_regex_all_descriptors(): + mds = MetadataStore(ATTRCONV, sec_config, disable_ssl_certificate_validation=True) + mds.imp(METADATACONF["15"]) + + scopes = mds.sbibmd_scopes(entity_id='http://example.com/saml2/idp.xml', typ="idpsso_descriptor") + all_scopes = list(scopes) + expected = [ + { + "regexp": False, + "text": "descriptor-example.org", + }, + { + "regexp": True, + "text": regex_compile("descriptor-example[^0-9]*\.org"), + }, + { + "regexp": False, + "text": "idpssodescriptor-example.org", + }, + ] + assert len(all_scopes) == 3 + assert all_scopes == expected + + if __name__ == "__main__": test_metadata_extension_algsupport() |