diff --git a/src/pyff/builtins.py b/src/pyff/builtins.py index a3a69ae2..89b623fb 100644 --- a/src/pyff/builtins.py +++ b/src/pyff/builtins.py @@ -552,10 +552,6 @@ def _nop(x): out = output_file data = req.t - # clean unused namespaces - the working document isn't always XML (eg discojson* produce JSON) - if isinstance(data, (etree._Element, etree._ElementTree)): - etree.cleanup_namespaces(data) - if not req.args.get('raw'): data = dumptree(req.t, pretty_print=req.args.get('pretty_print')) diff --git a/src/pyff/samlmd.py b/src/pyff/samlmd.py index b23f41b4..093e8ee3 100644 --- a/src/pyff/samlmd.py +++ b/src/pyff/samlmd.py @@ -1,4 +1,5 @@ import json +import re import traceback from base64 import b64decode from copy import deepcopy @@ -438,6 +439,35 @@ def _resolve(m, l_fn): return resolved_entities +# A QName is NCName ":" NCName. Requiring a valid NCName local part rejects the +# far more common URI-shaped attribute values ("https://..." has a '/' local part). +_QNAME_RE = re.compile(r'^[A-Za-z_][\w.-]*:[A-Za-z_][\w.-]*$') + + +def qname_prefixes(t): + """Namespace prefixes referenced from inside QName-valued attributes. + + lxml.etree.cleanup_namespaces only considers prefixes used in element and + attribute *names*, so a prefix appearing solely in an attribute *value* - + xsi:type="xsd:string" being the common case - looks unused and gets removed, + leaving a dangling QName that fails schema validation. + """ + keep = set() + match = _QNAME_RE.match + for elt in t.iter(): + values = elt.values() + if not values: + continue + for value in values: + if match(value): + prefix = value.split(':', 1)[0] + # Only keep prefixes actually bound here; anything else is either + # not a QName or already-broken metadata. + if prefix not in keep and prefix in elt.nsmap: + keep.add(prefix) + return keep + + def entitiesdescriptor( entities, name, @@ -488,6 +518,12 @@ def entitiesdescriptor( ent_insert = deepcopy(ent_insert) t.append(ent_insert) + # Clean unused namespaces. Prefixes referenced only from QName-valued attributes + # (xsi:type="xsd:string") are invisible to lxml's usage tracking, so collect and + # preserve them explicitly - see issue #333. + keep_ns_prefixes = sorted({'xs', 'xsi'} | qname_prefixes(t)) + etree.cleanup_namespaces(t, keep_ns_prefixes=keep_ns_prefixes) + if config.devel_write_xml_to_file: import os diff --git a/src/pyff/test/data/metadata/test05-xsd-prefix-idp.xml b/src/pyff/test/data/metadata/test05-xsd-prefix-idp.xml new file mode 100644 index 00000000..20f35e9f --- /dev/null +++ b/src/pyff/test/data/metadata/test05-xsd-prefix-idp.xml @@ -0,0 +1,57 @@ + + + + + + http://id.elegnamnden.se/ec/1.0/loa3-pnr + http://id.elegnamnden.se/st/1.0/public-sector-sp + http://id.elegnamnden.se/ec/1.0/eidas-naturalperson + + + + + + + Kronofogdens e-tjänster + Swedish Enforcement Authority + Med hjälp av våra e-tjänster kan du utföra ärenden då det passar dig bäst. Om du har en personlig e-legitimation med personnummer kan du, dygnet runt, logga in och lämna uppgifter till oss. + With the help of our e-services, you can perform tasks whenever it suits you best. If you have an e-ID with personal identity number (personnummer), you can 24 hours 365 days provide information to us. + https://www.kronofogden.se/images/18.4c1b677f134cb6b828f80003907/1371144387972/logotyp-header.png + + + + + + MIIEljCCAn6gAwIBAgIUUJYcgA6FTGauUb/a7H6GsZ5SyzkwDQYJKoZIhvcNAQELBQAwSDELMAkGA1UEBhMCU0UxFzAVBgNVBAoTDkNHSSBTdmVyaWdlIEFCMSAwHgYDVQQDExdjYS5mdW5rdGlvbnN0amFuc3Rlci5zZTAeFw0yNTAzMDMxMzM0MDBaFw0zMzEyMzEyMzU5MDBaMF4xIjAgBgNVBAMTGWF1dGguZnVua3Rpb25zdGphbnN0ZXIuc2UxFzAVBgNVBAoTDkNHSSBTdmVyaWdlIEFCMQswCQYDVQQGEwJTRTESMBAGA1UECBMJU3RvY2tob2xtMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz/rj9iRa8JOJBXRpH+4tRlwb5xZdy/aot1k6+QAmWvhSuDByMV/S0eijqfuGENq4dKnZ95pI1C074WqkN94q35HbdT+5C8kxe+ydUeVINAvpQVvRGbo0o1rPVRRdbhkj/T/KmU0Xy9gKd1KxFOv69hBe35+0hB0yVzckO6c3vGnIyIgTVbMoxz8gZ98btEyx0Z/m7J2FE7Cg2xfwrxQVEiwUQr9jeMkH5axN6aCxj0Ch14IDLl8NXhzKvJD5KUH2nt5K7tRzdDAv9GnWjRy2INxqYx3QC9AoQcpE0Sc5bhjSYY9skVJCkCD9YtUNTrqYswLbXTJQ1idbmGByQOy5wwIDAQABo2IwYDAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTxC/j/FcdqYpNS/+QIEERlqocadDALBgNVHQ8EBAMCA+gwJAYDVR0RBB0wG4IZYXV0aC5mdW5rdGlvbnN0amFuc3Rlci5zZTANBgkqhkiG9w0BAQsFAAOCAgEAsWs7B6Z8uSXPTz388OBG+OLo3vBCq5FOdow8IHzBpwbah+Ip/QLlO7q85o5uPSNglna5o5nS2D3LOfUsAhoPKYpA8vOTepEWfTLPVc6ZkyARbR+kqJlWpINjlKHlzp49YjvZhHaMsVae+8BfU1F+YIK0ArN4cBEZKFp7NafYoAb99YzdmCUyEdFRtgujAiBP8SiXLYiy04BqG8oh23OUBcOTV4KHmR9+VBMtoG31768nYzqqZTfGaXWzAsJzSOdSnmhAdRbm78oP25+lM80ZfzH0b+VqEIibCg5F8O9RYZ4aD06497M1J3Mt36nehZbAUFFCWgxOS10n6RMaZxyRtzGpVsZ5EgfFSQqM0k9IqAlm9qiHUIeg4dRI0qNY7NfeaJs8ucsHRDotGUkuJpeJpLjsSMud0eOCLKu5GUg68KOc/lA6FxdxikshA5CeB2Ull/vLk/ONXLZuD0JXhFfoqnUSQfc5zHZCrPf+H9e1LmKYpg3b82jNmHef8uhudg3DV0F0Oxe5mRrr9G0Im9dbK2nOPOw8Min0y7IsmKx+wsNhc5KSKVvGVIdPUCIOOzWmFT39WbKfXeQ3wPbEfk0WMshl69DcScHE6QipVW1e10kzgCxvXx19/yvAN0Gbf/6QAOUdIyp78I4WijMWwU4cWCbghaG0agK754M7ut2Hwnw= + + + + + + + + + MIIErDCCApSgAwIBAgIUThRHrydX8KwttHiKNPlrtADt9dAwDQYJKoZIhvcNAQELBQAwSDELMAkGA1UEBhMCU0UxFzAVBgNVBAoTDkNHSSBTdmVyaWdlIEFCMSAwHgYDVQQDExdjYS5mdW5rdGlvbnN0amFuc3Rlci5zZTAeFw0yNTA0MDcwODAwMDBaFw0zMzEyMzEyMzU5MDBaMGkxLTArBgNVBAMTJGVuY3J5cHRpb24uYXV0aC5mdW5rdGlvbnN0amFuc3Rlci5zZTEXMBUGA1UEChMOQ0dJIFN2ZXJpZ2UgQUIxCzAJBgNVBAYTAlNFMRIwEAYDVQQIEwlTdG9ja2hvbG0wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDb+xAFrd+QR7/1cfiqPcLaFqRQVsnPgESQMYkOYbN1It9D18LldPYuSFL3KubWKYYbJ67nR5mS4ACDiwp0CAaVJU2hDO7mxfcblGFmEvd2UpwWIG44vpbEqHVCCuAccU62jj9bW9QL09urWRfy6J8nXtzxpgsrRVbujzj8mAFCXi/waWQu7N3cQQV4cVuBvJUy9RVgZ15gf8uty+cFhmloZYOGnGkPctkG8aC+n5g+OQx36Cs8IGbl6LRbvSlBAyiUCEeTY0erB+FIlkddyWtDVb/pok2RWxJRBm2VmKePKpB2ykcuvYOl4jn2efSlOkU8gqKF1+FH87SvO2WvaI4XAgMBAAGjbTBrMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFHftF0eMUzkskpcB/VKCZPURDDuBMAsGA1UdDwQEAwID6DAvBgNVHREEKDAmgiRlbmNyeXB0aW9uLmF1dGguZnVua3Rpb25zdGphbnN0ZXIuc2UwDQYJKoZIhvcNAQELBQADggIBAF1oFWsnrs69LTsMgcIXlBQClt/xlinWNeSytvXQhQYCgp6h1euLRLeTt4fY/jLTO4BMHppaQ5FPcVf8Se16+LJcxMoxUGeIjbjG8eVKrT1P+ks4fZVmwMevN1z0LMCAZO/N43rLBvBakv9L7wd8atCIRUEKYP8WlyMrb5hC7MD8wdGsBd6GYBGBmdkVdVwJEvmyi5MZnWsHTxJ/DH+vCbHP3kIzZ+6CuB2oGiy5ZIScCzkeMAOzRPoqx+3PETYs2uIr71ibd3AlRRbx/gO1aEze1Lt0qN5J97/l4qi34mrSJHBlbydmepZkZAA9q7lx2hQnxhRS45T6PavzVL5f6tlp4iR99Tqb9SaLOHx5l/q23TwOttRc8rZA3LZZ7kqjq+inQ8y1KHaqgsKZFuaAEzBnJslEfQvJt3/oOzLYPzfqyamBBv+yYwrTVKzZMXurlbjeP8r+ZIDbZsHNa2wJKXpBmcfzhyJ4n66M58jh+KmRPYu2KlKA4r1eiZ/vKYXGrPCsiuY/RItTlNlagyy3+yFJDQO/5MXfL9tuoGI6GkYyZJyc585U5IjGERqcCpNegLgCHSKK29HajafOz+zPeh5Sk2qGlOcG6uehRrFPg4cGDdSqY6WEHq0ZGvYjH0GjMbStgw52KCEoWiRgsqZDhoPgsSpghntXxV+brmGK1eDE + + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + Kronofogden + Swedish Enforcement Authority + Kronofogden + Swedish Enforcement Authority + https://www.kronofogden.se/ + + + digitalatjanster.support@kronofogden.se + + + digitalatjanster.support@kronofogden.se + + diff --git a/src/pyff/test/data/metadata/test06-xs-and-xsd-declared.xml b/src/pyff/test/data/metadata/test06-xs-and-xsd-declared.xml new file mode 100644 index 00000000..17fa8417 --- /dev/null +++ b/src/pyff/test/data/metadata/test06-xs-and-xsd-declared.xml @@ -0,0 +1,145 @@ + + + + + + http://id.elegnamnden.se/st/1.0/sigservice + http://id.elegnamnden.se/st/1.0/public-sector-sp + http://id.swedenconnect.se/contract/sc/sweden-connect + http://id.swedenconnect.se/contract/sc/eid-authorization-system + http://id.swedenconnect.se/contract/sc/prepaid-auth-2021 + http://id.elegnamnden.se/ec/1.0/loa3-pnr + http://id.swedenconnect.se/ec/sc/uncertified-loa3-pnr + http://id.elegnamnden.se/ec/1.0/eidas-naturalperson + + + + + + + Kungliga biblioteket + National Library of Sweden + Kungliga bibliotekets e-tjänst för avancerade elektroniska underskrifter. + The National Library of Sweden's e-service for advanced electronic signatures. + https://www.kb.se/images/18.526c3ba718ee55a70f732e/1713273272431/KB-logo-text-black.svg + + + + + + + MIIGszCCBGegAwIBAgITZgAAAFw0umTVZIhT9gAAAAAAXDBBBgkqhkiG9w0BAQow + NKAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUA + ogMCASAwdzELMAkGA1UEBhMCU0UxEzARBgNVBGEMCjU1NjM0MjY2NjYxHTAbBgNV + BAsMFENlcnRpZmljYXRlIFNlcnZpY2VzMRMwEQYDVQQKDApDb21mYWN0IEFCMR8w + HQYDVQQDDBZDb21mYWN0IFNlcnZpY2VzIENBIEcxMB4XDTI0MTAwMjA2NDI0NFoX + DTI2MTAwMjA2NDI0NFowdzELMAkGA1UEBhMCU0UxEzARBgNVBGEMCjU1NjM0MjY2 + NjYxHTAbBgNVBAsMFENlcnRpZmljYXRlIFNlcnZpY2VzMRMwEQYDVQQKDApDb21m + YWN0IEFCMR8wHQYDVQQDDBZDb21mYWN0IERTUyBLQiBTaWduaW5nMIIBojANBgkq + hkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAveGOHhhLDVEIgtiVd5EofmauucDu5YwR + x2d4H4mOFfNqfaMvCkgEytVN/MjLebnZb6J/Iav7u47Mf0+ePGKvWd+QDvE83Nvp + 1qpPtLfbcF75U+TVKM/1qOe11UzfcW9Q3mqYBr9mWM1yPHYHmOhsWESf4WGd9wFC + 80vfQvsHLEW6vbwJKgzj7fGZPiW8hzQm8SJPHRFR9+j/2IgsHQPBuURlKXflp4Yi + fyCC0hxP38vlnu5VqRRMMZBrVFHwmBKzh6ah+X1aUQdFQr2pb+cEAMAwCMNmhgln + hknudlEPx2OANHrKULgQu+2zPLwHx64vs3yHn5s2+tbdiiJT85h/WJvsrsv4F76Q + oiTzHR/VXqkviysJqKqhq1+Cua6/UQDwsgAW4lQhOlR9BM/hxH9meBxY6ELwZcYP + iQT37XAo9CcjHQzuqIb2o7yby4w1WMDVrXVOdHa+nxxfvrNIzUzJ0VUEVxozk/bE + HIHWksVra/VWxDnrNWU/9iBb4jIyJGC1AgMBAAGjggFOMIIBSjAMBgNVHRMBAf8E + AjAAMA4GA1UdDwEB/wQEAwIHgDBFBgNVHSAEPjA8MDoGByqFcIF9CAMwLzAtBggr + BgEFBQcCARYhaHR0cHM6Ly93d3cuY29tZmFjdC5zZS9yZXBvc2l0b3J5MB0GA1Ud + DgQWBBQelGRvv8HtE6iJEZWoY7/cwAutMjAfBgNVHSMEGDAWgBShC3TWdGLMTBE/ + i2xnamODhnAEijBHBgNVHR8EQDA+MDygOqA4hjZodHRwOi8vcGtpLmNvbWZhY3Qu + Y29tL2NybHMvY29tZmFjdC1zZXJ2aWNlcy1jYS1nMS5jcmwwWgYIKwYBBQUHAQEE + TjBMMEoGCCsGAQUFBzAChj5odHRwOi8vcGtpLmNvbWZhY3QuY29tL2NlcnRpZmlj + YXRlcy9jb21mYWN0LXNlcnZpY2VzLWNhLWcxLmNlcjBBBgkqhkiG9w0BAQowNKAP + MA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMC + ASADggIBADv8b6tHD1tyNx9uY21tPwZ2ugKSupI2B5GzPr6z0gsY5iHoIBRh/WbF + Qthg2GUebqYIdj6v6RqwBG+LeQhHxy6DkJMTcY8dLSC40plSx+8s3rBFsAsxqNeu + fVuFb3fW9LO6+RzWPXeFP84ORVhV+vA3Dx+mpqPhaiL7kLgR3RH0FR6OzN6IE3GX + xDROhvc3wQjUhljK3tWi3duta7CTIDPPr2HKobuzk5orKz0b565CCP3yyVe6HgRK + vmN+cxA41LzVcd/WymndFS1W4SBgOR45B8XTEhgk8PFGypfy8SwM6sO2s5sUjS4g + 5O40649K9okXNdHAovctxYU6Qq5L4EvgSlyQWVPstDglE48ltbRAzLAHUVcCveZ5 + D6mRtmexoK6u6irCEUkq3CjH59oYK5RhaPNmMd0Ypma8j51JkyjmdkKwHbE+TZ8D + TosIyIpVE7crq8uW/WPaGVE7LsAupkfsfdBnqedsZ11/MYTbuNwuim0DGANXXVNY + swHUcVthvHmpmKTzpUYK+t4h3IV6zMRAfLbSFW89UbyGWdg0vqE6+3b7neyUoxyy + DhwpvVydDi1ws4rA8ecmGhktjPybZxOLyU4p8bwZ9WvLW3jiWDNNhPZ27JNpfJPa + p/or5jUu3rw/NaDrZ5rHhZQpipllkcbHT8xadMBQw9mInh8qFGe+ + + + + + + + + + MIIGtjCCBGqgAwIBAgITZgAAAFvnJQy/mieHIQAAAAAAWzBBBgkqhkiG9w0BAQow + NKAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUA + ogMCASAwdzELMAkGA1UEBhMCU0UxEzARBgNVBGEMCjU1NjM0MjY2NjYxHTAbBgNV + BAsMFENlcnRpZmljYXRlIFNlcnZpY2VzMRMwEQYDVQQKDApDb21mYWN0IEFCMR8w + HQYDVQQDDBZDb21mYWN0IFNlcnZpY2VzIENBIEcxMB4XDTI0MTAwMjA2NDIyNloX + DTI2MTAwMjA2NDIyNlowejELMAkGA1UEBhMCU0UxEzARBgNVBGEMCjU1NjM0MjY2 + NjYxHTAbBgNVBAsMFENlcnRpZmljYXRlIFNlcnZpY2VzMRMwEQYDVQQKDApDb21m + YWN0IEFCMSIwIAYDVQQDDBlDb21mYWN0IERTUyBLQiBFbmNyeXB0aW9uMIIBojAN + BgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEArY/Dc1ZvM73VjHgvm3pgICb5wClM + h7e8J2l/gXeZhQ/vuO7CornScy3ny2jBzi7OMp9L+OxfCk03v4+Vf0LLfy3BBPLZ + WOFw+BThEfUb7GmNH0JxrcTp+NKI4mUKKbMrEZel5Rsb9L7LAERA8gEG+6MJwssv + 3QUsvWk2a6jQdKnERDsmbAw4/BEy5F/PLTScMs6hBV0T9yvg6eCgRmYiNvmLATaM + AQXb6qeC0uKtdsfSugxfAflmfwJZUa6vdmf83CDuhyVbXsxlisJcedkPM/aWcvNo + 0q6BmpxYr4q2ZAMpzPVqEkKq1EWREAX902YE8qnVyZoDtdLsfEN0OLzdn9dpZ3Si + 6RTuc1zpAffznEj3aO5NxpHfFgCvDguy48tSNue7+cuR7y7pmd7Ev6FDoTuhst4I + 82O9kZV2rDGqeb6sh9Kljo1TWfiZ49CnCpzbndVnwhGNPj3yFMw/L2F3iLoV+pCl + xKec+47n5AVYEZoRlMGPG/FxW90C2oQ9RWu9AgMBAAGjggFOMIIBSjAMBgNVHRMB + Af8EAjAAMA4GA1UdDwEB/wQEAwIFIDBFBgNVHSAEPjA8MDoGByqFcIF9CAMwLzAt + BggrBgEFBQcCARYhaHR0cHM6Ly93d3cuY29tZmFjdC5zZS9yZXBvc2l0b3J5MB0G + A1UdDgQWBBSRMQwi0YoN3sZWU/VvXEqPHTp7ODAfBgNVHSMEGDAWgBShC3TWdGLM + TBE/i2xnamODhnAEijBHBgNVHR8EQDA+MDygOqA4hjZodHRwOi8vcGtpLmNvbWZh + Y3QuY29tL2NybHMvY29tZmFjdC1zZXJ2aWNlcy1jYS1nMS5jcmwwWgYIKwYBBQUH + AQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vcGtpLmNvbWZhY3QuY29tL2NlcnRp + ZmljYXRlcy9jb21mYWN0LXNlcnZpY2VzLWNhLWcxLmNlcjBBBgkqhkiG9w0BAQow + NKAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUA + ogMCASADggIBAA44+BFE/8tWF/AoC4HlaxT5gwzbQiuBEvRd3TKWIIZnQPSRbr2I + bwkA8jVa3VfA8rSvOMdyuRQvKrp/EMjI6FwFgtRfKYXI7npnHTo9WUj+BfU8F3fw + 7s15bZ4TKhzo40Gqtksrf5xJ6FwgiysF/GdFS5oC6T3jx1oU4A1gYirdIn7Ahl8G + v99V0F1zm2iS9oGhH/0aOpjjF4kT2Li4iGttp5gXpCRei3szNPo91VFDgv8ZKac4 + RgYNk2EHn80Tb1OicPtkuaKw0qw5QpdmXz0nl+v+0NmdA7P9erhGJGhEv7VDsm1f + n8jn27h3g23Ab074+HS5vt4Rpkb4xaWJMvDLHSRCkwhF5RS7cQnSSHmEgItKhq78 + lRa9EuN6u0zyzuOHy9FNK6LCq+rwc09mhy0waJBpkBVPMf5CKnn39Qi+ZtJWViTW + Xck2YbOV2yVqplWHhhkRltZc0ZfNJpqNLrbhgziGOf9FPHl20YMhHqWkE5ubiFyo + MBGvVfA19aqpRC5eBXoJoiVnxo6d26jZ0k0Z77SXget6DoJMvRFoTEzTQfClp6+S + JW68TGQo63TMuMH78gZAs9oIPxynBBbb3rPzGJnlap5BvCEjbzE/d0sSmUBltrpz + sRUy5MiIGmSSNik2yYUFkqB1qbnzNMjgQinVUFs5Lmsv04IerXxJw5Dr + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + Kungliga biblioteket + National Library of Sweden + + + + + + + + 2021001710 + + Kungliga biblioteket + National Library of Sweden + Kungliga biblioteket + National Library of Sweden + https://www.kb.se + https://www.kb.se + + + Kungliga biblioteket + ITsupport@kb.se + + + Kungliga biblioteket + ITsupport@kb.se + + diff --git a/src/pyff/test/test_pipeline.py b/src/pyff/test/test_pipeline.py index 26b2a575..918322bb 100644 --- a/src/pyff/test/test_pipeline.py +++ b/src/pyff/test/test_pipeline.py @@ -10,6 +10,7 @@ from mako.lookup import TemplateLookup from pyff import builtins +from pyff.constants import NS from pyff.exceptions import MetadataException from pyff.parse import ParserException from pyff.pipes import PipeException, Plumbing, plumbing @@ -723,6 +724,130 @@ def test_parsecopy_(self): assert "Expected exception from bad namespace in" assert md.lookup(entity) + def test_publish_keeps_xs_xsi_namespace(self): + """cleanup_namespaces() must not strip prefixes only used inside attribute values. + + Regression test for https://github.com/IdentityPython/pyFF/issues/333: the 'xs' + prefix in swamid-2.0-test.xml is declared on an EntityDescriptor but only referenced + from within an xsi:type attribute *value* (e.g. ns3:type="xs:string"), so lxml's + cleanup_namespaces() considers it unused and removes it, producing invalid metadata. + """ + entity = 'https://xenosmilus2.umdc.umu.se:8086/coc/sp.xml' + tmpfile = tempfile.NamedTemporaryFile('w').name + try: + self.exec_pipeline( + f""" +- load: + - file://{self.datadir}/metadata/swamid-2.0-test.xml +- select +- publish: {tmpfile} +""" + ) + t2 = parse_xml(tmpfile) + assert t2 is not None + entity_elt = t2.find(".//{{{}}}EntityDescriptor[@entityID='{}']".format(NS['md'], entity)) + assert entity_elt is not None + assert entity_elt.nsmap.get('xs') == NS['xs'] + finally: + try: + os.unlink(tmpfile) + except OSError: + pass + + def test_publish_keeps_xsd_prefix_qname(self): + """The xs/xsi hardcoded keep-list is not enough: the XMLSchema namespace can be + spelled with any prefix a document author chooses. + + Regression test with real-world metadata: test05-xsd-prefix-idp.xml declares the + XMLSchema namespace as 'xsd' (not 'xs') and uses xsi:type="xsd:string". Before this + fix, cleanup_namespaces() stripped 'xmlns:xsd' (only 'xs'/'xsi' were hardcoded), + schema validation then failed on the dangling QName, and filter_invalid silently + dropped the entity from published metadata entirely. + """ + entity = ( + 'https://auth.funktionstjanster.se/id/proxy/saml2/67cacd06db3b47c951eefdfa/' + 'idp/67cacbb7ef0f342709eefdf6/c/682f0e0f8dc75a89157ed724/authn' + ) + tmpfile = tempfile.NamedTemporaryFile('w').name + try: + self.exec_pipeline( + f""" +- load: + - file://{self.datadir}/metadata/test05-xsd-prefix-idp.xml +- select +- publish: {tmpfile} +""" + ) + t2 = parse_xml(tmpfile) + assert t2 is not None + entity_elt = t2.find(".//{{{}}}EntityDescriptor[@entityID='{}']".format(NS['md'], entity)) + assert entity_elt is not None, "entity was dropped - namespace cleanup broke schema validation" + assert entity_elt.nsmap.get('xsd') == NS['xs'] + finally: + try: + os.unlink(tmpfile) + except OSError: + pass + + def test_publish_prunes_genuinely_unused_namespace(self): + """Guard against 'fixing' future namespace reports by weakening cleanup entirely. + + test06-xs-and-xsd-declared.xml declares both 'xs' and 'xsd' bound to the same + XMLSchema URI, but only 'xs' is ever referenced (via xsi:type="xs:string"). The + genuinely unused 'xsd' declaration must still be pruned. + """ + entity = 'https://app.comfact.se/pdss/metadata/d70a69a512a2418f' + tmpfile = tempfile.NamedTemporaryFile('w').name + try: + self.exec_pipeline( + f""" +- load: + - file://{self.datadir}/metadata/test06-xs-and-xsd-declared.xml +- select +- publish: {tmpfile} +""" + ) + t2 = parse_xml(tmpfile) + assert t2 is not None + entity_elt = t2.find(".//{{{}}}EntityDescriptor[@entityID='{}']".format(NS['md'], entity)) + assert entity_elt is not None + assert entity_elt.nsmap.get('xs') == NS['xs'] + assert 'xsd' not in entity_elt.nsmap + finally: + try: + os.unlink(tmpfile) + except OSError: + pass + + def test_wsfed_type_prefix_survives(self): + """A complex-type xsi:type prefix (as opposed to a datatype prefix like xs/xsd) + needs no special-casing: real WS-Fed metadata also carries elements in that + namespace (e.g. fed:TokenTypesOffered), so lxml already sees the prefix as used. + """ + entity = 'http://idp.chalmers.se/adfs/services/trust' + tmpfile = tempfile.NamedTemporaryFile('w').name + try: + self.exec_pipeline( + f""" +- load: + - file://{self.datadir}/metadata/swamid-2.0-test.xml +- select +- publish: {tmpfile} +""" + ) + t2 = parse_xml(tmpfile) + assert t2 is not None + entity_elt = t2.find(".//{{{}}}EntityDescriptor[@entityID='{}']".format(NS['md'], entity)) + assert entity_elt is not None + role_elt = entity_elt.find("{{{}}}RoleDescriptor".format(NS['md'])) + assert role_elt is not None + assert role_elt.nsmap.get('fed') == 'http://docs.oasis-open.org/wsfed/federation/200706' + finally: + try: + os.unlink(tmpfile) + except OSError: + pass + def test_discojson_sp(self): with patch.multiple("sys", exit=self.sys_exit): tmpdir = tempfile.mkdtemp()