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

Issue:
spid-sp-test emits all attributes in responses
When producing SAML responses, spid-sp-test seems to always emit all the authenticated subject attributes, disregarding what was requested within the `AuthnRequest` by the SP.
I would expect it to honour the SP request (unless a specific check should test the SP behaviour otherwise) and perhaps provide some command-line argument to force the addition or removal of other attributes.


Discussion / hints:
that's something I decided to handle by hand, using `-aj` parameter with a custom collection of attributes.

I think that this issue could be covered with an automatic selection of attributes to be release, in base of which have been defined in the authn request

thank you @mauromol 

Fix patch:
diff --git a/setup.py b/setup.py
index 054f95c..3f476aa 100644
--- a/setup.py
+++ b/setup.py
@@ -10,7 +10,7 @@ def readme():
 
 setup(
     name=_pkg_name,
-    version='0.9.10',
+    version='0.9.11',
     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/__init__.py b/src/spid_sp_test/__init__.py
index 8bf167b..e427429 100644
--- a/src/spid_sp_test/__init__.py
+++ b/src/spid_sp_test/__init__.py
@@ -101,11 +101,13 @@ def _assert(
         level: str = "info",
         **kwargs,
     ):
-        if not check and level != "warning":
-            self.handle_error(error_message, description, traceback, **kwargs)
-        else:
-            # level = "info" if level in ("warning",) else level
-            self.handle_result(level, error_message, description, traceback, **kwargs)
+        if level == "warning":
+            # overwrite to info is warning doens't happen.
+            if check:
+                level = "info"
+        elif not check:
+            level = "error"
+        self.handle_result(level, error_message, description, traceback, **kwargs)
 
     def _assertTrue(self, *args, **kwargs):
         self._assert(*args, **kwargs)
diff --git a/src/spid_sp_test/authn_request.py b/src/spid_sp_test/authn_request.py
index 1d68667..524a90b 100644
--- a/src/spid_sp_test/authn_request.py
+++ b/src/spid_sp_test/authn_request.py
@@ -23,6 +23,7 @@
 from spid_sp_test import BASE_DIR, AbstractSpidCheck
 
 from saml2.server import Server
+
 # from saml2.sigver import CryptoBackendXmlSec1
 sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir))
 from tempfile import NamedTemporaryFile
@@ -372,10 +373,10 @@ def test_xmldsig(self):
                     tmp_file.write(self.authn_request_decoded)
                     tmp_file.seek(0)
                     cmd = (
-                        'xmlsec1 --verify --insecure --id-attr:ID '
+                        "xmlsec1 --verify --insecure --id-attr:ID "
                         '"urn:oasis:names:tc:SAML:2.0:protocol:AuthnRequest" '
                         # f'--pubkey-cert-pem {cert_file.name} '
-                        f'--pubkey-pem {pubkey_file.name} '
+                        f"--pubkey-pem {pubkey_file.name} "
                         f"{tmp_file.name} "
                     )
 
@@ -394,17 +395,29 @@ def test_xmldsig(self):
                         lines = [msg]
                         if err.stderr:
                             stderr = "stderr: " + "\nstderr: ".join(
-                                list(filter(None, err.stderr.decode("utf-8").split(r"\n")))
+                                list(
+                                    filter(
+                                        None, err.stderr.decode("utf-8").split(r"\n")
+                                    )
+                                )
                             )
                             lines.append(stderr)
                         if err.stdout:
                             stdout = "stdout: " + "\nstdout: ".join(
-                                list(filter(None, err.stdout.decode("utf-8").split(r"\n")))
+                                list(
+                                    filter(
+                                        None, err.stdout.decode("utf-8").split(r"\n")
+                                    )
+                                )
                             )
                             lines.append(stdout)
                         _msg = "\n".join(lines)
                         self.handle_result(
-                            "error", _msg, description="Description", traceback=_msg, **_data
+                            "error",
+                            "AuthnRequest Signature validation failed",
+                            description=_msg,
+                            traceback=_msg,
+                            **_data,
                         )
                         return
 
@@ -633,7 +646,14 @@ def test_AuthnRequest_SPID(self):
                     )
 
         attr = "AttributeConsumingServiceIndex"
-        if attr in req.attrib:
+        if attr not in req.attrib:
+            self._assertTrue(
+                (attr in req.attrib),
+                f"The {attr} attribute MUST be present",
+                description=req.attrib,
+                **_data,
+            )
+        else:
             availableattributeindexes = []
 
             acss = self.md.xpath(
@@ -685,8 +705,14 @@ def test_Subject(self):
                 description=subj,
                 **_data,
             )
-
-        if len(subj) == 1:
+        # Not shure that this must be checked :)
+        # elif len(subj) < 1:
+        # self._assertTrue(
+        # False,
+        # "The Subject element MUST be present",
+        # **_data,
+        # )
+        elif len(subj) == 1:
             subj = subj[0]
             name_id = subj.xpath("./NameID")
             self._assertTrue(
@@ -742,6 +768,11 @@ def test_Issuer(self):
         )
 
         if not e:
+            self._assertTrue(
+                e,
+                "The Issuer element MUST be present",
+                **_data,
+            )
             return self.is_ok(_method)
         else:
             e = e[0]
@@ -806,6 +837,11 @@ def test_NameIDPolicy(self):
         )
 
         if not e:
+            self._assertTrue(
+                e,
+                "The NameIDPolicy element MUST be present",
+                **_data,
+            )
             return self.is_ok(_method)
         else:
             e = e[0]
@@ -865,8 +901,16 @@ def test_Conditions(self):
                 description=e,
                 **_data,
             )
-
-        if len(e) == 1:
+        # Not shure that this must be checked :)
+        # elif len(e) < 1:
+        # self._assertTrue(
+        # e,
+        # "The Conditions element MUST be present",
+        # **_data,
+        # )
+        # return self.is_ok(_method)
+
+        elif len(e) == 1:
             e = e[0]
             for attr in ["NotBefore", "NotOnOrAfter"]:
                 self._assertTrue(
@@ -1023,7 +1067,7 @@ def test_Signature(self):
                 **_data,
             )
 
-            # save the grubbed certificate for future alanysis
+            # save the grubbed certificate for future analysis
             # cert = sign[0].xpath('./KeyInfo/X509Data/X509Certificate')[0]
             # dump_pem.dump_request_pem(cert, 'authn', 'signature', DATA_DIR)
         return self.is_ok(_method)
diff --git a/src/spid_sp_test/constants.py b/src/spid_sp_test/constants.py
index e564543..de90e58 100644
--- a/src/spid_sp_test/constants.py
+++ b/src/spid_sp_test/constants.py
@@ -163,7 +163,7 @@
 ]
 
 NODE_NAME = "urn:oasis:names:tc:SAML:2.0:assertion:Assertion"
-EMAIL_REGEXP = "^(\w|\.|\_|\-)+[@](\w|\_|\-|\.)+[.]\w{2,6}$"
+EMAIL_REGEXP = r"^(\w|\.|\_|\-)+[@](\w|\_|\-|\.)+[.]\w{2,6}$"
 
 
 ISO3166_CODES = [
diff --git a/src/spid_sp_test/metadata.py b/src/spid_sp_test/metadata.py
index 25d3b44..2d10f78 100644
--- a/src/spid_sp_test/metadata.py
+++ b/src/spid_sp_test/metadata.py
@@ -66,6 +66,7 @@ def get(self, metadata_url: str):
 
     def xsd_check(self, xsds_files: list = ["saml-schema-metadata-2.0.xsd"]):
         _method = f"{self.__class__.__name__}.xsd_check"
+        test_id = ["1.0.0"]
         logger.debug(self.metadata.decode())
         _orig_pos = os.getcwd()
         os.chdir(self.xsds_files_path)
@@ -77,28 +78,32 @@ def xsd_check(self, xsds_files: list = ["saml-schema-metadata-2.0.xsd"]):
                 schema = xmlschema.XMLSchema(schema_file)
                 if not schema.is_valid(metadata):
                     schema.validate(metadata)
-                    self.handle_result(
-                        level="error",
-                        title=msg,
+                    self._assertTrue(
+                        False,
+                        msg,
                         description=msg,
                         references="",
                         method=_method,
+                        test_id=test_id,
                     )
                     # raise Exception('Validation Error')
                 break
             except Exception as e:
                 os.chdir(_orig_pos)
                 logger.error(f"{msg}: {e}")
-                self.handle_result(
-                    "error",
+                self._assertTrue(
+                    False,
                     msg,
                     description="xsd test failed",
                     traceback=f"{e}",
                     method=_method,
+                    test_id=test_id,
                 )
         os.chdir(_orig_pos)
         if not self.errors:
-            self._assertTrue(True, _method, description=msg, method=_method)
+            self._assertTrue(
+                True, _method, description=msg, method=_method, test_id=test_id
+            )
         return self.is_ok(_method)
 
     def test_EntityDescriptor(self):
@@ -107,12 +112,13 @@ def test_EntityDescriptor(self):
             etree.tostring(ent).decode()[:128] for ent in entity_desc if entity_desc
         ]
         _method = f"{self.__class__.__name__}.test_EntityDescriptor"
-        _data = dict(test_id="", references=["TR pag. 19"], method=_method)
+        _data = dict(references=["TR pag. 19"], method=_method)
 
         self._assertTrue(
             len(entity_desc) == 1,
             "Only one EntityDescriptor element MUST be present",
             description=desc,
+            test_id=["1.3.0"],
             **_data,
         )
 
@@ -120,6 +126,7 @@ def test_EntityDescriptor(self):
             self.doc.attrib.get("entityID"),
             "The entityID attribute MUST be present",
             description=self.doc.attrib,
+            test_id=["1.3.1"],
             **_data,
         )
 
@@ -128,6 +135,7 @@ def test_EntityDescriptor(self):
             "The entityID attribute MUST have a value",
             description=entity_desc[0].get("entityID"),
             **_data,
+            test_id=["1.3.2"],
         )
 
         if self.production:
@@ -149,7 +157,9 @@ def test_SPSSODescriptor(self):
         desc = [etree.tostring(ent).decode()[:128] for ent in spsso if spsso]
 
         _method = f"{self.__class__.__name__}.test_SPSSODescriptor"
-        _data = dict(test_id="", references=[""], method=_method, description=desc)
+        _data = dict(
+            test_id=["1.6.0"], references=[""], method=_method, description=desc
+        )
 
         self._assertTrue(
             (len(spsso) == 1),
@@ -162,14 +172,13 @@ def test_SPSSODescriptor_SPID(self):
         spsso = self.doc.xpath("//EntityDescriptor/SPSSODescriptor")
         desc = [etree.tostring(ent).decode()[:128] for ent in spsso if spsso]
         _method = f"{self.__class__.__name__}.test_SPSSODescriptor_SPID"
-        _data = dict(
-            test_id="", references=["TR pag. 20"], method=_method, description=desc
-        )
+        _data = dict(references=["TR pag. 20"], method=_method, description=desc)
 
         for attr in ["protocolSupportEnumeration", "AuthnRequestsSigned"]:
             self._assertTrue(
                 (attr in spsso[0].attrib),
                 f"The {attr} attribute MUST be present",
+                test_id=["1.6.1", "1.6.3"],
                 **_data,
             )
 
@@ -177,6 +186,7 @@ def test_SPSSODescriptor_SPID(self):
             self._assertTrue(
                 a,
                 f"The {attr} attribute MUST have a value",
+                test_id=["1.6.2", "1.6.4"],
                 **_data,
             )
 
@@ -184,6 +194,7 @@ def test_SPSSODescriptor_SPID(self):
                 self._assertTrue(
                     a.lower() == "true",
                     f"The {attr} attribute MUST be true",
+                    test_id=["1.6.5"],
                     **_data,
                 )
 
@@ -210,7 +221,7 @@ def test_NameIDFormat_Transient(self):
 
     def test_xmldsig(self):
         """Verify the SP metadata signature"""
-        tmp_file = NamedTemporaryFile()
+        tmp_file = NamedTemporaryFile(suffix=".xml")
         tmp_file.write(self.metadata)
         tmp_file.seek(0)
         xmlsec_cmd = [
@@ -225,7 +236,7 @@ def test_xmldsig(self):
         msg = "the metadata signature MUST be valid"
 
         _data = dict(
-            test_id="",
+            test_id=["1.9.0"],
             references=["TR pag. 19"],
             method=f"{self.__class__.__name__}.test_xmldsig",
         )
@@ -266,7 +277,6 @@ def test_Signature(self):
         desc = [etree.tostring(ent).decode() for ent in sign if sign]
 
         _data = dict(
-            test_id="",
             description="".join(desc)[:128] or "",
             references=["TR pag. 19"],
             method=_method,
@@ -275,6 +285,7 @@ def test_Signature(self):
         self._assertTrue(
             (len(sign) > 0),
             "The Signature element MUST be present",
+            test_id=["1.7.0"],
             **_data,
         )
 
@@ -282,22 +293,26 @@ def test_Signature(self):
             self.handle_result(
                 "error",
                 "The SignatureMethod element MUST be present",
+                test_id=["1.7.1"],
                 **_data,
             )
             self.handle_result(
                 "error",
                 "The Algorithm attribute MUST be present in SignatureMethod element",
+                test_id=["1.7.2"],
                 **_data,
             )
             self.handle_result(
                 "error",
                 "The signature algorithm MUST be valid",
                 description=f"Must be one of [{', '.join(constants.ALLOWED_XMLDSIG_ALGS)}]",
+                test_id=["1.7.3"],
                 **_data,
             )
             self.handle_result(
                 "error",
                 "The Algorithm attribute MUST be present in DigestMethod element",
+                test_id=["1.7.4"],
                 **_data,
             )
 
@@ -306,6 +321,7 @@ def test_Signature(self):
                 "error",
                 "The digest algorithm MUST be valid",
                 description=f"Must be one of [{', '.join(constants.ALLOWED_DGST_ALGS)}]",
+                test_id=["1.7.5"],
                 **_data,
             )
         else:
@@ -315,6 +331,7 @@ def test_Signature(self):
             self._assertTrue(
                 (len(method) > 0),
                 "The SignatureMethod element MUST be present",
+                test_id=["1.7.1"],
                 **_data,
             )
 
@@ -330,6 +347,7 @@ def test_Signature(self):
                 alg in constants.ALLOWED_XMLDSIG_ALGS,
                 "The signature algorithm MUST be valid",
                 description=f"One of {(', '.join(constants.ALLOWED_XMLDSIG_ALGS))}",
+                test_id=["1.7.3"],
                 **_data,
             )
 
@@ -337,12 +355,14 @@ def test_Signature(self):
             self._assertTrue(
                 (len(method) == 1),
                 "The DigestMethod element MUST be present",
+                test_id=["1.7.4"],
                 **_data,
             )
 
             self._assertTrue(
                 ("Algorithm" in method[0].attrib),
                 "The Algorithm attribute MUST be present in DigestMethod element",
+                test_id=["1.7.5"],
                 **_data,
             )
 
@@ -351,6 +371,7 @@ def test_Signature(self):
                 alg in constants.ALLOWED_DGST_ALGS,
                 "The digest algorithm MUST be valid",
                 description=f"One of {(', '.join(constants.ALLOWED_DGST_ALGS))}",
+                test_id=["1.7.6"],
                 **_data,
             )
 
@@ -362,9 +383,12 @@ def test_KeyDescriptor(self):
         kds = self.doc.xpath(
             "//EntityDescriptor/SPSSODescriptor" '/KeyDescriptor[@use="signing"]'
         )
-        _data = dict(test_id="", references=["TR pag. 19"], method=_method)
+        _data = dict(references=["TR pag. 19"], method=_method)
         self._assertTrue(
-            len(kds) >= 1, "At least one signing KeyDescriptor MUST be present", **_data
+            len(kds) >= 1,
+            "At least one signing KeyDescriptor MUST be present",
+            test_id=["1.4.0"],
+            **_data,
         )
 
         desc = [etree.tostring(ent).decode() for ent in kds if kds]
@@ -375,6 +399,7 @@ def test_KeyDescriptor(self):
                 len(certs) >= 1,
                 "At least one signing x509 MUST be present",
                 description="".join(desc)[:128] or "",
+                test_id=["1.4.1"],
                 **_data,
             )
 
@@ -387,6 +412,7 @@ def test_KeyDescriptor(self):
             self._assertTrue(
                 len(certs) >= 1,
                 "At least one encryption x509 MUST be present",
+                test_id=["1.4.2"],
                 **_data,
             )
 
@@ -400,7 +426,6 @@ def test_SingleLogoutService(self):
         _method = f"{self.__class__.__name__}.test_SingleLogoutService"
         desc = [etree.tostring(ent).decode() for ent in slos if slos]
         _data = dict(
-            test_id="",
             references=["AV n. 3"],
             method=_method,
             description="".join(desc)[:128],
@@ -409,6 +434,7 @@ def test_SingleLogoutService(self):
         self._assertTrue(
             len(slos) >= 1,
             "One or more SingleLogoutService elements MUST be present",
+            test_id=["1.8.0"],
             **_data,
         )
 
@@ -417,6 +443,7 @@ def test_SingleLogoutService(self):
                 self._assertTrue(
                     (attr in slo.attrib),
                     f"The {attr} attribute in SingleLogoutService element MUST be present",
+                    test_id=["1.8.1", "1.8.4"],
                     **_data,
                 )
 
@@ -424,6 +451,7 @@ def test_SingleLogoutService(self):
                 self._assertTrue(
                     _attr,
                     f"The {attr} attribute in SingleLogoutService element MUST have a value",
+                    test_id=["1.8.2", "1.8.5"],
                     **_data,
                 )
 
@@ -436,6 +464,7 @@ def test_SingleLogoutService(self):
                             )
                             % (attr, ", ".join(constants.ALLOWED_BINDINGS))  # noqa
                         ),
+                        test_id=["1.8.3"],
                         **_data,  # noqa
                     )
                 if attr == "Location" and self.production:
@@ -444,6 +473,7 @@ def test_SingleLogoutService(self):
                         f"The {attr} attribute "
                         "in SingleLogoutService element "
                         "MUST be a valid HTTPS URL",
+                        test_id=["1.8.6"],
                         **_data,
                     )
                     self._assertHttpUrlWithoutPort(
@@ -470,7 +500,6 @@ def test_AssertionConsumerService(self):
         _method = f"{self.__class__.__name__}.test_AssertionConsumerService"
         desc = [etree.tostring(ent).decode() for ent in acss if acss]
         _data = dict(
-            test_id="",
             references=["TR pag. 20"],
             method=_method,
             description="".join(desc)[:128],
@@ -479,6 +508,7 @@ def test_AssertionConsumerService(self):
         self._assertTrue(
             len(acss) >= 1,
             "At least one AssertionConsumerService MUST be present",
+            test_id=["1.1.0"],
             **_data,
         )
 
@@ -487,6 +517,7 @@ def test_AssertionConsumerService(self):
                 self._assertTrue(
                     (attr in acs.attrib),
                     f"The {attr} attribute MUST be present",
+                    test_id=["1.1.1", "1.1.3", "1.1.5"],
                     **_data,
                 )
                 _attr = acs.get(attr)
@@ -494,6 +525,7 @@ def test_AssertionConsumerService(self):
                     self._assertTrue(
                         int(_attr) >= 0,
                         f"The {attr} attribute MUST be >= 0",
+                        test_id=["1.1.2"],
                         **_data,
                     )
                 elif attr == "Binding":
@@ -503,17 +535,20 @@ def test_AssertionConsumerService(self):
                             ("The %s attribute MUST be one of [%s]")
                             % (attr, ", ".join(constants.ALLOWED_BINDINGS))
                         ),
+                        test_id=["1.1.4"],
                         **_data,
                     )
                 elif attr == "Location" and self.production:
                     self._assertIsValidHttpsUrl(
                         _attr,
                         f"The {attr} attribute MUST be a valid HTTPS url",
+                        test_id=["1.1.6"],
                         **_data,
                     )
                     self._assertHttpUrlWithoutPort(
                         _attr,
                         'The entityID attribute MUST not contain any custom tcp ports, eg: ":8000"',
+                        test_id=["1.1.6"],
                         **_data,
                     )
                 else:
@@ -530,7 +565,6 @@ def test_AssertionConsumerService_SPID(self):
         _method = f"{self.__class__.__name__}.test_AssertionConsumerService_SPID"
         desc = [etree.tostring(ent).decode() for ent in acss if acss]
         _data = dict(
-            test_id="",
             references=["TR pag. 20"],
             method=_method,
             description="".join(desc)[:128],
@@ -539,6 +573,7 @@ def test_AssertionConsumerService_SPID(self):
         self._assertTrue(
             (len(acss) == 1),
             "Only one default AssertionConsumerService MUST be present",
+            test_id=["1.1.7"],
             **_data,
         )
 
@@ -551,6 +586,7 @@ def test_AssertionConsumerService_SPID(self):
         self._assertTrue(
             (len(acss) == 1),
             "Must be present the default AssertionConsumerService with index = 0",
+            test_id=["1.1.8"],
             **_data,
         )
         return self.is_ok(_method)
@@ -563,7 +599,6 @@ def test_AttributeConsumingService(self):
         _method = f"{self.__class__.__name__}.test_AttributeConsumingService"
         desc = [etree.tostring(ent).decode() for ent in acss if acss]
         _data = dict(
-            test_id="",
             references=["TR pag. 20"],
             method=_method,
             description="".join(desc)[:128],
@@ -572,6 +607,7 @@ def test_AttributeConsumingService(self):
         self._assertTrue(
             len(acss) >= 1,
             "One or more AttributeConsumingService elements MUST be present",
+            test_id=["1.2.0"],
             **_data,
         )
         return self.is_ok(_method)
@@ -585,7 +621,6 @@ def test_AttributeConsumingService_SPID(
         desc = [etree.tostring(ent).decode() for ent in acss if acss]
         _method = f"{self.__class__.__name__}.test_AttributeConsumingService_SPID"
         _data = dict(
-            test_id="",
             references=["TR pag. 20"],
             method=_method,
         )
@@ -595,6 +630,7 @@ def test_AttributeConsumingService_SPID(
                 ("index" in acs.attrib),
                 "The index attribute in AttributeConsumigService element MUST be present",
                 description=_desc,
+                test_id=["1.2.1"],
                 **_data,
             )
 
@@ -603,6 +639,7 @@ def test_AttributeConsumingService_SPID(
                 idx >= 0,
                 "The index attribute in AttributeConsumigService element MUST be >= 0",
                 description=_desc,
+                test_id=["1.2.2"],
                 **_data,
             )
 
@@ -611,6 +648,7 @@ def test_AttributeConsumingService_SPID(
                 (len(sn) > 0),
                 "The ServiceName element MUST be present",
                 description=_desc,
+                test_id=["1.2.3"],
                 **_data,
             )
             for sns in sn:
@@ -618,6 +656,7 @@ def test_AttributeConsumingService_SPID(
                     sns.text,
                     "The ServiceName element MUST have a value",
                     description=_desc,
+                    test_id=["1.2.4"],
                     **_data,
                 )
 
@@ -626,6 +665,7 @@ def test_AttributeConsumingService_SPID(
                 len(ras) >= 1,
                 "One or more RequestedAttribute elements MUST be present",
                 description=_desc,
+                test_id=["1.2.5"],
                 **_data,
             )
 
@@ -634,6 +674,7 @@ def test_AttributeConsumingService_SPID(
                     ("Name" in ra.attrib),
                     "The Name attribute in RequestedAttribute element "
                     "MUST be present",
+                    test_id=["1.2.6"],
                     description=_desc,
                     **_data,
                 )
@@ -643,6 +684,7 @@ def test_AttributeConsumingService_SPID(
                     f'The "{ra.attrib.values()[0]}" attribute in RequestedAttribute element MUST be valid',
                     description=f"one of [{', '.join(allowed_attributes)}]",
                     **_data,
+                    test_id=["1.2.7"],
                 )
 
             al = acs.xpath("RequestedAttribute/@Name")
@@ -660,10 +702,13 @@ def test_Organization(self):
 
         desc = [etree.tostring(ent).decode() for ent in orgs if orgs]
         _method = f"{self.__class__.__name__}.test_Organization"
-        _data = dict(description=desc or "", references=["TR pag. 20"], method=_method)
+        _data = dict(description=desc, references=["TR pag. 20"], method=_method)
 
         self._assertTrue(
-            (len(orgs) == 1), "Only one Organization element can be present", **_data
+            (len(orgs) == 1),
+            "Only one Organization element can be present",
+            test_id=["1.5.0"],
+            **_data,
         )
 
         enames = ["OrganizationName", "OrganizationDisplayName", "OrganizationURL"]
@@ -676,6 +721,7 @@ def test_Organization(self):
                 self._assertTrue(
                     len(elements) > 0,
                     f"One or more {ename} elements MUST be present",
+                    test_id=["1.5.1", "1.5.4"],
                     **_data,
                 )
 
@@ -686,6 +732,7 @@ def test_Organization(self):
                             in element.attrib
                         ),  # noqa
                         f"The lang attribute in {ename} element MUST be present",  # noqa
+                        test_id=["1.5.2", "1.5.5", "1.5.8"],
                         **_data,
                     )
 
@@ -698,6 +745,7 @@ def test_Organization(self):
                     self._assertTrue(
                         element.text,
                         f"The {ename} element MUST have a value",
+                        test_id=["1.5.3", "1.5.7", "1.5.9"],
                         **_data,
                     )
 
@@ -710,7 +758,8 @@ def test_Organization(self):
                             OrganizationURLvalue = f"https://{OrganizationURLvalue}"
                         self._assertIsValidHttpUrl(
                             OrganizationURLvalue,
-                            f"The {ename} -element MUST be a valid URL",
+                            f"The {ename} element MUST be a valid URL",
+                            test_id=["1.5.10"],
                             **_data,
                         )
 
@@ -723,6 +772,7 @@ def test_Organization(self):
                         "The elements OrganizationName, OrganizationDisplayName and OrganizationURL "
                         "MUST have the same number of lang attributes"
                     ),  # noqa
+                    test_id=["1.5.5", "1.5.8"],
                     **_data,
                 )
 
@@ -833,7 +883,9 @@ def test_profile_spid_sp_ag_public_lite(self):
         self.test_Contacts_PubPriv(entity_type="spid:aggregated")
 
         # TODO
-        # If the ContactPerson is of spid:entityType “spid:aggregator” the Extensions element MUST contain the element spid:KeyDescriptor with attribute use “spid:validation”
+        # If the ContactPerson is of spid:entityType “spid:aggregator”
+        # the Extensions element MUST contain the element spid:KeyDescriptor
+        # with attribute use “spid:validation”
 
         # The PublicServicesLightAggregator element MUST be present
         self.test_extensions_public_ag(
diff --git a/src/spid_sp_test/metadata_extra.py b/src/spid_sp_test/metadata_extra.py
index a77e6d7..b1a1451 100644
--- a/src/spid_sp_test/metadata_extra.py
+++ b/src/spid_sp_test/metadata_extra.py
@@ -120,7 +120,6 @@ def test_SPSSODescriptor_extra(self):
 
         _method = f"{self.__class__.__name__}.test_SPSSODescriptor_extra"
         _data = dict(
-            test_id="",
             references=[],
             method=_method,
         )
@@ -130,6 +129,7 @@ def test_SPSSODescriptor_extra(self):
                 (attr in spsso[0].attrib),
                 f"The {attr} attribute MUST be present",
                 description=spsso[0].attrib,
+                test_id=["1.6.1", "1.6.7"],
                 **_data,
             )
 
@@ -144,13 +144,18 @@ def test_SPSSODescriptor_extra(self):
                     f"The {attr} attribute MUST be "
                     "urn:oasis:names:tc:SAML:2.0:protocol",
                     description=a,
+                    test_id=["1.6.6"],
                     **_data,
                 )
 
             if attr == "WantAssertionsSigned":
                 a = spsso[0].get(attr)
                 self._assertTrue(
-                    a, f"The {attr} attribute MUST have a value", description=a, **_data
+                    a,
+                    f"The {attr} attribute MUST have a value",
+                    description=a,
+                    **_data,
+                    test_id=["1.6.8"],
                 )
 
                 if a:
@@ -158,6 +163,7 @@ def test_SPSSODescriptor_extra(self):
                         a.lower() == "true",
                         f"The {attr} attribute MUST be true",
                         description=a,
+                        test_id=["1.6.9"],
                         **_data,
                     )
         return self.is_ok(_method)
diff --git a/src/spid_sp_test/metadata_private.py b/src/spid_sp_test/metadata_private.py
index c4c0b5e..b83a36e 100644
--- a/src/spid_sp_test/metadata_private.py
+++ b/src/spid_sp_test/metadata_private.py
@@ -3,7 +3,6 @@ def test_Contacts_Priv(self):
         self.doc.xpath("//ContactPerson")
         _method = f"{self.__class__.__name__}.test_Contacts_Priv"
         _data = dict(
-            test_id="",
             references=[],
             method=_method,
         )
@@ -13,7 +12,7 @@ def test_Contacts_Priv(self):
             ipacode,
             "The IPACode element MUST NOT be present",
             description=ipacode,
-            **_data,
+            test_id = ['1.12.0'], **_data,
         )
 
         exts = self.doc.xpath("//ContactPerson/Extensions/CessionarioCommittente")
@@ -21,7 +20,7 @@ def test_Contacts_Priv(self):
             (len(exts) == 1),
             ("The CessionarioCommittente element MUST be present"),
             description=exts,
-            **_data,
+            test_id = ['1.14.4'], **_data,
         )
 
         if exts:
diff --git a/src/spid_sp_test/metadata_public.py b/src/spid_sp_test/metadata_public.py
index c347240..d1ef002 100644
--- a/src/spid_sp_test/metadata_public.py
+++ b/src/spid_sp_test/metadata_public.py
@@ -47,7 +47,7 @@ def test_Contacts_PubPriv(self, contact_type="other", entity_type=""):
                 len(entity_desc) == 1,
                 "Only one ContactPerson element of contactType "
                 f'"{contact_type}" MUST be present',
-                **_data,
+                test_id = ['1.10.0'], **_data,
             )
 
         return self.is_ok(_method)
@@ -66,7 +66,7 @@ def test_Extensions_PubPriv(self):
                 ext_cnt > 1,
                 "Only one Extensions element inside ContactPerson element MUST be present",
                 description=etree.tostring(cont).decode(),
-                **_data,
+                test_id = ['1.10.1'], **_data,
             )
 
         orgs = self.doc.xpath("//EntityDescriptor/Organization/OrganizationName")
@@ -87,7 +87,7 @@ def test_Extensions_PubPriv(self):
                     company.text == org.text,
                     "If the Company element if present it MUST be equal to OrganizationName",
                     description=(company.text, org.text),
-                    **_data,
+                    test_id = ['1.10.3'], **_data,
                 )
 
         return self.is_ok(_method)
@@ -100,20 +100,20 @@ def test_contactperson_email(self, email_xpath="//ContactPerson/EmailAddress"):
             email,
             f"The {email_xpath} element MUST be present",
             description=[etree.tostring(_val).decode() for _val in email],
-            **_data,
+            test_id = ['1.10.4'], **_data,
         )
         if email:
             self._assertTrue(
                 email[0].text,
                 f"The {email_xpath} element MUST have a value",
                 description=[etree.tostring(_val).decode() for _val in email],
-                **_data,
+                test_id = ['1.10.5'], **_data,
             )
             self._assertTrue(
                 re.match(EMAIL_REGEXP, email[0].text),
                 f"The {email_xpath} element MUST be a valid email address",
                 description=[etree.tostring(_val).decode() for _val in email],
-                **_data,
+                test_id = ['1.10.6'], **_data,
             )
         return self.is_ok(_method)
 
@@ -124,20 +124,21 @@ def test_contactperson_phone(self, phone_xpath="//ContactPerson/TelephoneNumber"
         if phone:
             phone = phone[0].text
             self._assertTrue(
-                phone, f"The {phone_xpath} element MUST have a value", **_data
+                phone, f"The {phone_xpath} element MUST have a value",
+                **_data
             )
             self._assertTrue(
                 (" " not in phone),
                 f"The {phone_xpath} element MUST not contain spaces",
                 description=phone,
-                **_data,
+                test_id = ['1.10.8'], **_data,
             )
             self._assertTrue(
                 (phone[0:3] == "+39"),
                 f'The {phone_xpath} element MUST start with "+39"',
                 description=phone,
                 level="warning",
-                **_data,
+                test_id = ['1.10.9'], **_data,
             )
 
         return self.is_ok(_method)
@@ -148,19 +149,20 @@ def test_Contacts_IPACode(self):
         _data = dict(references=[""], method=_method)
         if self.production:
             ipacode = self.doc.xpath("//ContactPerson/Extensions/IPACode")
-            self._assertTrue(ipacode, "The IPACode element MUST be present", **_data)
+            self._assertTrue(
+                ipacode, "The IPACode element MUST be present",
+                test_id = ['1.11.0'], **_data
+            )
             if ipacode:
                 ipacode = ipacode[0]
                 self._assertTrue(
-                    ipacode.text, "The IPACode element MUST have a value", **_data
-                )
-                self._assertTrue(
-                    ipacode.text, "The IPACode element MUST have a value", **_data
+                    ipacode.text, "The IPACode element MUST have a value",
+                    test_id = ['1.11.1'], **_data
                 )
                 self._assertTrue(
                     get_indicepa_by_ipacode(ipacode.text)[0] == 1,
                     "The IPACode element MUST have a valid value present on IPA",
-                    **_data,
+                    test_id = ['1.11.2'], **_data,
                 )
 
         return self.is_ok(_method)
@@ -177,16 +179,18 @@ def test_extensions_public_private(self, ext_type="Public", contact_type="other"
             ctype,
             f"Missing ContactPerson/Extensions/{ext_type.title()}, "
             "this element MUST be present",
-            **_data,
+            test_id = ['1.11.7', '1.12.5'], **_data,
         )
         if ctype:
             self._assertFalse(
-                ctype[0].text, f"The {ext_type.title()} element MUST be empty", **_data
+                ctype[0].text, f"The {ext_type.title()} element MUST be empty",
+                test_id = ['1.11.8', '1.12.6'], **_data
             )
 
         ctype = self.doc.xpath(f"//ContactPerson/Extensions/{ext_type_not.title()}")
         self._assertFalse(
-            ctype, f"The {ext_type_not.title()} element MUST not be present", **_data
+            ctype, f"The {ext_type_not.title()} element MUST not be present",
+            test_id = ['1.11.9', '1.12.7'], **_data
         )
         return self.is_ok(_method)
 
@@ -203,7 +207,8 @@ def test_Contacts_VATFC(self, private=False):
         )
         if vats:
             self._assertTrue(
-                vats[0].text, "The VATNumber element MUST have a value", **_data
+                vats[0].text, "The VATNumber element MUST have a value",
+                test_id = ['1.11.4'], **_data
             )
             self._assertTrue(
                 (vats[0].text[:2] in ISO3166_CODES),
@@ -221,7 +226,8 @@ def test_Contacts_VATFC(self, private=False):
             )
             fc = fcs[0]
             self._assertTrue(
-                fc.text, "The FiscalCode element MUST have a value", **_data
+                fc.text, "The FiscalCode element MUST have a value",
+                test_id = ['1.11.6'], **_data
             )
         if private and not len(fcs) and not len(vats):
             self._assertTrue(
diff --git a/src/spid_sp_test/response.py b/src/spid_sp_test/response.py
index db16545..ac2ba20 100644
--- a/src/spid_sp_test/response.py
+++ b/src/spid_sp_test/response.py
@@ -112,6 +112,14 @@ def __init__(self, *args, **kwargs):
         self.template_path = kwargs.get("template_path", self.template_path)
 
         self.metadata_etree = kwargs.get("metadata_etree")
+        self.requested_attrs_raw = self.metadata_etree.xpath(
+            "//EntityDescriptor/SPSSODescriptor"
+            "/AttributeConsumingService/RequestedAttribute"
+        )
+        self.requested_attrs = [
+            i.attrib["Name"] for i in self.requested_attrs_raw
+        ]
+
         self.acs_url = self.metadata_etree.xpath(
             "//SPSSODescriptor/AssertionConsumerService[@index=0]"
         )[0].attrib["Location"]
@@ -139,7 +147,11 @@ def __init__(self, *args, **kwargs):
             with open(kwargs["attr_json"], "r") as json_data:
                 self.user_attrs = json.loads(json_data.read())
         else:
-            self.user_attrs = settings.ATTRIBUTES
+            # returns ONLY the requested attributes shown in the metadata
+            # otherwise it returns all the attributes (for test purpose)
+            self.user_attrs = {
+                i:settings.ATTRIBUTES[i] for i in self.requested_attrs
+            } or settings.ATTRIBUTES
 
         self.html_path = kwargs.get("html_path")
         self.no_send_response = kwargs.get("no_send_response")
@@ -174,6 +186,7 @@ def do_authnrequest(self):
 
         self.issuer = self.kwargs.get("issuer", SAML2_IDP_CONFIG["entityid"])
         self.authnreq_attrs = self.authnreq_etree.xpath("/AuthnRequest")[0].attrib
+
         self.authnreq_issuer = self.authnreq_etree.xpath("/AuthnRequest/Issuer")[
             0
         ].attrib["NameQualifier"]
@@ -203,8 +216,9 @@ def do_authnrequest(self):
             "SessionIndex": _session_index,
             "Issuer": self.issuer,
             "Audience": self.authnreq_issuer,
-            "AuthnContextClassRef": self.acr
-            or settings.DEFAULT_RESPONSE["AuthnContextClassRef"],
+            "AuthnContextClassRef": (
+                self.acr or settings.DEFAULT_RESPONSE["AuthnContextClassRef"]
+            ),
             "IssueInstantMillis": now.strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
             "sign_response": settings.DEFAULT_RESPONSE["sign_response"],
             "sign_assertion": settings.DEFAULT_RESPONSE["sign_assertion"],
@@ -299,7 +313,7 @@ def check_response(self, res, msg: str, attendeds=[], test_id=""):
         status_code = f"[http status_code: {res.status_code}]"
         self._assertTrue(
             status,
-            f"{msg}: {status_code}",
+            msg,
             method=f"{self.__class__.__name__}.check_response",
             description=status_code,
             references=[],
diff --git a/src/spid_sp_test/utils.py b/src/spid_sp_test/utils.py
index b7fabbd..b3da804 100644
--- a/src/spid_sp_test/utils.py
+++ b/src/spid_sp_test/utils.py
@@ -209,40 +209,6 @@ def html_absolute_paths(html_content, url):
     return html.tostring(q)
 
 
-def report_to_html(o):
-    res = []
-    for h1_title in o.keys():
-        res.append("<h1>%s</h1>" % h1_title)
-        for h2_title in o[h1_title].keys():
-            res.append("<h2>%s</h2>" % h2_title)
-            for h3_title in o[h1_title][h2_title].keys():
-                res.append("<h3>%s</h3>" % h3_title)
-                for test_case in o[h1_title][h2_title][h3_title].keys():
-                    res.append("<h4>%s</h4>" % test_case)
-                    for t in o[h1_title][h2_title][h3_title][test_case]:  # noqa
-                        res.append(
-                            "<p><strong>Description:</strong> %s</p>" % t["test"]
-                        )  # noqa
-                        if t["result"] == "success":
-                            res.append(
-                                '<div style="background-color: #f2ffe6; padding: 5px; border: 1px solid green; margin: 5px">'
-                            )  # noqa
-                            res.append(
-                                '<h5 style="color: green">%s</h5>' % t["result"]
-                            )  # noqa
-                        else:
-                            res.append(
-                                '<div style="background-color: #ffebe6; padding: 5px; border: 1px solid red; margin: 5px">'
-                            )  # noqa
-                            res.append(
-                                '<h5 style="color: red">%s</h5>' % t["result"]
-                            )  # noqa
-                        # res.append('<p>Test: %s</p>' % t['test'].replace('\n', '<br/>'))  # noqa
-                        res.append("<p>Obtained value: %s</p>" % t["value"])
-                        res.append("</div>")
-    return "\n".join(res)
-
-
 def load_plugin(plugin_name):
     n1, _, n2 = plugin_name.rpartition(".")
     module = importlib.import_module(n1)