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
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
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
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)
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
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'))