Repository: italia/spid-sp-test (language: python)

Issue:
CIE Metadata Validation - Only one Municipality element MUST be present
Hi,
while testing the CIE profile for metadata validation, I get the error `Only one Municipality element MUST be present` when both Technical and Administrative contacts are present and both have a `Municipality` element, i.e.:

```
<md:ContactPerson contactType="administrative">
	<md:Extensions>
		<cie:Public xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions"/>
		<cie:IPACode xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">codiceipa</cie:IPACode>
		<cie:IPACategory xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">C1</cie:IPACategory>
		<cie:VATNumber xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">12345678901</cie:VATNumber>
		<cie:FiscalCode xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">12345678901</cie:FiscalCode>
		<cie:NACE2Code xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">12.34.56</cie:NACE2Code>
		<cie:NACE2Code xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">65.43.21</cie:NACE2Code>
		<cie:Municipality xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">Rome</cie:Municipality>
		<cie:Province xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">H501</cie:Province>
		<cie:Country xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">IT</cie:Country>
	</md:Extensions>
	<md:Company>ente</md:Company>
	<md:EmailAddress>mail@gov.it</md:EmailAddress>
</md:ContactPerson>
<md:ContactPerson contactType="technical">
	<md:Extensions>
		<cie:VATNumber xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">12345678901</cie:VATNumber>
		<cie:FiscalCode xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">12345678901</cie:FiscalCode>
		<cie:NACE2Code xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">01.02.03</cie:NACE2Code>
		<cie:NACE2Code xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">03.02.01</cie:NACE2Code>
		<cie:Municipality xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">Rome</cie:Municipality>
		<cie:Province xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">H501</cie:Province>
		<cie:Country xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">IT</cie:Country>
	</md:Extensions>
	<md:Company>ente</md:Company>
	<md:EmailAddress>mail@gov.it</md:EmailAddress>
</md:ContactPerson>
```


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index b34edff..0f5ee93 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,10 @@ Setup
 -----
 
 ````
-apt install libxml2-dev libxmlsec1-dev libxmlsec1-openssl xmlsec1 python3-pip
+apt install libxml2-dev libxmlsec1-dev libxmlsec1-openssl xmlsec1 python3-pip python3-virtualenv
+virtualenv -p python3 env
+source env/bin/activate
+
 pip install spid-sp-test --upgrade --no-cache
 ````
 
diff --git a/setup.py b/setup.py
index 4f9e2bc..88eedea 100644
--- a/setup.py
+++ b/setup.py
@@ -10,7 +10,7 @@ def readme():
 
 setup(
     name=_pkg_name,
-    version='0.9.8',
+    version='0.9.9',
     description="SAML2 SPID Service Provider validation tool that can be run from the command line",
     long_description=readme(),
     long_description_content_type='text/markdown',


Test patch:
diff --git a/src/spid_sp_test/constants.py b/src/spid_sp_test/constants.py
index 806da5e..c13f1c3 100644
--- a/src/spid_sp_test/constants.py
+++ b/src/spid_sp_test/constants.py
@@ -128,6 +128,13 @@
     "urn:oasis:names:tc:SAML:2.0:ac:classes:SpidL3",
 ]
 
+NOSESINDEX_ACRS = [
+    "https://www.spid.gov.it/SpidL2",
+    "https://www.spid.gov.it/SpidL3",
+    "urn:oasis:names:tc:SAML:2.0:ac:classes:SpidL2",
+    "urn:oasis:names:tc:SAML:2.0:ac:classes:SpidL3",
+]
+
 ONE_MONTH = 30
 SIX_MONTHS = 182
 ONE_YEAR = 365
diff --git a/src/spid_sp_test/metadata_public.py b/src/spid_sp_test/metadata_public.py
index 259d754..9346aa2 100644
--- a/src/spid_sp_test/metadata_public.py
+++ b/src/spid_sp_test/metadata_public.py
@@ -233,24 +233,33 @@ def test_extensions_cie(self, ext_type="Public"):
             )
 
         # the following elements MUST be present
-        for ele in attrs:
-            ctype = self.doc.xpath(f"//ContactPerson/Extensions/{ele}")
+        contacts = self.doc.xpath("//ContactPerson")
+        for contact in contacts:
+            for ele in attrs:
+                ctype = contact.xpath(f"Extensions/{ele}")
+
+                # some special conditions ...
+                if (
+                        contact.attrib['contactType'] == 'technical'
+                        and ele == "IPACode"
+                ):
+                    continue
 
-            self._assertTrue(
-                ctype,
-                f"{ele} element MUST be present",
-            )
-
-            # is <= because already protected with the previous check
-            self._assertTrue(
-                (len(ctype) <= 1),
-                f"only one {ele} element MUST be present",
-            )
+                self._assertTrue(
+                    ctype,
+                    f"{ele} element MUST be present",
+                )
 
-            if ctype:
+                # is <= because already protected with the previous check
                 self._assertTrue(
-                    ctype[0].text,
-                    f"The {ele} element MUST have a value",
+                    (len(ctype) <= 1),
+                    f"only one {ele} element MUST be present",
                 )
 
+                if ctype:
+                    self._assertTrue(
+                        ctype[0].text,
+                        f"The {ele} element MUST have a value",
+                    )
+
         return self.is_ok(f"{self.__class__.__name__}.test_extensions_cie")
diff --git a/src/spid_sp_test/response.py b/src/spid_sp_test/response.py
index 3b4d4c7..37717f1 100644
--- a/src/spid_sp_test/response.py
+++ b/src/spid_sp_test/response.py
@@ -22,6 +22,7 @@
 from spid_sp_test.utils import del_ns
 from tempfile import NamedTemporaryFile
 
+from .constants import NOSESINDEX_ACRS
 from .utils import get_xmlsec1_bin, html_absolute_paths
 from .utils import load_plugin
 
@@ -180,6 +181,11 @@ def do_authnrequest(self):
 
         self.acr = self.get_acr()
 
+        if self.acr in NOSESINDEX_ACRS:
+            _session_index = None
+        else:
+            _session_index = saml_rnd_id()
+
         self.response_attrs = {
             "ResponseID": saml_rnd_id(),
             "AuthnRequestID": self.authnreq_attrs["ID"],
@@ -194,7 +200,7 @@ def do_authnrequest(self):
             "NameID": "that-transient-opaque-value",
             "AssertionID": saml_rnd_id(),
             "AuthnIstant": datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
-            "SessionIndex": saml_rnd_id(),
+            "SessionIndex": _session_index,
             "Issuer": self.issuer,
             "Audience": self.authnreq_issuer,
             "AuthnContextClassRef": self.acr
diff --git a/src/spid_sp_test/responses/templates/base.xml b/src/spid_sp_test/responses/templates/base.xml
index e2f462e..4c2b465 100644
--- a/src/spid_sp_test/responses/templates/base.xml
+++ b/src/spid_sp_test/responses/templates/base.xml
@@ -60,7 +60,7 @@
         {% block AuthnStatement %}
         <saml:AuthnStatement
             AuthnInstant="{{AuthnIstant}}"
-            SessionIndex="{{SessionIndex}}" >
+            {% if SessionIndex %}SessionIndex="{{SessionIndex}}" {% endif %}>
             {% block AuthnContext %}
             <saml:AuthnContext>
                 <saml:AuthnContextClassRef>{{AuthnContextClassRef}}</saml:AuthnContextClassRef>
diff --git a/tests/metadata/public-sp-cie-double-contacts.xml b/tests/metadata/public-sp-cie-double-contacts.xml
new file mode 100644
index 0000000..cdc4a51
--- /dev/null
+++ b/tests/metadata/public-sp-cie-double-contacts.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="https://www.public-sp.it" ID="id-9DH0mAtqONiJQF0IB">
+
+<ds:Signature Id="Signature1">
+<ds:SignedInfo>
+<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
+<ds:Reference URI="#id-9DH0mAtqONiJQF0IB">
+<ds:Transforms>
+<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
+<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
+</ds:Transforms>
+<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
+<ds:DigestValue />
+</ds:Reference>
+</ds:SignedInfo>
+<ds:SignatureValue />
+<ds:KeyInfo>
+<ds:X509Data>
+<ds:X509Certificate>MIIEGDCCAwCgAwIBAgIJAOrYj9oLEJCwMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAklUMQ4wDAYDVQQIEwVJdGFseTENMAsGA1UEBxMEUm9tZTENMAsGA1UEChMEQWdJRDESMBAGA1UECxMJQWdJRCBURVNUMRQwEgYDVQQDEwthZ2lkLmdvdi5pdDAeFw0xOTA0MTExMDAyMDhaFw0yNTAzMDgxMDAyMDhaMGUxCzAJBgNVBAYTAklUMQ4wDAYDVQQIEwVJdGFseTENMAsGA1UEBxMEUm9tZTENMAsGA1UEChMEQWdJRDESMBAGA1UECxMJQWdJRCBURVNUMRQwEgYDVQQDEwthZ2lkLmdvdi5pdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK8kJVo+ugRrbbv9xhXCuVrqi4B7/MQzQc62ocwlFFujJNd4m1mXkUHFbgvwhRkQqo2DAmFeHiwCkJT3K1eeXIFhNFFroEzGPzONyekLpjNvmYIs1CFvirGOj0bkEiGaKEs+/umzGjxIhy5JQlqXE96y1+Izp2QhJimDK0/KNij8I1bzxseP0Ygc4SFveKS+7QO+PrLzWklEWGMs4DM5Zc3VRK7g4LWPWZhKdImC1rnS+/lEmHSvHisdVp/DJtbSrZwSYTRvTTz5IZDSq4kAzrDfpj16h7b3t3nFGc8UoY2Ro4tRZ3ahJ2r3b79yK6C5phY7CAANuW3gDdhVjiBNYs0CAwEAAaOByjCBxzAdBgNVHQ4EFgQU3/7kV2tbdFtphbSA4LH7+w8SkcwwgZcGA1UdIwSBjzCBjIAU3/7kV2tbdFtphbSA4LH7+w8SkcyhaaRnMGUxCzAJBgNVBAYTAklUMQ4wDAYDVQQIEwVJdGFseTENMAsGA1UEBxMEUm9tZTENMAsGA1UEChMEQWdJRDESMBAGA1UECxMJQWdJRCBURVNUMRQwEgYDVQQDEwthZ2lkLmdvdi5pdIIJAOrYj9oLEJCwMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAJNFqXg/V3aimJKUmUaqmQEEoSc3qvXFITvT5f5bKw9yk/NVhR6wndL+z/24h1OdRqs76blgH8k116qWNkkDtt0AlSjQOx5qvFYh1UviOjNdRI4WkYONSw+vuavcx+fB6O5JDHNmMhMySKTnmRqTkyhjrch7zaFIWUSV7hsBuxpqmrWDoLWdXbV3eFH3mINA5AoIY/m0bZtzZ7YNgiFWzxQgekpxd0vcTseMnCcXnsAlctdir0FoCZztxMuZjlBjwLTtM6Ry3/48LMM8Z+lw7NMciKLLTGQyU8XmKKSSOh0dGh5Lrlt5GxIIJkH81C0YimWebz8464QPL3RbLnTKg+c=</ds:X509Certificate>
+</ds:X509Data>
+</ds:KeyInfo>
+</ds:Signature>
+
+   <md:SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+      <md:KeyDescriptor use="signing">
+         <ds:KeyInfo>
+            <ds:X509Data>
+               <ds:X509Certificate>MIIEGDCCAwCgAwIBAgIJAOrYj9oLEJCwMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAklUMQ4wDAYDVQQIEwVJdGFseTENMAsGA1UEBxMEUm9tZTENMAsGA1UEChMEQWdJRDESMBAGA1UECxMJQWdJRCBURVNUMRQwEgYDVQQDEwthZ2lkLmdvdi5pdDAeFw0xOTA0MTExMDAyMDhaFw0yNTAzMDgxMDAyMDhaMGUxCzAJBgNVBAYTAklUMQ4wDAYDVQQIEwVJdGFseTENMAsGA1UEBxMEUm9tZTENMAsGA1UEChMEQWdJRDESMBAGA1UECxMJQWdJRCBURVNUMRQwEgYDVQQDEwthZ2lkLmdvdi5pdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK8kJVo+ugRrbbv9xhXCuVrqi4B7/MQzQc62ocwlFFujJNd4m1mXkUHFbgvwhRkQqo2DAmFeHiwCkJT3K1eeXIFhNFFroEzGPzONyekLpjNvmYIs1CFvirGOj0bkEiGaKEs+/umzGjxIhy5JQlqXE96y1+Izp2QhJimDK0/KNij8I1bzxseP0Ygc4SFveKS+7QO+PrLzWklEWGMs4DM5Zc3VRK7g4LWPWZhKdImC1rnS+/lEmHSvHisdVp/DJtbSrZwSYTRvTTz5IZDSq4kAzrDfpj16h7b3t3nFGc8UoY2Ro4tRZ3ahJ2r3b79yK6C5phY7CAANuW3gDdhVjiBNYs0CAwEAAaOByjCBxzAdBgNVHQ4EFgQU3/7kV2tbdFtphbSA4LH7+w8SkcwwgZcGA1UdIwSBjzCBjIAU3/7kV2tbdFtphbSA4LH7+w8SkcyhaaRnMGUxCzAJBgNVBAYTAklUMQ4wDAYDVQQIEwVJdGFseTENMAsGA1UEBxMEUm9tZTENMAsGA1UEChMEQWdJRDESMBAGA1UECxMJQWdJRCBURVNUMRQwEgYDVQQDEwthZ2lkLmdvdi5pdIIJAOrYj9oLEJCwMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAJNFqXg/V3aimJKUmUaqmQEEoSc3qvXFITvT5f5bKw9yk/NVhR6wndL+z/24h1OdRqs76blgH8k116qWNkkDtt0AlSjQOx5qvFYh1UviOjNdRI4WkYONSw+vuavcx+fB6O5JDHNmMhMySKTnmRqTkyhjrch7zaFIWUSV7hsBuxpqmrWDoLWdXbV3eFH3mINA5AoIY/m0bZtzZ7YNgiFWzxQgekpxd0vcTseMnCcXnsAlctdir0FoCZztxMuZjlBjwLTtM6Ry3/48LMM8Z+lw7NMciKLLTGQyU8XmKKSSOh0dGh5Lrlt5GxIIJkH81C0YimWebz8464QPL3RbLnTKg+c=</ds:X509Certificate>
+            </ds:X509Data>
+         </ds:KeyInfo>
+      </md:KeyDescriptor>
+      <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://www.public-sp.it/Shibboleth.sso/SLO/POST" />
+      <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
+      <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://www.public-sp.it/Shibboleth.sso/SAML2/POST" index="0" isDefault="true" />
+      <md:AttributeConsumingService index="0">
+         <md:ServiceName xml:lang="it">CIE Service for Public Administration</md:ServiceName>
+         <md:RequestedAttribute Name="name" />
+         <md:RequestedAttribute Name="familyName" />
+         <md:RequestedAttribute Name="dateOfBirth" />
+         <md:RequestedAttribute Name="fiscalNumber" />
+      </md:AttributeConsumingService>
+   </md:SPSSODescriptor>
+   <md:Organization>
+      <md:OrganizationName xml:lang="it">Public Service</md:OrganizationName>
+      <md:OrganizationDisplayName xml:lang="it">Public Service</md:OrganizationDisplayName>
+      <md:OrganizationURL xml:lang="it">https://www.public-sp.it/</md:OrganizationURL>
+   </md:Organization>
+    <md:ContactPerson contactType="administrative">
+        <md:Extensions>
+            <cie:Public xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions"/>
+            <cie:IPACode xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">codiceipa</cie:IPACode>
+            <cie:IPACategory xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">C1</cie:IPACategory>
+            <cie:VATNumber xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">12345678901</cie:VATNumber>
+            <cie:FiscalCode xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">12345678901</cie:FiscalCode>
+            <cie:NACE2Code xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">12.34.56</cie:NACE2Code>
+            <cie:NACE2Code xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">65.43.21</cie:NACE2Code>
+            <cie:Municipality xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">Rome</cie:Municipality>
+            <cie:Province xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">H501</cie:Province>
+            <cie:Country xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">IT</cie:Country>
+        </md:Extensions>
+        <md:Company>ente</md:Company>
+        <md:EmailAddress>mail@gov.it</md:EmailAddress>
+    </md:ContactPerson>
+    <md:ContactPerson contactType="technical">
+        <md:Extensions>
+            <cie:VATNumber xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">12345678901</cie:VATNumber>
+            <cie:FiscalCode xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">12345678901</cie:FiscalCode>
+            <cie:NACE2Code xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">01.02.03</cie:NACE2Code>
+            <cie:NACE2Code xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">03.02.01</cie:NACE2Code>
+            <cie:Municipality xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">Rome</cie:Municipality>
+            <cie:Province xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">H501</cie:Province>
+            <cie:Country xmlns:cie="https://www.cartaidentita.interno.gov.it/saml-extensions">IT</cie:Country>
+        </md:Extensions>
+        <md:Company>ente</md:Company>
+        <md:EmailAddress>mail@gov.it</md:EmailAddress>
+    </md:ContactPerson>
+</md:EntityDescriptor>