Skip to content

Commit 531f6bd

Browse files
committed
Add requested_attributes param
Add requested_attributes param to create_authn_request
1 parent 32ab8e6 commit 531f6bd

File tree

4 files changed

+106
-22
lines changed

4 files changed

+106
-22
lines changed

src/saml2/client_base.py

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212

1313
from saml2.entity import Entity
1414

15-
import saml2.attributemaps as attributemaps
16-
1715
from saml2.mdstore import destinations
1816
from saml2.profile import paos, ecp
1917
from saml2.saml import NAMEID_FORMAT_TRANSIENT
@@ -24,7 +22,8 @@
2422
from saml2.samlp import AuthnRequest
2523
from saml2.samlp import Extensions
2624
from saml2.extension import sp_type
27-
from saml2.extension import requested_attributes
25+
from saml2.extension.requested_attributes import RequestedAttribute
26+
from saml2.extension.requested_attributes import RequestedAttributes
2827

2928
import saml2
3029
from saml2.soap import make_soap_enveloped_saml_thingy
@@ -235,7 +234,7 @@ def create_authn_request(self, destination, vorg="", scoping=None,
235234
service_url_binding=None, message_id=0,
236235
consent=None, extensions=None, sign=None,
237236
allow_create=None, sign_prepare=False, sign_alg=None,
238-
digest_alg=None, **kwargs):
237+
digest_alg=None, requested_attributes=None, **kwargs):
239238
""" Creates an authentication request.
240239
241240
:param destination: Where the request should be sent.
@@ -253,6 +252,9 @@ def create_authn_request(self, destination, vorg="", scoping=None,
253252
:param allow_create: If the identity provider is allowed, in the course
254253
of fulfilling the request, to create a new identifier to represent
255254
the principal.
255+
:param requested_attributes: A list of dicts which contain attributes
256+
to be appended to the requested_attributes config option. The
257+
dicts format is similar to the requested_attributes config option.
256258
:param kwargs: Extra key word arguments
257259
:return: either a tuple of request ID and <samlp:AuthnRequest> instance
258260
or a tuple of request ID and str when sign is set to True
@@ -379,17 +381,19 @@ def create_authn_request(self, destination, vorg="", scoping=None,
379381
item = sp_type.SPType(text=conf_sp_type)
380382
extensions.add_extension_element(item)
381383

382-
requested_attrs = self.config.getattr('requested_attributes', 'sp')
383-
if requested_attrs:
384+
if requested_attributes:
385+
requested_attributes += \
386+
self.config.getattr('requested_attributes', 'sp')
387+
else:
388+
requested_attributes = \
389+
self.config.getattr('requested_attributes', 'sp')
390+
391+
if requested_attributes:
384392
if not extensions:
385393
extensions = Extensions()
386394

387-
attributemapsmods = []
388-
for modname in attributemaps.__all__:
389-
attributemapsmods.append(getattr(attributemaps, modname))
390-
391395
items = []
392-
for attr in requested_attrs:
396+
for attr in requested_attributes:
393397
friendly_name = attr.get('friendly_name')
394398
name = attr.get('name')
395399
name_format = attr.get('name_format')
@@ -401,34 +405,34 @@ def create_authn_request(self, destination, vorg="", scoping=None,
401405
'name', 'friendly_name'))
402406

403407
if not name:
404-
for mod in attributemapsmods:
408+
for converter in self.config.attribute_converters:
405409
try:
406-
name = mod.MAP['to'][friendly_name]
410+
name = converter._to[friendly_name.lower()]
407411
except KeyError:
408412
continue
409413
else:
410414
if not name_format:
411-
name_format = mod.MAP['identifier']
415+
name_format = converter.name_format
412416
break
413417

414418
if not friendly_name:
415-
for mod in attributemapsmods:
419+
for converter in self.config.attribute_converters:
416420
try:
417-
friendly_name = mod.MAP['fro'][name]
421+
friendly_name = converter._fro[name.lower()]
418422
except KeyError:
419423
continue
420424
else:
421425
if not name_format:
422-
name_format = mod.MAP['identifier']
426+
name_format = converter.name_format
423427
break
424428

425-
items.append(requested_attributes.RequestedAttribute(
429+
items.append(RequestedAttribute(
426430
is_required=is_required,
427431
name_format=name_format,
428432
friendly_name=friendly_name,
429433
name=name))
430434

431-
item = requested_attributes.RequestedAttributes(
435+
item = RequestedAttributes(
432436
extension_elements=items)
433437
extensions.add_extension_element(item)
434438

src/saml2/config.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,50 @@ def ecp_endpoint(self, ipaddress):
509509

510510
return None
511511

512+
def load(self, cnf, metadata_construction=False):
513+
super().load(cnf, metadata_construction=False)
514+
self.fix_requested_attributes()
515+
return self
516+
517+
def fix_requested_attributes(self):
518+
"""Add friendly_name or name if missing to the requested attributes"""
519+
requested_attrs = self.getattr('requested_attributes', 'sp')
520+
521+
if not requested_attrs:
522+
return
523+
524+
for attr in requested_attrs:
525+
friendly_name = attr.get('friendly_name')
526+
name = attr.get('name')
527+
name_format = attr.get('name_format')
528+
529+
if not name and not friendly_name:
530+
raise ValueError(
531+
"Missing required attribute: '{}' or '{}'".format(
532+
'name', 'friendly_name'))
533+
534+
if not name:
535+
for converter in self.attribute_converters:
536+
try:
537+
attr['name'] = converter._to[friendly_name.lower()]
538+
except KeyError:
539+
continue
540+
else:
541+
if not name_format:
542+
attr['name_format'] = converter.name_format
543+
break
544+
545+
if not friendly_name:
546+
for converter in self.attribute_converters:
547+
try:
548+
attr['friendly_name'] = converter._fro[name.lower()]
549+
except KeyError:
550+
continue
551+
else:
552+
if not name_format:
553+
attr['name_format'] = converter.name_format
554+
break
555+
512556

513557
class IdPConfig(Config):
514558
def_context = "idp"

tests/server_conf.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@
1616
"idp": ["urn:mace:example.com:saml:roland:idp"],
1717
"requested_attributes": [
1818
{
19-
"name": "http://eidas.europa.eu/attributes/naturalperson/DateOfBirth",
19+
"name": "urn:oid:1.3.6.1.4.1.5923.1.1.1.2",
2020
"required": False,
2121
},
2222
{
23-
"friendly_name": "PersonIdentifier",
23+
"friendly_name": "eduPersonNickname",
2424
"required": True,
2525
},
2626
{
27-
"friendly_name": "PlaceOfBirth",
27+
"friendly_name": "eduPersonScopedAffiliation",
2828
},
2929
],
3030
}

tests/test_51_client.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,42 @@ def test_create_auth_request_0(self):
286286
assert c.attributes['FriendlyName']
287287
assert c.attributes['NameFormat']
288288

289+
def test_create_auth_request_requested_attributes(self):
290+
req_attr = [{"friendly_name": "eduPersonOrgUnitDN", "required": True}]
291+
ar_str = "%s" % self.client.create_authn_request(
292+
"http://www.example.com/sso",
293+
message_id="id1",
294+
requested_attributes=req_attr
295+
)[1]
296+
297+
ar = samlp.authn_request_from_string(ar_str)
298+
299+
node_requested_attributes = None
300+
for e in ar.extensions.extension_elements:
301+
if e.tag == RequestedAttributes.c_tag:
302+
node_requested_attributes = e
303+
break
304+
assert node_requested_attributes is not None
305+
306+
attr = None
307+
for c in node_requested_attributes.children:
308+
if c.attributes['FriendlyName'] == "eduPersonOrgUnitDN":
309+
attr = c
310+
break
311+
312+
assert attr
313+
assert attr.tag == RequestedAttribute.c_tag
314+
assert attr.attributes['isRequired'] == 'true'
315+
assert (
316+
attr.attributes['Name']
317+
== 'urn:mace:dir:attribute-def:eduPersonOrgUnitDN'
318+
)
319+
assert attr.attributes['FriendlyName'] == 'eduPersonOrgUnitDN'
320+
assert (
321+
attr.attributes['NameFormat']
322+
== 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic'
323+
)
324+
289325
def test_create_auth_request_unset_force_authn_by_default(self):
290326
req_id, req = self.client.create_authn_request(
291327
"http://www.example.com/sso", sign=False, message_id="id1"

0 commit comments

Comments
 (0)