Beispiel #1
0
    def validate(self, request):
        xmlstr = request.saml_request
        data = saml_to_dict(xmlstr)
        if self._action == 'login':
            req_type = 'AuthnRequest'
        elif self._action == 'logout':
            req_type = 'LogoutRequest'
        issuer_name = data.get(
            '{urn:oasis:names:tc:SAML:2.0:protocol}%s' % (req_type),
            {}).get('children',
                    {}).get('{urn:oasis:names:tc:SAML:2.0:assertion}Issuer',
                            {}).get('text')
        if issuer_name is None:
            raise UnknownEntityIDError(
                'Issuer non presente nella {}'.format(req_type))
        try:
            sp_metadata = self._registry.load(issuer_name)
        except MetadataNotFoundError:
            raise UnknownEntityIDError(
                'L\'entity ID "{}" indicato nell\'elemento <Issuer> non corrisponde a nessun Service Provider registrato in questo Identity Provider di test.'
                .format(issuer_name))

        atcss = sp_metadata.attribute_consuming_services
        attribute_consuming_service_indexes = [
            str(el.get('attrs').get('index')) for el in atcss
            if 'index' in el.get('attrs', {})
        ]
        ascss = sp_metadata.assertion_consumer_services
        assertion_consumer_service_indexes = [
            str(el.get('index')) for el in ascss
        ]
        assertion_consumer_service_urls = [
            str(el.get('Location')) for el in ascss
        ]

        entity_id = self._config.entity_id

        issuer = Schema(
            {
                'attrs': {
                    'Format':
                    Equal(
                        NAMEID_FORMAT_ENTITY,
                        msg=DEFAULT_VALUE_ERROR.format(NAMEID_FORMAT_ENTITY)),
                    'NameQualifier':
                    Any(Url(), Match(r'^urn:'), msg="Invalid URI"),
                },
                'children': {},
                'text': str,
            },
            required=True,
        )

        name_id = Schema(
            {
                'attrs': {
                    'NameQualifier':
                    str,
                    'Format':
                    Equal(NAMEID_FORMAT_TRANSIENT,
                          msg=DEFAULT_VALUE_ERROR.format(
                              NAMEID_FORMAT_TRANSIENT)),
                },
                'children': {},
                'text': str
            },
            required=True,
        )

        name_id_policy = Schema(
            {
                'attrs': {
                    'Format':
                    Equal(NAMEID_FORMAT_TRANSIENT,
                          msg=DEFAULT_VALUE_ERROR.format(
                              NAMEID_FORMAT_TRANSIENT)),
                    Optional('SPNameQualifier'):
                    str,
                },
                'children': {},
                'text': None,
            },
            required=True,
        )

        conditions = Schema(
            {
                'attrs': {
                    'NotBefore': All(str, _check_utc_date),
                    'NotOnOrAfter': All(str, _check_utc_date),
                },
                'children': {},
                'text': None,
            },
            required=True,
        )

        authn_context_class_ref = Schema(
            {
                'attrs': {},
                'children': {},
                'text':
                All(
                    str,
                    In(SPID_LEVELS,
                       msg=DEFAULT_LIST_VALUE_ERROR.format(
                           ', '.join(SPID_LEVELS))))
            },
            required=True,
        )

        requested_authn_context = Schema(
            {
                'attrs': {
                    'Comparison': str
                },
                'children': {
                    '{%s}AuthnContextClassRef' % (ASSERTION):
                    authn_context_class_ref
                },
                'text': None
            },
            required=True,
        )

        scoping = Schema(
            {
                'attrs': {
                    'ProxyCount': Equal('0',
                                        msg=DEFAULT_VALUE_ERROR.format('0'))
                },
                'children': {},
                'text': None
            },
            required=True,
        )

        signature = Schema(
            {
                'attrs': dict,
                'children': dict,
                'text': None
            },
            required=True,
        )

        subject = Schema(
            {
                'attrs': {
                    'Format':
                    Equal(
                        NAMEID_FORMAT_ENTITY,
                        msg=DEFAULT_VALUE_ERROR.format(NAMEID_FORMAT_ENTITY)),
                    'NameQualifier':
                    str
                },
                'children': {},
                'text': None
            },
            required=True,
        )

        # LOGIN

        def check_assertion_consumer_service(attrs):
            keys = list(attrs.keys())
            if ('AssertionConsumerServiceURL' in keys
                    and 'ProtocolBinding' in keys
                    and 'AssertionConsumerServiceIndex' not in keys):
                _errors = []
                if attrs['ProtocolBinding'] != BINDING_HTTP_POST:
                    _errors.append(
                        Invalid(DEFAULT_VALUE_ERROR.format(BINDING_HTTP_POST),
                                path=['ProtocolBinding']))
                if attrs[
                        'AssertionConsumerServiceURL'] not in assertion_consumer_service_urls:
                    _errors.append(
                        Invalid(DEFAULT_VALUE_ERROR.format(
                            assertion_consumer_service_urls),
                                path=['AssertionConsumerServiceURL']))
                if _errors:
                    raise MultipleInvalid(errors=_errors)
                return attrs

            elif ('AssertionConsumerServiceURL' not in keys
                  and 'ProtocolBinding' not in keys
                  and 'AssertionConsumerServiceIndex' in keys):
                if attrs[
                        'AssertionConsumerServiceIndex'] not in assertion_consumer_service_indexes:
                    raise Invalid(DEFAULT_LIST_VALUE_ERROR.format(
                        ', '.join(assertion_consumer_service_indexes)),
                                  path=['AssertionConsumerServiceIndex'])
                return attrs

            else:
                raise Invalid(
                    'Uno e uno solo uno tra gli attributi o gruppi di attributi devono essere presenti: '
                    '[AssertionConsumerServiceIndex, [AssertionConsumerServiceUrl, ProtocolBinding]]'
                )

        authnrequest_attr_schema = Schema(All(
            {
                'ID':
                str,
                'Version':
                Equal('2.0', msg=DEFAULT_VALUE_ERROR.format('2.0')),
                'IssueInstant':
                All(str, _check_utc_date, self._check_date_in_range),
                'Destination':
                In([entity_id, self._config.absolute_sso_url],
                   msg=DEFAULT_VALUE_ERROR.format(entity_id)),
                Optional('ForceAuthn'):
                str,
                Optional('AttributeConsumingServiceIndex'):
                In(attribute_consuming_service_indexes,
                   msg=DEFAULT_LIST_VALUE_ERROR.format(
                       ', '.join(attribute_consuming_service_indexes))),
                Optional('AssertionConsumerServiceIndex'):
                str,
                Optional('AssertionConsumerServiceURL'):
                str,
                Optional('ProtocolBinding'):
                str,
            },
            check_assertion_consumer_service,
        ),
                                          required=True)

        AUTHNREQUEST_TAG = '{%s}AuthnRequest' % (PROTOCOL)

        authnrequest_schema = {
            AUTHNREQUEST_TAG: {
                'attrs':
                authnrequest_attr_schema,
                'children':
                Schema(
                    {
                        Optional('{%s}Subject' % (ASSERTION)): subject,
                        '{%s}Issuer' % (ASSERTION): issuer,
                        '{%s}NameIDPolicy' % (PROTOCOL): name_id_policy,
                        Optional('{%s}Conditions' % (ASSERTION)): conditions,
                        '{%s}RequestedAuthnContext' % (PROTOCOL):
                        requested_authn_context,
                        Optional('{%s}Scoping' % (PROTOCOL)): scoping,
                    },
                    required=True,
                ),
                'text':
                None
            }
        }

        # LOGOUT

        LOGOUTREQUEST_TAG = '{%s}LogoutRequest' % (PROTOCOL)

        logoutrequest_attr_schema = Schema(All({
            'ID':
            str,
            'Version':
            Equal('2.0', msg=DEFAULT_VALUE_ERROR.format('2.0')),
            'IssueInstant':
            All(str, _check_utc_date, self._check_date_in_range),
            'Destination':
            In([entity_id, self._config.absolute_slo_url],
               msg=DEFAULT_VALUE_ERROR.format(entity_id)),
            Optional('NotOnOrAfter'):
            All(str, _check_utc_date, self._check_date_not_expired),
            Optional('Reason'):
            str,
        }),
                                           required=True)

        logoutrequest_schema = {
            LOGOUTREQUEST_TAG: {
                'attrs':
                logoutrequest_attr_schema,
                'children':
                Schema(
                    {
                        '{%s}Issuer' % (ASSERTION): issuer,
                        '{%s}NameID' % (ASSERTION): name_id,
                        '{%s}SessionIndex' % (PROTOCOL): dict,
                    },
                    required=True),
                'text':
                None
            }
        }

        if self._binding == BINDING_HTTP_POST:
            if self._action == 'login':
                # Add signature schema
                _new_sub_schema = authnrequest_schema[AUTHNREQUEST_TAG][
                    'children'].extend(
                        {'{%s}Signature' % (SIGNATURE): signature})
                authnrequest_schema[AUTHNREQUEST_TAG][
                    'children'] = _new_sub_schema
            if self._action == 'logout':
                _new_sub_schema = logoutrequest_schema[LOGOUTREQUEST_TAG][
                    'children'].extend(
                        {'{%s}Signature' % (SIGNATURE): signature})
                logoutrequest_schema[LOGOUTREQUEST_TAG][
                    'children'] = _new_sub_schema

        authn_request = Schema(
            authnrequest_schema,
            required=True,
        )

        logout_request = Schema(
            logoutrequest_schema,
            required=True,
        )

        saml_schema = None
        if self._action == 'login':
            saml_schema = authn_request
        elif self._action == 'logout':
            saml_schema = logout_request
        errors = []
        try:
            saml_schema(data)
        except MultipleInvalid as e:
            for err in e.errors:
                _paths = []
                _attr = None
                for idx, _path in enumerate(err.path):
                    if _path != 'children':
                        if _path == 'attrs':
                            try:
                                _attr = err.path[(idx + 1)]
                            except IndexError:
                                _attr = ''
                            break

                        # strip namespaces for better readability
                        _paths.append(_strip_namespaces(str(_path)))
                path = '/'.join(_paths)
                if _attr is not None:
                    path += " - attribute: " + _attr

                # find value to show (iterate multiple times inside data
                # until we find the sub-element or attribute)
                _val = data
                for _ in err.path:
                    try:
                        _val = _val[_]
                    except KeyError:
                        _val = None
                    except ValueError:
                        _val = None

                # no need to show value if the error is the presence of the element
                _msg = err.msg
                if "extra keys not allowed" in _msg:
                    _val = None
                    _msg = "item not allowed"

                errors.append(
                    ValidationDetail(_val, None, None, None, None, _msg, path))
            raise SPIDValidationError(details=errors)
Beispiel #2
0
    def validate(self, request):
        xmlstr = request.saml_request
        data = saml_to_dict(xmlstr)
        atcss = []
        if self._action == 'login':
            req_type = 'AuthnRequest'
            service = 'single_sign_on_service'
        elif self._action == 'logout':
            req_type = 'LogoutRequest'
            service = 'single_logout_service'
        issuer_name = data.get(
            '{urn:oasis:names:tc:SAML:2.0:protocol}%s' % (req_type),
            {}).get('children',
                    {}).get('{urn:oasis:names:tc:SAML:2.0:assertion}Issuer',
                            {}).get('text')
        if issuer_name and issuer_name not in self._metadata.service_providers(
        ):
            raise UnknownEntityIDError(
                'entity ID {} non registrato'.format(issuer_name))
        for k, _md in self._metadata.items():
            if k == issuer_name:
                _srvs = _md.get('spsso_descriptor', [])
                for _srv in _srvs:
                    for _acs in _srv.get('attribute_consuming_service', []):
                        atcss.append(_acs)
        try:
            ascss = self._metadata.assertion_consumer_service(issuer_name)
        except Exception:
            ascss = []
        except Exception:
            ascss = []
        attribute_consuming_service_indexes = [
            str(el.get('index')) for el in atcss
        ]
        assertion_consumer_service_indexes = [
            str(el.get('index')) for el in ascss
        ]
        receivers = self._config.receivers(service)

        issuer = Schema(
            {
                'attrs': {
                    'Format':
                    Equal(
                        NAMEID_FORMAT_ENTITY,
                        msg=DEFAULT_VALUE_ERROR.format(NAMEID_FORMAT_ENTITY)),
                    'NameQualifier':
                    Equal(issuer_name,
                          msg=DEFAULT_VALUE_ERROR.format(issuer_name)),
                },
                'children': {},
                'text': str,
            },
            required=True,
        )

        name_id = Schema(
            {
                'attrs': {
                    'NameQualifier':
                    str,
                    'Format':
                    Equal(NAMEID_FORMAT_TRANSIENT,
                          msg=DEFAULT_VALUE_ERROR.format(
                              NAMEID_FORMAT_TRANSIENT)),
                },
                'children': {},
                'text': str
            },
            required=True,
        )

        name_id_policy = Schema(
            {
                'attrs': {
                    'Format':
                    Equal(NAMEID_FORMAT_TRANSIENT,
                          msg=DEFAULT_VALUE_ERROR.format(
                              NAMEID_FORMAT_TRANSIENT)),
                },
                'children': {},
                'text': None,
            },
            required=True,
        )

        conditions = Schema(
            {
                'attrs': {
                    'NotBefore': All(str, self._check_utc_date),
                    'NotOnOrAfter': All(str, self._check_utc_date),
                },
                'children': {},
                'text': None,
            },
            required=True,
        )

        authn_context_class_ref = Schema(
            {
                'attrs': {},
                'children': {},
                'text':
                All(
                    str,
                    In(SPID_LEVELS,
                       msg=DEFAULT_LIST_VALUE_ERROR.format(SPID_LEVELS)))
            },
            required=True,
        )

        requested_authn_context = Schema(
            {
                'attrs': {
                    'Comparison': str
                },
                'children': {
                    '{%s}AuthnContextClassRef' % (ASSERTION):
                    authn_context_class_ref
                },
                'text': None
            },
            required=True,
        )

        scoping = Schema(
            {
                'attrs': {
                    'ProxyCount': Equal('0',
                                        msg=DEFAULT_VALUE_ERROR.format('0'))
                },
                'children': {},
                'text': None
            },
            required=True,
        )

        signature = Schema(
            {
                'attrs': dict,
                'children': dict,
                'text': None
            },
            required=True,
        )

        subject = Schema(
            {
                'attrs': {
                    'Format':
                    Equal(
                        NAMEID_FORMAT_ENTITY,
                        msg=DEFAULT_VALUE_ERROR.format(NAMEID_FORMAT_ENTITY)),
                    'NameQualifier':
                    str
                },
                'children': {},
                'text': None
            },
            required=True,
        )

        # LOGIN

        def check_assertion_consumer_service(attrs):
            keys = attrs.keys()
            if ('AssertionConsumerServiceURL' in keys
                    and 'ProtocolBinding' in keys
                    and 'AssertionConsumerServiceIndex' not in keys):
                if attrs['ProtocolBinding'] != BINDING_HTTP_POST:
                    raise Invalid(
                        DEFAULT_VALUE_ERROR.format(BINDING_HTTP_POST),
                        path=['ProtocolBinding'])
                return attrs

            elif ('AssertionConsumerServiceURL' not in keys
                  and 'ProtocolBinding' not in keys
                  and 'AssertionConsumerServiceIndex' in keys):
                if attrs[
                        'AssertionConsumerServiceIndex'] not in assertion_consumer_service_indexes:
                    raise Invalid(DEFAULT_LIST_VALUE_ERROR.format(
                        assertion_consumer_service_indexes),
                                  path=['AssertionConsumerServiceIndex'])
                return attrs

            else:
                raise Invalid(
                    'Uno e uno solo uno tra gli attributi o gruppi di attributi devono essere presenti: '
                    '[AssertionConsumerServiceIndex, [AssertionConsumerServiceUrl, ProtocolBinding]]'
                )

        authnrequest_attr_schema = Schema(All(
            {
                'ID':
                str,
                'Version':
                Equal('2.0', msg=DEFAULT_VALUE_ERROR.format('2.0')),
                'IssueInstant':
                All(str, self._check_utc_date, self._check_date_in_range),
                'Destination':
                In(receivers, msg=DEFAULT_LIST_VALUE_ERROR.format(receivers)),
                Optional('ForceAuthn'):
                str,
                Optional('AttributeConsumingServiceIndex'):
                In(attribute_consuming_service_indexes,
                   msg=DEFAULT_LIST_VALUE_ERROR.format(
                       attribute_consuming_service_indexes)),
                Optional('AssertionConsumerServiceIndex'):
                str,
                Optional('AssertionConsumerServiceURL'):
                str,
                Optional('ProtocolBinding'):
                str,
            },
            check_assertion_consumer_service,
        ),
                                          required=True)

        authnrequest_schema = {
            '{%s}AuthnRequest' % (PROTOCOL): {
                'attrs':
                authnrequest_attr_schema,
                'children':
                Schema(
                    {
                        Optional('{%s}Subject' % (ASSERTION)): subject,
                        '{%s}Issuer' % (ASSERTION): issuer,
                        '{%s}NameIDPolicy' % (PROTOCOL): name_id_policy,
                        Optional('{%s}Conditions' % (ASSERTION)): conditions,
                        '{%s}RequestedAuthnContext' % (PROTOCOL):
                        requested_authn_context,
                        Optional('{%s}Scoping' % (PROTOCOL)): scoping,
                    },
                    required=True,
                ),
                'text':
                None
            }
        }

        if self._binding == BINDING_HTTP_POST:
            authnrequest_schema['{%s}AuthnRequest' %
                                (PROTOCOL)]['children'].extend = {
                                    '{%s}Signature' % (SIGNATURE): signature
                                }

        authn_request = Schema(
            authnrequest_schema,
            required=True,
        )

        # LOGOUT

        logout_request = Schema(
            {
                '{%s}LogoutRequest' % (PROTOCOL): {
                    'attrs': {
                        'ID':
                        str,
                        'Version':
                        Equal('2.0', msg=DEFAULT_VALUE_ERROR.format('2.0')),
                        'IssueInstant':
                        All(str, self._check_utc_date,
                            self._check_date_in_range),
                        'Destination':
                        In(receivers,
                           msg=DEFAULT_LIST_VALUE_ERROR.format(receivers)),
                    },
                    'children': {
                        '{%s}Issuer' % (ASSERTION): issuer,
                        '{%s}NameID' % (ASSERTION): name_id,
                        '{%s}SessionIndex' % (PROTOCOL): dict,
                    },
                    'text': None
                }
            },
            required=True,
        )

        saml_schema = None
        if self._action == 'login':
            saml_schema = authn_request
        elif self._action == 'logout':
            saml_schema = logout_request
        errors = []
        try:
            saml_schema(data)
        except MultipleInvalid as e:
            for err in e.errors:
                _val = data
                _paths = []
                _attr = None
                for idx, _path in enumerate(err.path):
                    if _path != 'children':
                        if _path == 'attrs':
                            try:
                                _attr = err.path[(idx + 1)]
                            except IndexError:
                                _attr = ''
                            break
                        _paths.append(_path)
                path = '/'.join(_paths)
                path = 'xpath: {}'.format(path)
                if _attr is not None:
                    path = '{} - attribute: {}'.format(path, _attr)
                for _ in err.path:
                    _val = _val.get(_)
                errors.append(
                    ValidationDetail(_val, None, None, None, None, err.msg,
                                     path))
            raise SPIDValidationError(details=errors)
Beispiel #3
0
    def validate(self, metadata):
        data = saml_to_dict(metadata)
        key_descriptor = Schema(
            All([{
                'attrs':
                Schema(
                    {
                        'use':
                        All(
                            str,
                            In(KEYDESCRIPTOR_USES,
                               msg=DEFAULT_LIST_VALUE_ERROR.format(
                                   ', '.join(KEYDESCRIPTOR_USES)))),
                    },
                    required=True),
                'children': {
                    '{%s}KeyInfo' % (SIGNATURE): {
                        'attrs': {},
                        'children': {
                            '{%s}X509Data' % (SIGNATURE): {
                                'attrs': {},
                                'children': {
                                    '{%s}X509Certificate' % (SIGNATURE): {
                                        'attrs': {},
                                        'children': {},
                                        'text': All(str, _check_certificate)
                                    }
                                },
                                'text': None
                            }
                        },
                        'text': None
                    }
                },
                'text':
                None
            }], self._check_keydescriptor),
            required=True,
        )
        slo = Schema(
            All([{
                'attrs': dict,
                'children': dict,
                'text': None
            }], Length(min=1)),
            required=True,
        )
        acs = Schema(
            All([{
                'attrs': dict,
                'children': dict,
                'text': None
            }], Length(min=1)),
            required=True,
        )
        atcs = Schema(
            All([{
                'attrs': {
                    'index': str
                },
                'children': {
                    '{%s}ServiceName' % (METADATA): {
                        'attrs': dict,
                        'children': {},
                        'text': str
                    },
                    Optional('{%s}ServiceDescription' % (METADATA)): {
                        'attrs': dict,
                        'children': {},
                        'text': str
                    },
                    '{%s}RequestedAttribute' % (METADATA):
                    All([{
                        'attrs': {
                            'Name':
                            All(
                                str,
                                In(SPID_ATTRIBUTES_NAMES,
                                   msg=DEFAULT_LIST_VALUE_ERROR.format(
                                       ', '.join(SPID_ATTRIBUTES_NAMES)))),
                            Optional('NameFormat'):
                            Equal(NAME_FORMAT_BASIC,
                                  msg=DEFAULT_VALUE_ERROR.format(
                                      NAME_FORMAT_BASIC)),
                            Optional('FriendlyName'):
                            str,
                            Optional('isRequired'):
                            str
                        },
                        'children': {},
                        'text': None
                    }], Length(min=1)),
                },
                'text': None
            }], Length(min=1)),
            required=True,
        )
        name_id_format = Schema(
            {
                'attrs': {},
                'children': {},
                'text':
                Equal(NAMEID_FORMAT_TRANSIENT,
                      msg=DEFAULT_VALUE_ERROR.format(NAMEID_FORMAT_TRANSIENT)),
            },
            required=True,
        )
        spsso_descriptor_attr_schema = Schema(All({
            'protocolSupportEnumeration':
            Equal(PROTOCOL, msg=DEFAULT_VALUE_ERROR.format(PROTOCOL)),
            'AuthnRequestsSigned':
            Equal('true', msg=DEFAULT_VALUE_ERROR.format('true')),
            Optional('WantAssertionsSigned'):
            str,
        }),
                                              required=True)
        spsso = Schema(
            {
                'attrs': spsso_descriptor_attr_schema,
                'children': {
                    '{%s}KeyDescriptor' % (METADATA): key_descriptor,
                    '{%s}SingleLogoutService' % (METADATA): slo,
                    '{%s}AssertionConsumerService' % (METADATA): acs,
                    '{%s}AttributeConsumingService' % (METADATA): atcs,
                    '{%s}NameIDFormat' % (METADATA): name_id_format,
                },
                'text': None
            },
            required=True)
        entity_descriptor_schema = Schema(
            {
                '{%s}EntityDescriptor' % (METADATA): {
                    'attrs':
                    Schema(
                        {
                            'entityID': str,
                            Optional('ID'): str,
                            Optional('validUntil'): All(str, _check_utc_date),
                            Optional('cacheDuration'): str,
                            Optional('Name'): str,
                        },
                        required=True),
                    'children':
                    Schema(
                        {
                            Optional('{%s}Signature' % (SIGNATURE)):
                            Schema(
                                {
                                    'attrs': dict,
                                    'children': dict,
                                    'text': None
                                },
                                required=True,
                            ),
                            '{%s}SPSSODescriptor' % (METADATA):
                            spsso,
                            Optional('{%s}Organization' % (METADATA)):
                            dict,
                            Optional('{%s}ContactPerson' % (METADATA)):
                            list
                        },
                        required=True),
                    'text':
                    None
                }
            },
            required=True)
        errors = []
        try:
            entity_descriptor_schema(data)
        except MultipleInvalid as e:
            for err in e.errors:
                _val = data
                _paths = []
                _attr = None
                for idx, _path in enumerate(err.path):
                    if _path != 'children':
                        if _path == 'attrs':
                            try:
                                _attr = err.path[(idx + 1)]
                            except IndexError:
                                _attr = ''
                            break

                        # strip namespaces for better readability
                        _paths.append(_strip_namespaces(str(_path)))
                path = '/'.join(_paths)
                if _attr is not None:
                    path = '{} - attribute: {}'.format(path, _attr)
                for _ in err.path:
                    try:
                        _val = _val[_]
                    except IndexError:
                        _val = None
                    except KeyError:
                        _val = None
                errors.append(
                    ValidationDetail(_val, None, None, None, None, err.msg,
                                     path))
            raise SPIDValidationError(details=errors)