Skip to content

Commit c8c7864

Browse files
committed
Add organization and contact person validations
- Adds validation for organization info (name or display name or url) and triggers a warning if none is present - Adds support and technical contact person validation (both should exist and have an email address) and triggers a warning if not both are present - Adds `not_empty` function to do a shallow truthiness check (aka nested values like [{}] will be considered not empty) - Adds test for the above scenarios
1 parent 4e3aeb7 commit c8c7864

File tree

4 files changed

+182
-11
lines changed

4 files changed

+182
-11
lines changed

src/saml2/config.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from saml2.mdstore import MetadataStore
2525
from saml2.saml import NAME_FORMAT_URI
2626
from saml2.virtual_org import VirtualOrg
27+
from saml2.utility import not_empty
2728
from saml2.utility.config import ConfigValidationError
2829

2930
logger = logging.getLogger(__name__)
@@ -610,6 +611,15 @@ def validate_application_identifier_format(application_identifier):
610611
return re.search(r"([a-zA-Z0-9])+:([a-zA-Z0-9():_\-])+:([0-9])+"
611612
r"(\.([0-9])+){1,2}", application_identifier)
612613

614+
@staticmethod
615+
def get_type_contact_person(contacts, ctype):
616+
return [contact for contact in contacts
617+
if contact.get("contact_type") == ctype]
618+
619+
@staticmethod
620+
def contact_has_email_address(contact):
621+
return not_empty(contact.get("email_address"))
622+
613623

614624
class eIDASSPConfig(SPConfig, eIDASConfig):
615625
def get_endpoint_element(self, element):
@@ -633,9 +643,21 @@ def validate(self):
633643
"manage_name_id_service SHOULD NOT be declared":
634644
self.get_endpoint_element("manage_name_id_service") is None,
635645
"application_identifier SHOULD be declared":
636-
self.get_application_identifier() is not None,
646+
not_empty(self.get_application_identifier()),
637647
"protocol_version SHOULD be declared":
638-
self.get_protocol_version() is not None,
648+
not_empty(self.get_protocol_version()),
649+
"minimal organization info (name/dname/url) SHOULD be declared":
650+
not_empty(self.organization),
651+
"contact_person with contact_type 'technical' and at least one "
652+
"email_address SHOULD be declared":
653+
any(filter(self.contact_has_email_address,
654+
self.get_type_contact_person(self.contact_person,
655+
ctype="technical"))),
656+
"contact_person with contact_type 'support' and at least one "
657+
"email_address SHOULD be declared":
658+
any(filter(self.contact_has_email_address,
659+
self.get_type_contact_person(self.contact_person,
660+
ctype="support")))
639661
}
640662

641663
if not all(warning_validators.values()):

src/saml2/utility/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,12 @@ def make_type(mtype, *args):
88

99
def make_list(*args):
1010
return make_type(list, *args)
11+
12+
13+
def not_empty(element):
14+
if isinstance(element, bool):
15+
return True
16+
17+
if element:
18+
return True
19+
return False

tests/eidas/sp_conf.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,22 @@
6161
"display_name": ("AB Exempel", "se"),
6262
"url": "http://www.example.org",
6363
},
64-
"contact_person": [{
65-
"given_name": "Roland",
66-
"sur_name": "Hedberg",
67-
"telephone_number": "+46 70 100 0000",
68-
"email_address": ["tech@eample.com",
69-
"tech@example.org"],
70-
"contact_type": "technical"
71-
},
64+
"contact_person": [
65+
{
66+
"given_name": "Roland",
67+
"sur_name": "Hedberg",
68+
"telephone_number": "+46 70 100 0000",
69+
"email_address": ["tech@eample.com",
70+
"tech@example.org"],
71+
"contact_type": "technical"
72+
},
73+
{
74+
"given_name": "Roland",
75+
"sur_name": "Hedberg",
76+
"telephone_number": "+46 70 100 0000",
77+
"email_address": ["tech@eample.com",
78+
"tech@example.org"],
79+
"contact_type": "support"}
7280
],
7381
"logger": {
7482
"rotating": {

tests/eidas/test_sp.py

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,20 @@ def test_protocol_version_in_metadata(self, config):
111111

112112

113113
class TestSPConfig:
114+
@pytest.fixture(scope="function")
115+
def technical_contacts(self, config):
116+
return [
117+
x for x in config["contact_person"]
118+
if x["contact_type"] == "technical"
119+
]
120+
121+
@pytest.fixture(scope="function")
122+
def support_contacts(self, config):
123+
return [
124+
x for x in config["contact_person"]
125+
if x["contact_type"] == "support"
126+
]
127+
114128
@pytest.fixture(scope="function")
115129
def raise_error_on_warning(self, monkeypatch):
116130
def r(*args, **kwargs):
@@ -182,6 +196,15 @@ def test_no_application_identifier_warning(self, config, raise_error_on_warning)
182196
with pytest.raises(ConfigValidationError):
183197
conf.validate()
184198

199+
def test_empty_application_identifier_warning(self, config, raise_error_on_warning):
200+
config["service"]["sp"]["application_identifier"] = ""
201+
202+
conf = eIDASSPConfig()
203+
conf.load(config)
204+
205+
with pytest.raises(ConfigValidationError):
206+
conf.validate()
207+
185208
def test_application_identifier_wrong_format(self, config):
186209
config["service"]["sp"]["application_identifier"] = "TEST:Node.1"
187210

@@ -191,7 +214,7 @@ def test_application_identifier_wrong_format(self, config):
191214
with pytest.raises(ConfigValidationError):
192215
conf.validate()
193216

194-
def test_application_identifier_ok_format(self, config):
217+
def test_application_identifier_ok_format(self, config, raise_error_on_warning):
195218
conf = eIDASSPConfig()
196219
conf.load(config)
197220
conf.validate()
@@ -204,3 +227,112 @@ def test_no_protocol_version_warning(self, config, raise_error_on_warning):
204227

205228
with pytest.raises(ConfigValidationError):
206229
conf.validate()
230+
231+
def test_empty_protocol_version_warning(self, config, raise_error_on_warning):
232+
config["service"]["sp"]["protocol_version"] = ""
233+
234+
conf = eIDASSPConfig()
235+
conf.load(config)
236+
237+
with pytest.raises(ConfigValidationError):
238+
conf.validate()
239+
240+
def test_no_organization_info_warning(self, config, raise_error_on_warning):
241+
del config["organization"]
242+
243+
conf = eIDASSPConfig()
244+
conf.load(config)
245+
246+
with pytest.raises(ConfigValidationError):
247+
conf.validate()
248+
249+
def test_empty_organization_info_warning(self, config, raise_error_on_warning):
250+
config["organization"] = {}
251+
252+
conf = eIDASSPConfig()
253+
conf.load(config)
254+
255+
with pytest.raises(ConfigValidationError):
256+
conf.validate()
257+
258+
def test_no_technical_contact_person(self,
259+
config,
260+
technical_contacts,
261+
raise_error_on_warning):
262+
for contact in technical_contacts:
263+
contact["contact_type"] = "other"
264+
265+
conf = eIDASSPConfig()
266+
conf.load(config)
267+
268+
with pytest.raises(ConfigValidationError):
269+
conf.validate()
270+
271+
def test_technical_contact_person_no_email(self,
272+
config,
273+
technical_contacts,
274+
raise_error_on_warning):
275+
276+
for contact in technical_contacts:
277+
del contact["email_address"]
278+
279+
conf = eIDASSPConfig()
280+
conf.load(config)
281+
282+
with pytest.raises(ConfigValidationError):
283+
conf.validate()
284+
285+
def test_technical_contact_person_empty_email(self,
286+
config,
287+
technical_contacts,
288+
raise_error_on_warning):
289+
290+
for contact in technical_contacts:
291+
del contact["email_address"]
292+
293+
conf = eIDASSPConfig()
294+
conf.load(config)
295+
296+
with pytest.raises(ConfigValidationError):
297+
conf.validate()
298+
299+
def test_no_support_contact_person(self,
300+
config,
301+
support_contacts,
302+
raise_error_on_warning):
303+
for contact in support_contacts:
304+
contact["contact_type"] = "other"
305+
306+
conf = eIDASSPConfig()
307+
conf.load(config)
308+
309+
with pytest.raises(ConfigValidationError):
310+
conf.validate()
311+
312+
def test_support_contact_person_no_email(self,
313+
config,
314+
support_contacts,
315+
raise_error_on_warning):
316+
317+
for contact in support_contacts:
318+
del contact["email_address"]
319+
320+
conf = eIDASSPConfig()
321+
conf.load(config)
322+
323+
with pytest.raises(ConfigValidationError):
324+
conf.validate()
325+
326+
def test_support_contact_person_empty_email(self,
327+
config,
328+
support_contacts,
329+
raise_error_on_warning):
330+
331+
for contact in support_contacts:
332+
del contact["email_address"]
333+
334+
conf = eIDASSPConfig()
335+
conf.load(config)
336+
337+
with pytest.raises(ConfigValidationError):
338+
conf.validate()

0 commit comments

Comments
 (0)