Skip to content

Commit e5d0b4f

Browse files
committed
Support arbitrary entity attributes
Introduce new configuration option `entity_attributes` that defines a list of dictionaries each of which represents an <Attribute> element. Each dicrionary has fields for the NameFormat, the Name, the FriendName and a list of strings that are used to create <AttributeValue> elements, each with the string as the text node. "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"], }, ] Signed-off-by: Ivan Kanakarakis <[email protected]>
1 parent 21eb11f commit e5d0b4f

File tree

5 files changed

+116
-23
lines changed

5 files changed

+116
-23
lines changed

docs/howto/config.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,29 @@ if you need to include a certificate chain.
7676

7777
Each entry in *additional_cert_files* must be a PEM formatted file with a single certificate.
7878

79+
entity_attributes
80+
^^^^^^^^^^^^^^^^^
81+
82+
Generates an ``Attribute`` element with the given NameFormat, Name, FriendlyName and
83+
values, each as an ``AttributeValue`` element.
84+
85+
The element is added under the generated metadata ``EntityDescriptor`` as an
86+
``Extension`` element under the ``EntityAttributes`` element.
87+
88+
And omit
89+
90+
Example::
91+
92+
"entity_attributes": [
93+
{
94+
"name_format": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
95+
"name": "urn:oasis:names:tc:SAML:profiles:subject-id:req",
96+
# "friendly_name" is not set
97+
"values": ["any"],
98+
},
99+
]
100+
101+
79102
assurance_certification
80103
^^^^^^^^^^^^^^^^^^^^^^^
81104

src/saml2/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"preferred_binding",
5555
"session_storage",
5656
"assurance_certification",
57+
"entity_attributes",
5758
"entity_category",
5859
"entity_category_support",
5960
"xmlsec_path",
@@ -226,6 +227,7 @@ def __init__(self, homedir="."):
226227
self.domain = ""
227228
self.name_qualifier = ""
228229
self.assurance_certification = []
230+
self.entity_attributes = []
229231
self.entity_category = []
230232
self.entity_category_support = []
231233
self.crypto_backend = 'xmlsec1'

src/saml2/metadata.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,24 @@ def entity_descriptor(confd):
708708
if confd.contact_person is not None:
709709
entd.contact_person = do_contact_persons_info(confd.contact_person)
710710

711+
if confd.entity_attributes:
712+
if not entd.extensions:
713+
entd.extensions = md.Extensions()
714+
attributes = [
715+
Attribute(
716+
name_format=attr.get("format"),
717+
name=attr.get("name"),
718+
friendly_name=attr.get("friendly_name"),
719+
attribute_value=[
720+
AttributeValue(text=value)
721+
for value in attr.get("values", [])
722+
],
723+
)
724+
for attr in confd.entity_attributes
725+
]
726+
for attribute in attributes:
727+
_add_attr_to_entity_attributes(entd.extensions, attribute)
728+
711729
if confd.assurance_certification:
712730
if not entd.extensions:
713731
entd.extensions = md.Extensions()

tests/sp_mdext_conf.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,20 @@
33
CONFIG = {
44
"entityid": "urn:mace:example.com:saml:roland:sp",
55
"name": "urn:mace:example.com:saml:roland:sp",
6+
"entity_attributes": [
7+
{
8+
"name_format": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
9+
"name": "urn:oasis:names:tc:SAML:profiles:subject-id:req",
10+
# "friendly_name" is not set
11+
"values": ["any"],
12+
},
13+
{
14+
"name": "somename",
15+
"friendly_name": "somefriendlyname",
16+
"name_format": "format",
17+
"values": ["x", "y", "z"],
18+
},
19+
],
620
"description": "My own SP",
721
"service": {
822
"sp": {

tests/test_83_md_extensions.py

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,63 @@
1+
from saml2 import create_class_from_xml_string as parse_str_as
2+
from saml2 import create_class_from_element_tree as parse_element_as
13
from saml2.config import Config
2-
from saml2.metadata import entity_descriptor
34
from saml2.extension.sp_type import SPType
5+
from saml2.metadata import Attribute
6+
from saml2.metadata import entity_descriptor
47

5-
__author__ = 'roland'
6-
7-
fil = "sp_mdext_conf.py"
8-
9-
cnf = Config().load_file(fil, metadata_construction=True)
10-
ed = entity_descriptor(cnf)
11-
12-
print(ed)
13-
14-
assert ed.spsso_descriptor.extensions
15-
assert len(ed.spsso_descriptor.extensions.extension_elements) == 3
16-
17-
assert ed.extensions
18-
assert len(ed.extensions.extension_elements) > 1
19-
20-
assert any(e.tag is SPType.c_tag for e in ed.extensions.extension_elements)
21-
22-
cnf.setattr('sp', 'sp_type_in_metadata', False)
23-
ed = entity_descriptor(cnf)
24-
25-
print(ed)
268

27-
assert all(e.tag is not SPType.c_tag for e in ed.extensions.extension_elements)
9+
class TestMDExt():
10+
def test_sp_type_true(self):
11+
fil = "sp_mdext_conf.py"
12+
cnf = Config().load_file(fil, metadata_construction=True)
13+
ed = entity_descriptor(cnf)
14+
15+
assert ed.spsso_descriptor.extensions
16+
assert len(ed.spsso_descriptor.extensions.extension_elements) == 3
17+
assert ed.extensions
18+
assert len(ed.extensions.extension_elements) > 1
19+
assert any(e.tag is SPType.c_tag for e in ed.extensions.extension_elements)
20+
21+
def test_sp_type_false(self):
22+
fil = "sp_mdext_conf.py"
23+
cnf = Config().load_file(fil, metadata_construction=True)
24+
cnf.setattr('sp', 'sp_type_in_metadata', False)
25+
ed = entity_descriptor(cnf)
26+
27+
assert all(e.tag is not SPType.c_tag for e in ed.extensions.extension_elements)
28+
29+
def test_entity_attributes(self):
30+
fil = "sp_mdext_conf.py"
31+
cnf = Config().load_file(fil, metadata_construction=True)
32+
ed = entity_descriptor(cnf)
33+
34+
entity_attributes = next(
35+
e
36+
for e in ed.extensions.extension_elements
37+
if e.tag == 'EntityAttributes'
38+
)
39+
attributes = [
40+
parse_str_as(Attribute, e.to_string())
41+
for e in entity_attributes.children
42+
]
43+
assert all(
44+
a.name in [
45+
"urn:oasis:names:tc:SAML:profiles:subject-id:req",
46+
"somename",
47+
]
48+
for a in attributes
49+
)
50+
51+
import saml2.attribute_converter
52+
attrc = saml2.attribute_converter.ac_factory()
53+
54+
import saml2.mdstore
55+
mds = saml2.mdstore.MetadataStore(attrc, cnf)
56+
57+
mds.load("inline", ed.to_string())
58+
entityid = ed.entity_id
59+
entity_attributes = mds.entity_attributes(entityid)
60+
assert entity_attributes == {
61+
'urn:oasis:names:tc:SAML:profiles:subject-id:req': ['any'],
62+
'somename': ['x', 'y', 'z'],
63+
}

0 commit comments

Comments
 (0)