diff options
author | Nikos Sklikas <nsklikas@admin.grnet.gr> | 2020-05-05 14:12:02 +0300 |
---|---|---|
committer | Nikos Sklikas <nsklikas@admin.grnet.gr> | 2020-05-28 13:00:06 +0300 |
commit | 531f6bd1eac3488987be0e153d847ff59f5bbfbf (patch) | |
tree | 0e5f6fc6248d07efbf13aaccb0a0c998693c40a8 | |
parent | 32ab8e68b48cb0f32b7299a1c72a36f25af3cc17 (diff) | |
download | pysaml2-531f6bd1eac3488987be0e153d847ff59f5bbfbf.tar.gz |
Add requested_attributes param
Add requested_attributes param to create_authn_request
-rw-r--r-- | src/saml2/client_base.py | 42 | ||||
-rw-r--r-- | src/saml2/config.py | 44 | ||||
-rw-r--r-- | tests/server_conf.py | 6 | ||||
-rw-r--r-- | tests/test_51_client.py | 36 |
4 files changed, 106 insertions, 22 deletions
diff --git a/src/saml2/client_base.py b/src/saml2/client_base.py index ea0c86f9..a6a2c871 100644 --- a/src/saml2/client_base.py +++ b/src/saml2/client_base.py @@ -12,8 +12,6 @@ import logging from saml2.entity import Entity -import saml2.attributemaps as attributemaps - from saml2.mdstore import destinations from saml2.profile import paos, ecp from saml2.saml import NAMEID_FORMAT_TRANSIENT @@ -24,7 +22,8 @@ from saml2.samlp import AuthzDecisionQuery from saml2.samlp import AuthnRequest from saml2.samlp import Extensions from saml2.extension import sp_type -from saml2.extension import requested_attributes +from saml2.extension.requested_attributes import RequestedAttribute +from saml2.extension.requested_attributes import RequestedAttributes import saml2 from saml2.soap import make_soap_enveloped_saml_thingy @@ -235,7 +234,7 @@ class Base(Entity): service_url_binding=None, message_id=0, consent=None, extensions=None, sign=None, allow_create=None, sign_prepare=False, sign_alg=None, - digest_alg=None, **kwargs): + digest_alg=None, requested_attributes=None, **kwargs): """ Creates an authentication request. :param destination: Where the request should be sent. @@ -253,6 +252,9 @@ class Base(Entity): :param allow_create: If the identity provider is allowed, in the course of fulfilling the request, to create a new identifier to represent the principal. + :param requested_attributes: A list of dicts which contain attributes + to be appended to the requested_attributes config option. The + dicts format is similar to the requested_attributes config option. :param kwargs: Extra key word arguments :return: either a tuple of request ID and <samlp:AuthnRequest> instance or a tuple of request ID and str when sign is set to True @@ -379,17 +381,19 @@ class Base(Entity): item = sp_type.SPType(text=conf_sp_type) extensions.add_extension_element(item) - requested_attrs = self.config.getattr('requested_attributes', 'sp') - if requested_attrs: + if requested_attributes: + requested_attributes += \ + self.config.getattr('requested_attributes', 'sp') + else: + requested_attributes = \ + self.config.getattr('requested_attributes', 'sp') + + if requested_attributes: if not extensions: extensions = Extensions() - attributemapsmods = [] - for modname in attributemaps.__all__: - attributemapsmods.append(getattr(attributemaps, modname)) - items = [] - for attr in requested_attrs: + for attr in requested_attributes: friendly_name = attr.get('friendly_name') name = attr.get('name') name_format = attr.get('name_format') @@ -401,34 +405,34 @@ class Base(Entity): 'name', 'friendly_name')) if not name: - for mod in attributemapsmods: + for converter in self.config.attribute_converters: try: - name = mod.MAP['to'][friendly_name] + name = converter._to[friendly_name.lower()] except KeyError: continue else: if not name_format: - name_format = mod.MAP['identifier'] + name_format = converter.name_format break if not friendly_name: - for mod in attributemapsmods: + for converter in self.config.attribute_converters: try: - friendly_name = mod.MAP['fro'][name] + friendly_name = converter._fro[name.lower()] except KeyError: continue else: if not name_format: - name_format = mod.MAP['identifier'] + name_format = converter.name_format break - items.append(requested_attributes.RequestedAttribute( + items.append(RequestedAttribute( is_required=is_required, name_format=name_format, friendly_name=friendly_name, name=name)) - item = requested_attributes.RequestedAttributes( + item = RequestedAttributes( extension_elements=items) extensions.add_extension_element(item) diff --git a/src/saml2/config.py b/src/saml2/config.py index 147d1bdf..011ab43d 100644 --- a/src/saml2/config.py +++ b/src/saml2/config.py @@ -509,6 +509,50 @@ class SPConfig(Config): return None + def load(self, cnf, metadata_construction=False): + super().load(cnf, metadata_construction=False) + self.fix_requested_attributes() + return self + + def fix_requested_attributes(self): + """Add friendly_name or name if missing to the requested attributes""" + requested_attrs = self.getattr('requested_attributes', 'sp') + + if not requested_attrs: + return + + for attr in requested_attrs: + friendly_name = attr.get('friendly_name') + name = attr.get('name') + name_format = attr.get('name_format') + + if not name and not friendly_name: + raise ValueError( + "Missing required attribute: '{}' or '{}'".format( + 'name', 'friendly_name')) + + if not name: + for converter in self.attribute_converters: + try: + attr['name'] = converter._to[friendly_name.lower()] + except KeyError: + continue + else: + if not name_format: + attr['name_format'] = converter.name_format + break + + if not friendly_name: + for converter in self.attribute_converters: + try: + attr['friendly_name'] = converter._fro[name.lower()] + except KeyError: + continue + else: + if not name_format: + attr['name_format'] = converter.name_format + break + class IdPConfig(Config): def_context = "idp" diff --git a/tests/server_conf.py b/tests/server_conf.py index 4b528119..2b87b942 100644 --- a/tests/server_conf.py +++ b/tests/server_conf.py @@ -16,15 +16,15 @@ CONFIG = { "idp": ["urn:mace:example.com:saml:roland:idp"], "requested_attributes": [ { - "name": "http://eidas.europa.eu/attributes/naturalperson/DateOfBirth", + "name": "urn:oid:1.3.6.1.4.1.5923.1.1.1.2", "required": False, }, { - "friendly_name": "PersonIdentifier", + "friendly_name": "eduPersonNickname", "required": True, }, { - "friendly_name": "PlaceOfBirth", + "friendly_name": "eduPersonScopedAffiliation", }, ], } diff --git a/tests/test_51_client.py b/tests/test_51_client.py index f6fc2759..cd1f0669 100644 --- a/tests/test_51_client.py +++ b/tests/test_51_client.py @@ -286,6 +286,42 @@ class TestClient: assert c.attributes['FriendlyName'] assert c.attributes['NameFormat'] + def test_create_auth_request_requested_attributes(self): + req_attr = [{"friendly_name": "eduPersonOrgUnitDN", "required": True}] + ar_str = "%s" % self.client.create_authn_request( + "http://www.example.com/sso", + message_id="id1", + requested_attributes=req_attr + )[1] + + ar = samlp.authn_request_from_string(ar_str) + + node_requested_attributes = None + for e in ar.extensions.extension_elements: + if e.tag == RequestedAttributes.c_tag: + node_requested_attributes = e + break + assert node_requested_attributes is not None + + attr = None + for c in node_requested_attributes.children: + if c.attributes['FriendlyName'] == "eduPersonOrgUnitDN": + attr = c + break + + assert attr + assert attr.tag == RequestedAttribute.c_tag + assert attr.attributes['isRequired'] == 'true' + assert ( + attr.attributes['Name'] + == 'urn:mace:dir:attribute-def:eduPersonOrgUnitDN' + ) + assert attr.attributes['FriendlyName'] == 'eduPersonOrgUnitDN' + assert ( + attr.attributes['NameFormat'] + == 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic' + ) + def test_create_auth_request_unset_force_authn_by_default(self): req_id, req = self.client.create_authn_request( "http://www.example.com/sso", sign=False, message_id="id1" |