diff options
-rw-r--r-- | docs/howto/config.rst | 23 | ||||
-rw-r--r-- | src/saml2/config.py | 2 | ||||
-rw-r--r-- | src/saml2/metadata.py | 18 | ||||
-rw-r--r-- | tests/sp_mdext_conf.py | 14 | ||||
-rw-r--r-- | tests/test_83_md_extensions.py | 82 |
5 files changed, 116 insertions, 23 deletions
diff --git a/docs/howto/config.rst b/docs/howto/config.rst index 9f20eba6..a46927a4 100644 --- a/docs/howto/config.rst +++ b/docs/howto/config.rst @@ -76,6 +76,29 @@ if you need to include a certificate chain. Each entry in *additional_cert_files* must be a PEM formatted file with a single certificate. +entity_attributes +^^^^^^^^^^^^^^^^^ + +Generates an ``Attribute`` element with the given NameFormat, Name, FriendlyName and +values, each as an ``AttributeValue`` element. + +The element is added under the generated metadata ``EntityDescriptor`` as an +``Extension`` element under the ``EntityAttributes`` element. + +And omit + +Example:: + + "entity_attributes": [ + { + "name_format": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", + "name": "urn:oasis:names:tc:SAML:profiles:subject-id:req", + # "friendly_name" is not set + "values": ["any"], + }, + ] + + assurance_certification ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/saml2/config.py b/src/saml2/config.py index 278aba16..dbf57aa3 100644 --- a/src/saml2/config.py +++ b/src/saml2/config.py @@ -54,6 +54,7 @@ COMMON_ARGS = [ "preferred_binding", "session_storage", "assurance_certification", + "entity_attributes", "entity_category", "entity_category_support", "xmlsec_path", @@ -226,6 +227,7 @@ class Config(object): self.domain = "" self.name_qualifier = "" self.assurance_certification = [] + self.entity_attributes = [] self.entity_category = [] self.entity_category_support = [] self.crypto_backend = 'xmlsec1' diff --git a/src/saml2/metadata.py b/src/saml2/metadata.py index d80b41ac..50b4ff71 100644 --- a/src/saml2/metadata.py +++ b/src/saml2/metadata.py @@ -708,6 +708,24 @@ def entity_descriptor(confd): if confd.contact_person is not None: entd.contact_person = do_contact_persons_info(confd.contact_person) + if confd.entity_attributes: + if not entd.extensions: + entd.extensions = md.Extensions() + attributes = [ + Attribute( + name_format=attr.get("format"), + name=attr.get("name"), + friendly_name=attr.get("friendly_name"), + attribute_value=[ + AttributeValue(text=value) + for value in attr.get("values", []) + ], + ) + for attr in confd.entity_attributes + ] + for attribute in attributes: + _add_attr_to_entity_attributes(entd.extensions, attribute) + if confd.assurance_certification: if not entd.extensions: entd.extensions = md.Extensions() diff --git a/tests/sp_mdext_conf.py b/tests/sp_mdext_conf.py index b1f0cf42..2fe43390 100644 --- a/tests/sp_mdext_conf.py +++ b/tests/sp_mdext_conf.py @@ -3,6 +3,20 @@ from pathutils import full_path, xmlsec_path CONFIG = { "entityid": "urn:mace:example.com:saml:roland:sp", "name": "urn:mace:example.com:saml:roland:sp", + "entity_attributes": [ + { + "name_format": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", + "name": "urn:oasis:names:tc:SAML:profiles:subject-id:req", + # "friendly_name" is not set + "values": ["any"], + }, + { + "name": "somename", + "friendly_name": "somefriendlyname", + "name_format": "format", + "values": ["x", "y", "z"], + }, + ], "description": "My own SP", "service": { "sp": { diff --git a/tests/test_83_md_extensions.py b/tests/test_83_md_extensions.py index dace10a5..35098595 100644 --- a/tests/test_83_md_extensions.py +++ b/tests/test_83_md_extensions.py @@ -1,27 +1,63 @@ +from saml2 import create_class_from_xml_string as parse_str_as +from saml2 import create_class_from_element_tree as parse_element_as from saml2.config import Config -from saml2.metadata import entity_descriptor from saml2.extension.sp_type import SPType +from saml2.metadata import Attribute +from saml2.metadata import entity_descriptor -__author__ = 'roland' - -fil = "sp_mdext_conf.py" - -cnf = Config().load_file(fil, metadata_construction=True) -ed = entity_descriptor(cnf) - -print(ed) - -assert ed.spsso_descriptor.extensions -assert len(ed.spsso_descriptor.extensions.extension_elements) == 3 - -assert ed.extensions -assert len(ed.extensions.extension_elements) > 1 - -assert any(e.tag is SPType.c_tag for e in ed.extensions.extension_elements) - -cnf.setattr('sp', 'sp_type_in_metadata', False) -ed = entity_descriptor(cnf) - -print(ed) -assert all(e.tag is not SPType.c_tag for e in ed.extensions.extension_elements) +class TestMDExt(): + def test_sp_type_true(self): + fil = "sp_mdext_conf.py" + cnf = Config().load_file(fil, metadata_construction=True) + ed = entity_descriptor(cnf) + + assert ed.spsso_descriptor.extensions + assert len(ed.spsso_descriptor.extensions.extension_elements) == 3 + assert ed.extensions + assert len(ed.extensions.extension_elements) > 1 + assert any(e.tag is SPType.c_tag for e in ed.extensions.extension_elements) + + def test_sp_type_false(self): + fil = "sp_mdext_conf.py" + cnf = Config().load_file(fil, metadata_construction=True) + cnf.setattr('sp', 'sp_type_in_metadata', False) + ed = entity_descriptor(cnf) + + assert all(e.tag is not SPType.c_tag for e in ed.extensions.extension_elements) + + def test_entity_attributes(self): + fil = "sp_mdext_conf.py" + cnf = Config().load_file(fil, metadata_construction=True) + ed = entity_descriptor(cnf) + + entity_attributes = next( + e + for e in ed.extensions.extension_elements + if e.tag == 'EntityAttributes' + ) + attributes = [ + parse_str_as(Attribute, e.to_string()) + for e in entity_attributes.children + ] + assert all( + a.name in [ + "urn:oasis:names:tc:SAML:profiles:subject-id:req", + "somename", + ] + for a in attributes + ) + + import saml2.attribute_converter + attrc = saml2.attribute_converter.ac_factory() + + import saml2.mdstore + mds = saml2.mdstore.MetadataStore(attrc, cnf) + + mds.load("inline", ed.to_string()) + entityid = ed.entity_id + entity_attributes = mds.entity_attributes(entityid) + assert entity_attributes == { + 'urn:oasis:names:tc:SAML:profiles:subject-id:req': ['any'], + 'somename': ['x', 'y', 'z'], + } |