Example #1
0
 def continue_response(self):
     key = request.form['request_key']
     if key and key in self.responses and key in self.responses:
         _response = self.responses.pop(key)
         auth_req = self.ticket.pop(key)
         if 'confirm' in request.form:
             return _response, 200
         elif 'delete' in request.form:
             destination = self.get_destination(
                 auth_req, auth_req.message.issuer.text)
             error_response = self.server.create_error_response(
                 in_response_to=auth_req.message.id,
                 destination=destination,
                 info=get_spid_error(AUTH_NO_CONSENT))
             self.app.logger.debug('Error response: \n{}'.format(
                 prettify_xml(str(error_response))))
             http_args = self.server.apply_binding(
                 BINDING_HTTP_POST,
                 error_response,
                 destination,
                 response=True,
                 sign=True,
             )
             return http_args['data'], 200
     return render_template('403.html'), 403
Example #2
0
 def _handle_errors(self, errors, xmlstr):
     _escaped_xml = escape(prettify_xml(xmlstr.decode()))
     rendered_error_response = render_template_string(
         spid_error_table, **{
             'lines': _escaped_xml.splitlines(),
             'errors': errors
         })
     return rendered_error_response
Example #3
0
 def _handle_errors(self, xmlstr, errors={}):
     _escaped_xml = escape(prettify_xml(xmlstr.decode()))
     rendered_error_response = render_template(
         'spid_error.html', **{
             'lines': _escaped_xml.splitlines(),
             'spid_errors': errors.get('spid_errors', []),
             'validation_errors': errors.get('validation_errors', [])
         })
     return rendered_error_response
Example #4
0
    def single_logout_service(self):
        """
        SLO endpoint
        """

        self.app.logger.debug("req: '%s'", request)
        saml_msg = self.unpack_args(request.args)
        try:
            req_info, _binding = self._parse_message(action='logout')
            msg = req_info.message
            self.app.logger.debug('LogoutRequest: \n{}'.format(
                prettify_xml(str(msg))))
            issuer_name = req_info.issuer.text
            extra = {}
            extra['receivers'] = req_info.receiver_addrs
            _, errors = self._check_spid_restrictions(req_info, 'logout',
                                                      _binding, **extra)
        except UnknownBinding as err:
            self.app.logger.debug(str(err))
            self._raise_error(
                'Binding non supportato. Formati supportati ({}, {})'.format(
                    BINDING_HTTP_POST, BINDING_HTTP_REDIRECT))
        except UnknownSystemEntity as err:
            self.app.logger.debug(str(err))
            self._raise_error(
                'entity ID {} non registrato.'.format(issuer_name))
        except IncorrectlySigned as err:
            self.app.logger.debug(str(err))
            self._raise_error(
                'Messaggio corrotto o non firmato correttamente.')

        if errors:
            return self._handle_errors(errors, req_info.xmlstr)

        # Check if it is signed
        if _binding == BINDING_HTTP_REDIRECT:
            self._verify_redirect(saml_msg, issuer_name)
        _slo = self._sp_single_logout_service(issuer_name)
        if _slo is None:
            self._raise_error(
                'Impossibile trovare un servizio di'\
                ' Single Logout per il service provider {}'.format(
                    issuer_name
                )
            )
        response_binding = _slo[0].get('binding')
        self.app.logger.debug(
            'Response binding: \n{}'.format(response_binding))
        _signing = True if response_binding == BINDING_HTTP_POST else False
        self.app.logger.debug(
            'Signature inside response: \n{}'.format(_signing))
        response = self.server.create_logout_response(msg, [response_binding],
                                                      sign_alg=SIGN_ALG,
                                                      digest_alg=DIGEST_ALG,
                                                      sign=_signing)
        self.app.logger.debug('Response: \n{}'.format(response))
        binding, destination = self.server.pick_binding(
            "single_logout_service", [response_binding], "spsso", req_info)
        self.app.logger.debug('Destination {}'.format(destination))
        if response_binding == BINDING_HTTP_POST:
            _sign = False
            extra = {}
        else:
            _sign = True
            extra = {'sigalg': SIGN_ALG}

        relay_state = saml_msg.get('RelayState', '')
        http_args = self.server.apply_binding(binding,
                                              "%s" % response,
                                              destination,
                                              response=True,
                                              sign=_sign,
                                              relay_state=relay_state,
                                              **extra)
        if response_binding == BINDING_HTTP_POST:
            self.app.logger.debug('Form post {}'.format(http_args['data']))
            return http_args['data'], 200
        elif response_binding == BINDING_HTTP_REDIRECT:
            headers = dict(http_args['headers'])
            self.app.logger.debug('Headers {}'.format(headers))
            location = headers.get('Location')
            self.app.logger.debug('Location {}'.format(location))
            if location:
                return redirect(location)
        abort(400)
Example #5
0
    def login(self):
        """
        Login endpoint (verify user credentials)
        """
        def from_session(key):
            return session[key] if key in session else None

        key = from_session('request_key')
        relay_state = from_session('relay_state')
        self.app.logger.debug('Request key: {}'.format(key))
        if key and key in self.ticket:
            authn_request = self.ticket[key]
            message = authn_request.message
            sp_id = message.issuer.text
            destination = self.get_destination(authn_request, sp_id)
            authn_context = message.requested_authn_context
            spid_level = authn_context.authn_context_class_ref[0].text
            authn_info = self.authn_broker.pick(authn_context)
            callback, reference = authn_info[0]
            if request.method == 'GET':
                # inject extra data in form login based on spid level
                extra_challenge = callback(**{'key': key})
                rendered_form = render_template(
                    'login.html', **{
                        'action': url_for('login'),
                        'request_key': key,
                        'relay_state': relay_state,
                        'extra_challenge': extra_challenge
                    })
                return rendered_form, 200

            if 'confirm' in request.form:
                # verify optional challenge based on spid level
                verified = callback(verify=True,
                                    **{
                                        'key': key,
                                        'data': request.form
                                    })
                if verified:
                    # verify user credentials
                    user_id, user = self.user_manager.get(
                        request.form['username'], request.form['password'],
                        sp_id)
                    if user_id is not None:
                        # setup response
                        identity = user['attrs'].copy()
                        AUTHN = {
                            "class_ref": spid_level,
                            "authn_auth": spid_level
                        }
                        self.app.logger.debug(
                            'Unfiltered data: {}'.format(identity))
                        atcs_idx = message.attribute_consuming_service_index
                        self.app.logger.debug(
                            'attribute_consuming_service_index: {}'.format(
                                atcs_idx))
                        if atcs_idx:
                            attrs = self.server.wants(sp_id, atcs_idx)
                            required = [
                                Attribute(name=el.get('name'),
                                          friendly_name=None,
                                          name_format=NAME_FORMAT_BASIC)
                                for el in attrs.get('required')
                            ]
                            optional = [
                                Attribute(name=el.get('name'),
                                          friendly_name=None,
                                          name_format=NAME_FORMAT_BASIC)
                                for el in attrs.get('optional')
                            ]
                            acs = ac_factory(
                                './testenv/attributemaps',
                                **{'override_types': self._all_attributes})
                            rava = list_to_local(acs, required)
                            oava = list_to_local(acs, optional)
                        else:
                            rava = {}
                            oava = {}
                        self.app.logger.debug(
                            'Required attributes: {}'.format(rava))
                        self.app.logger.debug(
                            'Optional attributes: {}'.format(oava))
                        identity = filter_on_demands(identity, rava, oava)
                        self.app.logger.debug(
                            'Filtered data: {}'.format(identity))
                        _data = dict(identity=identity,
                                     userid=user_id,
                                     in_response_to=message.id,
                                     destination=destination,
                                     sp_entity_id=sp_id,
                                     authn=AUTHN,
                                     issuer=self.server.config.entityid,
                                     sign_alg=SIGN_ALG,
                                     digest_alg=DIGEST_ALG,
                                     sign_assertion=True,
                                     release_policy=SpidPolicy(restrictions={
                                         'default': {
                                             'name_form': NAME_FORMAT_BASIC,
                                         }
                                     },
                                                               index=atcs_idx))
                        response = self.server.create_authn_response(**_data)
                        self.app.logger.debug(
                            'Response: \n{}'.format(response))
                        http_args = self.server.apply_binding(
                            BINDING_HTTP_POST,
                            response,
                            destination,
                            response=True,
                            sign=True,
                            relay_state=relay_state)
                        # Setup confirmation page data
                        self.responses[key] = http_args['data']
                        rendered_response = render_template(
                            'confirm.html', **{
                                'destination_service':
                                sp_id,
                                'lines':
                                escape(prettify_xml(response)).splitlines(),
                                'attrs':
                                identity.keys(),
                                'action':
                                '/continue-response',
                                'request_key':
                                key
                            })
                        return rendered_response, 200
            elif 'delete' in request.form:
                error_response = self.server.create_error_response(
                    in_response_to=authn_request.message.id,
                    destination=destination,
                    info=get_spid_error(AUTH_NO_CONSENT))
                self.app.logger.debug('Error response: \n{}'.format(
                    prettify_xml(str(error_response))))
                http_args = self.server.apply_binding(BINDING_HTTP_POST,
                                                      error_response,
                                                      destination,
                                                      response=True,
                                                      sign=True,
                                                      relay_state=relay_state)
                del self.ticket[key]
                return http_args['data'], 200
        return render_template('403.html'), 403
Example #6
0
    def single_sign_on_service(self):
        """
        Process Http-Redirect or Http-POST request

        :param request: Flask request object
        """
        # Unpack parameters
        saml_msg = self.unpack_args(request.args)
        try:
            req_info, binding = self._parse_message(action='login')
            authn_req = req_info.message
            self.app.logger.debug('AuthnRequest: \n{}'.format(
                prettify_xml(str(authn_req))))
            extra = {}
            sp_id = authn_req.issuer.text
            issuer_name = authn_req.issuer.text
            if issuer_name and issuer_name not in self.server.metadata.service_providers(
            ):
                raise UnknownSystemEntity
            # TODO: refactor a bit fetching this kind of data from pysaml2
            atcss = []
            for k, _md in self.server.metadata.items():
                if k == sp_id:
                    _srvs = _md.get('spsso_descriptor', [])
                    for _srv in _srvs:
                        for _acs in _srv.get('attribute_consuming_service',
                                             []):
                            atcss.append(_acs)
            try:
                ascss = self.server.metadata.assertion_consumer_service(sp_id)
            except UnknownSystemEntity as err:
                ascss = []
            except UnsupportedBinding as err:
                ascss = []
            atcss_indexes = [str(el.get('index')) for el in atcss]
            ascss_indexes = [str(el.get('index')) for el in ascss]
            extra['issuer'] = issuer_name
            extra['attribute_consuming_service_indexes'] = atcss_indexes
            extra['assertion_consumer_service_indexes'] = ascss_indexes
            extra['receivers'] = req_info.receiver_addrs
            _, errors = self._check_spid_restrictions(req_info, 'login',
                                                      binding, **extra)
        except UnknownBinding as err:
            self.app.logger.debug(str(err))
            self._raise_error(
                'Binding non supportato. Formati supportati ({}, {})'.format(
                    BINDING_HTTP_POST, BINDING_HTTP_REDIRECT))
        except UnknownSystemEntity as err:
            self.app.logger.debug(str(err))
            self._raise_error(
                'entity ID {} non registrato.'.format(issuer_name))
        except IncorrectlySigned as err:
            self.app.logger.debug(str(err))
            self._raise_error(
                'Messaggio corrotto o non firmato correttamente.'.format(
                    issuer_name))

        if errors:
            return self._handle_errors(errors, req_info.xmlstr)

        if not req_info:
            self._raise_error('Processo di parsing del messaggio fallito.')

        # Check if it is signed
        if binding == BINDING_HTTP_REDIRECT:
            self._verify_redirect(saml_msg, issuer_name)
        # Perform login
        key = self._store_request(req_info)
        relay_state = saml_msg.get('RelayState', '')
        session['request_key'] = key
        session['relay_state'] = relay_state
        return redirect(url_for('login'))