diff options
author | ivan <ivan@grnet.gr> | 2017-07-18 15:14:28 +0300 |
---|---|---|
committer | ivan <ivan@grnet.gr> | 2017-10-08 19:47:24 +0300 |
commit | 20c961045cb1e2b7b56b2c7eb72595897d1a1477 (patch) | |
tree | 7735d7895ba7c2926ea41fbfc263ebaf00c1b1d1 | |
parent | 144248f968603c1483c56fcbbddab0edfd61613f (diff) | |
download | pysaml2-20c961045cb1e2b7b56b2c7eb72595897d1a1477.tar.gz |
Add eIDAS RequestedAttributes node support
-rw-r--r-- | src/saml2/client_base.py | 56 | ||||
-rw-r--r-- | src/saml2/config.py | 1 | ||||
-rw-r--r-- | src/saml2/extension/requested_attributes.py | 131 | ||||
-rw-r--r-- | tests/server_conf.py | 13 | ||||
-rw-r--r-- | tests/test_51_client.py | 16 | ||||
-rw-r--r-- | tools/data/requested_attributes.xsd | 28 |
6 files changed, 245 insertions, 0 deletions
diff --git a/src/saml2/client_base.py b/src/saml2/client_base.py index 88a9bd11..531ddea5 100644 --- a/src/saml2/client_base.py +++ b/src/saml2/client_base.py @@ -10,6 +10,8 @@ import six 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 @@ -20,6 +22,7 @@ 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 import saml2 import time @@ -357,6 +360,59 @@ 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 not extensions: + extensions = Extensions() + + attributemapsmods = [] + for modname in attributemaps.__all__: + attributemapsmods.append(getattr(attributemaps, modname)) + + items = [] + for attr in requested_attrs: + friendly_name = attr.get('friendly_name') + name = attr.get('name') + name_format = attr.get('name_format') + is_required = str(attr.get('required', False)).lower() + + if not name and not friendly_name: + raise ValueError( + "Missing required attribute: '{}' or '{}'".format( + 'name', 'friendly_name')) + + if not name: + for mod in attributemapsmods: + try: + name = mod.MAP['to'][friendly_name] + except KeyError: + continue + else: + if not name_format: + name_format = mod.MAP['identifier'] + break + + if not friendly_name: + for mod in attributemapsmods: + try: + friendly_name = mod.MAP['fro'][name] + except KeyError: + continue + else: + if not name_format: + name_format = mod.MAP['identifier'] + break + + items.append(requested_attributes.RequestedAttribute( + is_required=is_required, + name_format=name_format, + friendly_name=friendly_name, + name=name)) + + item = requested_attributes.RequestedAttributes( + extension_elements=items) + extensions.add_extension_element(item) + if kwargs: _args, extensions = self._filter_args(AuthnRequest(), extensions, **kwargs) diff --git a/src/saml2/config.py b/src/saml2/config.py index 3c8618f4..296f0e85 100644 --- a/src/saml2/config.py +++ b/src/saml2/config.py @@ -80,6 +80,7 @@ SP_ARGS = [ "force_authn", "sp_type", "sp_type_in_metadata", + "requested_attributes", ] AA_IDP_ARGS = [ diff --git a/src/saml2/extension/requested_attributes.py b/src/saml2/extension/requested_attributes.py new file mode 100644 index 00000000..3d574f15 --- /dev/null +++ b/src/saml2/extension/requested_attributes.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python + +# +# Generated Tue Jul 18 14:58:29 2017 by parse_xsd.py version 0.5. +# + +import saml2 +from saml2 import SamlBase + +from saml2 import saml + + +NAMESPACE = 'http://eidas.europa.eu/saml-extensions' + +class RequestedAttributeType_(SamlBase): + """The http://eidas.europa.eu/saml-extensions:RequestedAttributeType element """ + + c_tag = 'RequestedAttributeType' + c_namespace = NAMESPACE + c_children = SamlBase.c_children.copy() + c_attributes = SamlBase.c_attributes.copy() + c_child_order = SamlBase.c_child_order[:] + c_cardinality = SamlBase.c_cardinality.copy() + c_children['{urn:oasis:names:tc:SAML:2.0:assertion}AttributeValue'] = ('attribute_value', [saml.AttributeValue]) + c_cardinality['attribute_value'] = {"min":0} + c_attributes['Name'] = ('name', 'None', True) + c_attributes['NameFormat'] = ('name_format', 'None', True) + c_attributes['FriendlyName'] = ('friendly_name', 'None', False) + c_attributes['isRequired'] = ('is_required', 'None', False) + c_child_order.extend(['attribute_value']) + + def __init__(self, + attribute_value=None, + name=None, + name_format=None, + friendly_name=None, + is_required=None, + text=None, + extension_elements=None, + extension_attributes=None, + ): + SamlBase.__init__(self, + text=text, + extension_elements=extension_elements, + extension_attributes=extension_attributes, + ) + self.attribute_value=attribute_value or [] + self.name=name + self.name_format=name_format + self.friendly_name=friendly_name + self.is_required=is_required + +def requested_attribute_type__from_string(xml_string): + return saml2.create_class_from_xml_string(RequestedAttributeType_, xml_string) + + +class RequestedAttribute(RequestedAttributeType_): + """The http://eidas.europa.eu/saml-extensions:RequestedAttribute element """ + + c_tag = 'RequestedAttribute' + c_namespace = NAMESPACE + c_children = RequestedAttributeType_.c_children.copy() + c_attributes = RequestedAttributeType_.c_attributes.copy() + c_child_order = RequestedAttributeType_.c_child_order[:] + c_cardinality = RequestedAttributeType_.c_cardinality.copy() + +def requested_attribute_from_string(xml_string): + return saml2.create_class_from_xml_string(RequestedAttribute, xml_string) + + +class RequestedAttributesType_(SamlBase): + """The http://eidas.europa.eu/saml-extensions:RequestedAttributesType element """ + + c_tag = 'RequestedAttributesType' + c_namespace = NAMESPACE + c_children = SamlBase.c_children.copy() + c_attributes = SamlBase.c_attributes.copy() + c_child_order = SamlBase.c_child_order[:] + c_cardinality = SamlBase.c_cardinality.copy() + c_children['{http://eidas.europa.eu/saml-extensions}RequestedAttribute'] = ('requested_attribute', [RequestedAttribute]) + c_cardinality['requested_attribute'] = {"min":0} + c_child_order.extend(['requested_attribute']) + + def __init__(self, + requested_attribute=None, + text=None, + extension_elements=None, + extension_attributes=None, + ): + SamlBase.__init__(self, + text=text, + extension_elements=extension_elements, + extension_attributes=extension_attributes, + ) + self.requested_attribute=requested_attribute or [] + +def requested_attributes_type__from_string(xml_string): + return saml2.create_class_from_xml_string(RequestedAttributesType_, xml_string) + + +class RequestedAttributes(RequestedAttributesType_): + """The http://eidas.europa.eu/saml-extensions:RequestedAttributes element """ + + c_tag = 'RequestedAttributes' + c_namespace = NAMESPACE + c_children = RequestedAttributesType_.c_children.copy() + c_attributes = RequestedAttributesType_.c_attributes.copy() + c_child_order = RequestedAttributesType_.c_child_order[:] + c_cardinality = RequestedAttributesType_.c_cardinality.copy() + +def requested_attributes_from_string(xml_string): + return saml2.create_class_from_xml_string(RequestedAttributes, xml_string) + + +ELEMENT_FROM_STRING = { + RequestedAttributes.c_tag: requested_attributes_from_string, + RequestedAttributesType_.c_tag: requested_attributes_type__from_string, + RequestedAttribute.c_tag: requested_attribute_from_string, + RequestedAttributeType_.c_tag: requested_attribute_type__from_string, +} + +ELEMENT_BY_TAG = { + 'RequestedAttributes': RequestedAttributes, + 'RequestedAttributesType': RequestedAttributesType_, + 'RequestedAttribute': RequestedAttribute, + 'RequestedAttributeType': RequestedAttributeType_, +} + + +def factory(tag, **kwargs): + return ELEMENT_BY_TAG[tag](**kwargs) diff --git a/tests/server_conf.py b/tests/server_conf.py index aa34d8f7..4b528119 100644 --- a/tests/server_conf.py +++ b/tests/server_conf.py @@ -14,6 +14,19 @@ CONFIG = { "required_attributes": ["surName", "givenName", "mail"], "optional_attributes": ["title"], "idp": ["urn:mace:example.com:saml:roland:idp"], + "requested_attributes": [ + { + "name": "http://eidas.europa.eu/attributes/naturalperson/DateOfBirth", + "required": False, + }, + { + "friendly_name": "PersonIdentifier", + "required": True, + }, + { + "friendly_name": "PlaceOfBirth", + }, + ], } }, "debug": 1, diff --git a/tests/test_51_client.py b/tests/test_51_client.py index bcc535af..2bd4d7cf 100644 --- a/tests/test_51_client.py +++ b/tests/test_51_client.py @@ -22,6 +22,8 @@ from saml2 import samlp from saml2 import sigver from saml2 import s_utils from saml2.assertion import Assertion +from saml2.extension.requested_attributes import RequestedAttributes +from saml2.extension.requested_attributes import RequestedAttribute from saml2.authn_context import INTERNETPROTOCOLPASSWORD from saml2.client import Saml2Client @@ -280,6 +282,20 @@ class TestClient: assert nid_policy.allow_create == "false" assert nid_policy.format == saml.NAMEID_FORMAT_TRANSIENT + 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 + + for c in node_requested_attributes.children: + assert c.tag == RequestedAttribute.c_tag + assert c.attributes['isRequired'] in ['true', 'false'] + assert c.attributes['Name'] + assert c.attributes['FriendlyName'] + assert c.attributes['NameFormat'] + def test_create_auth_request_unset_force_authn(self): req_id, req = self.client.create_authn_request( "http://www.example.com/sso", sign=False, message_id="id1") diff --git a/tools/data/requested_attributes.xsd b/tools/data/requested_attributes.xsd new file mode 100644 index 00000000..b796f3d3 --- /dev/null +++ b/tools/data/requested_attributes.xsd @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xsd:schema + xmlns="http://eidas.europa.eu/saml-extensions" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" + xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" + xmlns:eidas="http://eidas.europa.eu/saml-extensions" + targetNamespace="http://eidas.europa.eu/saml-extensions" + elementFormDefault="qualified" + attributeFormDefault="unqualified" + version="1"> + <xsd:element name="RequestedAttributes" type="eidas:RequestedAttributesType"/> + <xsd:complexType name="RequestedAttributesType"> + <xsd:sequence> + <xsd:element minOccurs="0" maxOccurs="unbounded" ref="eidas:RequestedAttribute"/> + </xsd:sequence> + </xsd:complexType> + <xsd:element name="RequestedAttribute" type="eidas:RequestedAttributeType"/> + <xsd:complexType name="RequestedAttributeType"> + <xsd:sequence> + <xsd:element minOccurs="0" maxOccurs="unbounded" ref="saml2:AttributeValue" type="anyType"/> + </xsd:sequence> + <xsd:attribute name="Name" type="string" use="required"/> + <xsd:attribute name="NameFormat" type="anyURI" use="required"/> + <xsd:attribute name="FriendlyName" type="string" use="optional"/> + <xsd:anyAttribute namespace="##other" processContents="lax"/> + <xsd:attribute name="isRequired" type="boolean" use="optional"/> + </xsd:complexType> +</xsd:schema> |