diff --git a/src/pyff/resource.py b/src/pyff/resource.py index fe5ce6a3..0c8fbd14 100644 --- a/src/pyff/resource.py +++ b/src/pyff/resource.py @@ -339,6 +339,10 @@ def _replace(self, r: Resource) -> None: raise ValueError("Resource {} not present - use add_child".format(r.url)) def add_child(self, url: str, opts: ResourceOpts) -> Resource: + """ + Spent 3 man days. Make sure to make a deep copy of opts. + + """ r = Resource(url, opts) if r in self.children: log.debug(f'\n\n{self}:\nURL {url}\nReplacing child {r}') diff --git a/src/pyff/samlmd.py b/src/pyff/samlmd.py index 671f32f4..c611a55c 100644 --- a/src/pyff/samlmd.py +++ b/src/pyff/samlmd.py @@ -187,9 +187,9 @@ def parse(self, resource: Resource, content: str) -> SAMLParserInfo: resource.expire_time = expire_time info.expiration_time = str(expire_time) - def _extra_md(_t, info, **kwargs): + def _extra_md(_t, resource_opts, **kwargs): entityID = kwargs.get('entityID') - if info['alias'] != entityID: + if resource_opts['alias'] != entityID: return _t sp_entities = kwargs.get('sp_entities') location = kwargs.get('location') @@ -218,7 +218,8 @@ def _extra_md(_t, info, **kwargs): if md_source is not None: location = md_source.attrib.get('src') if location is not None: - child_opts = resource.opts.model_copy(update={'alias': entityID}) + child_opts = resource.opts.model_copy(update={'alias': entityID}, deep=True) + r = resource.add_child(location, child_opts) kwargs = { 'entityID': entityID, @@ -320,12 +321,12 @@ def parse(self, resource: Resource, content: str) -> EidasMDParserInfo: info.scheme_territory, location, fp, args.get('country_code') ) ) - child_opts = resource.opts.model_copy(update={'alias': None}) + child_opts = resource.opts.model_copy(update={'alias': None}, deep=True) child_opts.verify = fp r = resource.add_child(location, child_opts) # this is specific post-processing for MDSL files - def _update_entities(_t, **kwargs): + def _update_entities(_t, resource_opts, **kwargs): _country_code = kwargs.get('country_code') _hide_from_discovery = kwargs.get('hide_from_discovery') for e in iter_entities(_t): diff --git a/src/pyff/test/data/eidas/countries/FR.xml b/src/pyff/test/data/eidas/countries/FR.xml new file mode 100644 index 00000000..4db01c9e --- /dev/null +++ b/src/pyff/test/data/eidas/countries/FR.xml @@ -0,0 +1,55 @@ + + + + + + + MIIECTCCAnGgAwIBAgIBATANBgkqhkiG9w0BAQsFADAzMQswCQYDVQQGEwJGUjEkMCIGA1UECgwb +RlItRElOVU0tZUlEQVMtbm9kZS1wcmVwcm9kMB4XDTIyMDkyMjE0NDgwNloXDTMyMDkxOTE0NDgw +NlowTDELMAkGA1UEBhMCRlIxJDAiBgNVBAoMG0ZSLURJTlVNLWVJREFTLW5vZGUtcHJlcHJvZDEX +MBUGA1UEAwwOZUlEQVMtbWV0YWRhdGEwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDO +B8ffvjFTk3rBzcyBiwFFMDlYPxIlj+i1BLdLvvzSJdUHKZhegOhzNuRVCeJfoyvml9vtlwK5tfzD +iY4znZsFO/qIB1wROxrPVRq8AEw0LiPCfP1Ie1rvK2Ddaw0wUEI6wFn4ViAStP5wI3/yaOqN1cFf +JceXUbvgVfjRS5ETRVZK7UER5vMeMIPn4ESkf86d+GB0rvZoipNOyymXfgs9RU/dvjd40lrRs5rZ +Nc/l4dOrFUpxHXq/AfLsWKjmmOGx959qMmYtsK9KcQdEAQs2L/adKM+yLJL0JyHxyNnWuaUJDBwW +xV5PK/hWnkLkebkgpeB5loF7f7Ra2MnB1uzZlaAa69tSILjycdw0cOcY5+hnH6QFq74JDL4WPDR8 +FzCdcN+TY7/HvG6cTR4lPdW/iY3eZQycQqkEccQiFITAeewwVwZbQddbWhHxdtJE8P73QoZl3iSp +/TzP33Kr3im+IEX1o2PV6Ur36/cDNmc9WQWsRKTeydoIY+AOmV/odt0CAwEAAaMPMA0wCwYDVR0P +BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4IBgQB2FpL/Xen8z318X2To+3nEdRthc/9OwPF9ZNzftcce +QP4Q+lxuwKFacklt5VoXURy/JMsgPtSHhar5kiFZTm2SMXVSEqVDj4UzAobVnUutiZPsFoyPWr/c +iEsWu0VSthsI31AUOvSTisy0w81rPKjNkRuCU5V+AS1Z5rBqMlkwOEiUxUMci+pZQ4VhH+mqg1oH +1rMuJ1gysYf/zMjSWGlraSbcHApIMBAjs24uSTH/O6io+9k9jLhZGZv9LpssoeSPVz3wJY6h1LHT +D5OHZYDtelO4X5ZCVRFx5kCaUpKtb4mvo72oWN9KlGvrqp6SkUF1ogovTl16sEf2FZHoN4r0+5ZN +/vS/plTsCGbe46QPGuj+FO5yN6KclM+eonmBp0JSyoABfYN1T28a4dRwE8CPWCOgJmzxuwqSOH+P +XlUbPftbx5nGsE3mRBAU1Jga2S4S+zJPHirc2lfZGh6ggdYG9kkqp7X7kiTyPyPuWU644+YaCOwv +x7804cuLUcQ8VSQ= + + + + + + + + MIIECTCCAnGgAwIBAgIBATANBgkqhkiG9w0BAQsFADAzMQswCQYDVQQGEwJGUjEkMCIGA1UECgwb +RlItRElOVU0tZUlEQVMtbm9kZS1wcmVwcm9kMB4XDTIyMDkyMjE0NDgwNloXDTMyMDkxOTE0NDgw +NlowTDELMAkGA1UEBhMCRlIxJDAiBgNVBAoMG0ZSLURJTlVNLWVJREFTLW5vZGUtcHJlcHJvZDEX +MBUGA1UEAwwOZUlEQVMtbWV0YWRhdGEwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDO +B8ffvjFTk3rBzcyBiwFFMDlYPxIlj+i1BLdLvvzSJdUHKZhegOhzNuRVCeJfoyvml9vtlwK5tfzD +iY4znZsFO/qIB1wROxrPVRq8AEw0LiPCfP1Ie1rvK2Ddaw0wUEI6wFn4ViAStP5wI3/yaOqN1cFf +JceXUbvgVfjRS5ETRVZK7UER5vMeMIPn4ESkf86d+GB0rvZoipNOyymXfgs9RU/dvjd40lrRs5rZ +Nc/l4dOrFUpxHXq/AfLsWKjmmOGx959qMmYtsK9KcQdEAQs2L/adKM+yLJL0JyHxyNnWuaUJDBwW +xV5PK/hWnkLkebkgpeB5loF7f7Ra2MnB1uzZlaAa69tSILjycdw0cOcY5+hnH6QFq74JDL4WPDR8 +FzCdcN+TY7/HvG6cTR4lPdW/iY3eZQycQqkEccQiFITAeewwVwZbQddbWhHxdtJE8P73QoZl3iSp +/TzP33Kr3im+IEX1o2PV6Ur36/cDNmc9WQWsRKTeydoIY+AOmV/odt0CAwEAAaMPMA0wCwYDVR0P +BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4IBgQB2FpL/Xen8z318X2To+3nEdRthc/9OwPF9ZNzftcce +QP4Q+lxuwKFacklt5VoXURy/JMsgPtSHhar5kiFZTm2SMXVSEqVDj4UzAobVnUutiZPsFoyPWr/c +iEsWu0VSthsI31AUOvSTisy0w81rPKjNkRuCU5V+AS1Z5rBqMlkwOEiUxUMci+pZQ4VhH+mqg1oH +1rMuJ1gysYf/zMjSWGlraSbcHApIMBAjs24uSTH/O6io+9k9jLhZGZv9LpssoeSPVz3wJY6h1LHT +D5OHZYDtelO4X5ZCVRFx5kCaUpKtb4mvo72oWN9KlGvrqp6SkUF1ogovTl16sEf2FZHoN4r0+5ZN +/vS/plTsCGbe46QPGuj+FO5yN6KclM+eonmBp0JSyoABfYN1T28a4dRwE8CPWCOgJmzxuwqSOH+P +XlUbPftbx5nGsE3mRBAU1Jga2S4S+zJPHirc2lfZGh6ggdYG9kkqp7X7kiTyPyPuWU644+YaCOwv +x7804cuLUcQ8VSQ= + + + + diff --git a/src/pyff/test/data/eidas/countries/GR.xml b/src/pyff/test/data/eidas/countries/GR.xml new file mode 100644 index 00000000..a8e1626e --- /dev/null +++ b/src/pyff/test/data/eidas/countries/GR.xml @@ -0,0 +1,63 @@ + + + + + + + MIIE5zCCA0+gAwIBAgIULMraEI5oF6p3T3LH0x/21gVFOvcwDQYJKoZIhvcNAQELBQAwgYkxCzAJ +BgNVBAYTAkdSMScwJQYDVQQKDB5NaW5pc3RyeSBvZiBEaWdpdGFsIEdvdmVybmFuY2UxPTA7BgNV +BAsMNGVJREFTIE5vZGUgTWV0YWRhdGEgU2lnbmluZyB0ZXN0aW5nIGVudmlyb25tZW50IDIwMjMx +EjAQBgNVBAMMCUNvbm5lY3RvcjAeFw0yMzA5MjAxODIxMDlaFw0zMzA5MTcxODIxMDlaMIGJMQsw +CQYDVQQGEwJHUjEnMCUGA1UECgweTWluaXN0cnkgb2YgRGlnaXRhbCBHb3Zlcm5hbmNlMT0wOwYD +VQQLDDRlSURBUyBOb2RlIE1ldGFkYXRhIFNpZ25pbmcgdGVzdGluZyBlbnZpcm9ubWVudCAyMDIz +MRIwEAYDVQQDDAlDb25uZWN0b3IwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQC0PU5P +pibubglyAQJDW0Q+iWiKXnfoBV6K5yFvzzE91UT/lE2UvX3haqqj8PwsfsJsPG7M805ky2UPY5nO +U7VcnaQ3MWzz35kjNI1fvlXkR06YEkhBkr64WJ/7JTxf0wLO83ZRqBcvMqlDCqf8bPHXdjar89sS +e866Wd2rthi7Tu6Ah6buF96lXpS2d9vwnf1S9mhOmtykQ53Vs5zgqMVOaHfKwCThefoYOCyzuqNP +S1G0dQaRbXkIDpbniT96aap8Ksf0/Yx5E+o7BnvbnEsCJPHTYudTKqN7ljD8+Q4M2UNpMT05qIR7 +Zd2KfpufEV8o5YJzMCIftZH6sndBYbpDYkqM+Cd6qy84HBy2/UWgZDt2iQ1eML/Szk59wSm21szd +Q4mshSv4rTwHuTtHg+ZeiCzwJGgvnQen3WR+TGm0YyHijfI9DqIvzbM8yXF8Abp00l3sA3Grm7wo +E0YW+EbkiiJGUFrs88+P+cpcXeNkLltXSvhVosgF0JLcgoLl62UCAwEAAaNFMEMwEgYDVR0TAQH/ +BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFDu6OnhYjoq3nx9jmT8vwQ5tjFWD +MA0GCSqGSIb3DQEBCwUAA4IBgQARw/klO118DF5BNunXqsiExeQSQqb+n/qcBXDYLypQJomO/JAs +VEg4FEn05Z26QxyacwWxVqWhInVLFVhXgBJ5vnRTwK7RUkJCDQ+9u00oHd3JeOvygFjc7DoC5SWJ +q9hnN+pX1qK9yI+R9hs7cKPKpQ+MaNPWl0yVQCE53GZrWk+Skgl2T5/s0rY+LTYUx5d21kxq1tKE +cpzy9di+33w05uv7ozh7xcbL91wj7zbdDSibxpXmFdlY6C8BH4DOI0kRoYjWKpbuQnPrEbhQwZjI +V9S5OcEaPyiYkVB9n4Z0z3AMTD5G4X52gKmdSh/iTuZuYP4Vj1hbgNYpMHI7B5fnXisa26e2DzT4 +OFEots8gDlsf26WBEHQcJnrNzqCPTd4Zyl5Jpzg+Vo/dwknIESuZYn2l+Iu1GJCMIV+RrI/LoPD4 +FRGrw9YbcFOqAgmSdqxRj6fSb2W5WanIvc7OAT0hKQjPu1jYHDGIeXipKf1rBLjpRF/xtzU/xb4m +JDmr+yA= + + + + + + + + MIIE7TCCA1WgAwIBAgIUTo2pg82nNFjKFzNyHnCbQu1rEVYwDQYJKoZIhvcNAQELBQAwgYwxCzAJ +BgNVBAYTAkdSMScwJQYDVQQKDB5NaW5pc3RyeSBvZiBEaWdpdGFsIEdvdmVybmFuY2UxPTA7BgNV +BAsMNGVJREFTIE5vZGUgTWV0YWRhdGEgU2lnbmluZyB0ZXN0aW5nIGVudmlyb25tZW50IDIwMjMx +FTATBgNVBAMMDFByb3h5U2VydmljZTAeFw0yMzA5MjAxODM5MDZaFw0zMzA5MTcxODM5MDZaMIGM +MQswCQYDVQQGEwJHUjEnMCUGA1UECgweTWluaXN0cnkgb2YgRGlnaXRhbCBHb3Zlcm5hbmNlMT0w +OwYDVQQLDDRlSURBUyBOb2RlIE1ldGFkYXRhIFNpZ25pbmcgdGVzdGluZyBlbnZpcm9ubWVudCAy +MDIzMRUwEwYDVQQDDAxQcm94eVNlcnZpY2UwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIB +gQCqNGRL80G3XrF5wgtJIOCglAg8361zga2Hup8G+w6eQdP5bUyX9JZskTr9IUNeajUw7sRGN9GC +iflAILmzHqySnspOZZer4bUaOEKhRE0RZnhWoCyeZgdp1j9bwe2uLgRJtLQWpeGq3kEzCoSqul70 +iLegKd85f8i0S5ZgzdpjSBJcetGwQxV8bw1+3IT4/OXrL467Z2tBvPE1AClf0ETkw9y5vfI1O+Vr +OHJg8ywIKUgLdfgpCpjHGzljOVlA1ZbCplPvKOOkjRKx7BWAFwFqUbSLYjVDrhIHkCv+EsGeHLK4 +aLmxAVVpwj5qhlnxJ7vweKUEorw7GUGHhAmiM9bem5Wc3jakt06Hd8vA6/kn7Yr17feZtaBfWHAP +HoH0nvGLHk6+WU3W3/i89KLnYH+JsGfY8vSOQesaavmZy6WTEmXk6AkDrUocdC0IrMhq8duOiN3u +KrzN9t0vaRb6KMr35x7l9Sq6hxyiIjfZW+qB8434HmxKyZODCcXEncpNK8MCAwEAAaNFMEMwEgYD +VR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFM6pVXDExVEGIVCJbVKV +/S9m7zFtMA0GCSqGSIb3DQEBCwUAA4IBgQCmADqhrXNb4iOsSOkHBghndhsT/lowKPtCicLrwfmm +twQtwA/H1JILe11r97LxIuDmFi0wdSIwx+E0ioA3BF4kcddXrYDimQD2LDQZIWFYHaMhAII89Kfv +VXnn9Ox048cJHsKrteMchCOKw1xxZYsOKr1dFPiQQOX+mP/S1aBnj+7Sr4GuTSkWXC4OiO+BaZQW +4mWe83DsngdUjHmCDrvnBT3xzUbJx5hEky2lnU1ZFZtusH6v6VElQ1KPgDBov1dTktB+r44v47DO +WZ7GUkwvoOVS0lpl7nCpD9QBBch0JmAyPI9RvVRD5vaPghD+Jv5JEiQtMJwcE2l1GYLcwo5Q1BOu +FsLJQEEmD7PUm0ZisV7GTQbRqh2YcLSHMrXOMvbvBlNdEuTDrdMvKFK/FMGh+4DHh/J3+hklGw+e +X+U++5GGUbOS6nZtDwmW/KBYMSnanWyWJ1cnhos7A2JkL429B6uhZsPIHAQn26LBz8yGx3vxze8D +wBm+w1VVhk19CHc= + + + + diff --git a/src/pyff/test/data/eidas/eidas.xml b/src/pyff/test/data/eidas/eidas.xml new file mode 100644 index 00000000..f9d28c92 --- /dev/null +++ b/src/pyff/test/data/eidas/eidas.xml @@ -0,0 +1,18 @@ + + + + Swedish E-Identification Board + urn:se:elegnamnden:eidas:mdlist:local + SE + + + + + + https://qa.md.eidas.swedenconnect.se/mdservicelist-aggregate.xml + + diff --git a/src/pyff/test/test_mdsl.py b/src/pyff/test/test_mdsl.py new file mode 100644 index 00000000..3ec44d75 --- /dev/null +++ b/src/pyff/test/test_mdsl.py @@ -0,0 +1,124 @@ +import json +import os +import shutil +import sys +import tempfile + +import pytest +import six +import yaml +from mako.lookup import TemplateLookup +from mock import patch + +from pyff import builtins +from pyff.exceptions import MetadataException +from pyff.parse import ParserException +from pyff.pipes import PipeException, Plumbing, plumbing +from pyff.repo import MDRepository +from pyff.resource import ResourceException +from pyff.test import ExitException, SignerTestCase +from pyff.utils import hash_id, parse_xml, resource_filename, root, dumptree +from pyff.constants import NS + + +__author__ = 'leifj' + +# The 'builtins' import appears unused to static analysers, ensure it isn't removed +assert builtins is not None + + +class PipeLineTest(SignerTestCase): + @pytest.fixture(autouse=True) + def _capsys(self, capsys): + self._capsys = capsys + + @property + def captured_stdout(self) -> str: + """ Return anything written to STDOUT during this test """ + out, _err = self._capsys.readouterr() # type: ignore + return out + + @property + def captured_stderr(self) -> str: + """ Return anything written to STDERR during this test """ + _out, err = self._capsys.readouterr() # type: ignore + return err + + @pytest.fixture(autouse=True) + def _caplog(self, caplog): + """ Return anything written to the logging system during this test """ + self._caplog = caplog + + @property + def captured_log_text(self) -> str: + return self._caplog.text # type: ignore + + def run_pipeline(self, pl_name, ctx=None, md=None): + if ctx is None: + ctx = dict() + + if md is None: + md = MDRepository() + + templates = TemplateLookup(directories=[os.path.join(self.datadir, 'simple-pipeline')]) + pipeline = tempfile.NamedTemporaryFile('w').name + template = templates.get_template(pl_name) + with open(pipeline, "w") as fd: + fd.write(template.render(ctx=ctx)) + res = plumbing(pipeline).process(md, state={'batch': True, 'stats': {}}) + os.unlink(pipeline) + return res, md, ctx + + def exec_pipeline(self, pstr): + md = MDRepository() + p = yaml.safe_load(six.StringIO(pstr)) + print("\n{}".format(yaml.dump(p))) + pl = Plumbing(p, pid="test") + res = pl.process(md, state={'batch': True, 'stats': {}}) + return res, md + + @classmethod + def setUpClass(cls): + SignerTestCase.setUpClass() + + def setUp(self): + SignerTestCase.setUpClass() + self.templates = TemplateLookup(directories=[os.path.join(self.datadir, 'simple-pipeline')]) + + +class ParseTest(PipeLineTest): + def test_eidas_country(self): + tmpfile = tempfile.NamedTemporaryFile('w').name + try: + self.exec_pipeline(f""" +- when eidas: + - xslt: + stylesheet: eidas-cleanup.xsl + - break + +- load: + - file://{self.datadir}/eidas/eidas.xml cleanup eidas +- select +- publish: {tmpfile} +""" + ) + xml = parse_xml(tmpfile) + assert xml is not None + entityID = "https://pre.eidas.gov.gr/EidasNode/ServiceMetadata" + with_hide_from_discovery = xml.find("{%s}EntityDescriptor[@entityID='%s']" % (NS['md'], entityID)) + assert with_hide_from_discovery is not None + search = "{%s}Extensions/{%s}EntityAttributes/{%s}Attribute[@Name='%s']" % (NS['md'], NS['mdattr'], NS['saml'],'http://macedir.org/entity-category') + ecs = with_hide_from_discovery.find(search) + assert ecs is not None + entityID2 = "https://eidas.pp.dev-franceconnect.fr/EidasNode/ServiceMetadata" + without_hide_from_discovery = xml.find("{%s}EntityDescriptor[@entityID='%s']" % (NS['md'], entityID2)) + ecs2 = without_hide_from_discovery.find(search) + assert ecs2 is None + except IOError: + pass + finally: + try: + #os.unlink(tmpfile) + pass + except (IOError, OSError): + pass