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()