def _handle_authn_request(self, context, binding_in, idp): """ See doc for handle_authn_request method. :type context: satosa.context.Context :type binding_in: str :type idp: saml.server.Server :rtype: satosa.response.Response :param context: The current context :param binding_in: The pysaml binding type :param idp: The saml frontend idp server :return: response """ req_info = idp.parse_authn_request(context.request["SAMLRequest"], binding_in) authn_req = req_info.message msg = "{}".format(authn_req) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) # keep the ForceAuthn value to be used by plugins context.decorate(Context.KEY_FORCE_AUTHN, authn_req.force_authn) try: resp_args = idp.response_args(authn_req) except SAMLError as e: msg = "Could not find necessary info about entity: {}".format(e) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.error(logline) return ServiceError("Incorrect request from requester: %s" % e) requester = resp_args["sp_entity_id"] context.state[self.name] = self._create_state_data( context, idp.response_args(authn_req), context.request.get("RelayState")) subject = authn_req.subject name_id_value = subject.name_id.text if subject else None nameid_formats = { "from_policy": authn_req.name_id_policy and authn_req.name_id_policy.format, "from_response": subject and subject.name_id and subject.name_id.format, "from_metadata": (idp.metadata[requester].get("spsso_descriptor", [{}])[0].get("name_id_format", [{}])[0].get("text")), "default": NAMEID_FORMAT_TRANSIENT, } name_id_format = ( nameid_formats["from_policy"] or (nameid_formats["from_response"] != NAMEID_FORMAT_UNSPECIFIED and nameid_formats["from_response"]) or nameid_formats["from_metadata"] or nameid_formats["from_response"] or nameid_formats["default"]) requester_name = self._get_sp_display_name(idp, requester) internal_req = InternalData( subject_id=name_id_value, subject_type=name_id_format, requester=requester, requester_name=requester_name, ) idp_policy = idp.config.getattr("policy", "idp") if idp_policy: internal_req.attributes = self._get_approved_attributes( idp, idp_policy, requester, context.state) return self.auth_req_callback_func(context, internal_req)
def _handle_authn_response(self, context, internal_response, idp): """ See super class satosa.frontends.base.FrontendModule :type context: satosa.context.Context :type internal_response: satosa.internal.InternalData :type idp: saml.server.Server :param context: The current context :param internal_response: The internal response :param idp: The saml frontend idp server :return: A saml response """ request_state = self.load_state(context.state) resp_args = request_state["resp_args"] sp_entity_id = resp_args["sp_entity_id"] internal_response.attributes = self._filter_attributes( idp, internal_response, context) ava = self.converter.from_internal(self.attribute_profile, internal_response.attributes) auth_info = {} if self.acr_mapping: auth_info["class_ref"] = self.acr_mapping.get( internal_response.auth_info.issuer, self.acr_mapping[""]) else: auth_info["class_ref"] = internal_response.auth_info.auth_class_ref auth_info["authn_auth"] = internal_response.auth_info.issuer if self.custom_attribute_release: custom_release = util.get_dict_defaults( self.custom_attribute_release, internal_response.auth_info.issuer, sp_entity_id) attributes_to_remove = custom_release.get("exclude", []) for k in attributes_to_remove: ava.pop(k, None) nameid_value = internal_response.subject_id nameid_format = subject_type_to_saml_nameid_format( internal_response.subject_type) # If the backend did not receive a SAML <NameID> and so # name_id is set to None then do not create a NameID instance. # Instead pass None as the name name_id to the IdP server # instance and it will use its configured policy to construct # a <NameID>, with the default to create a transient <NameID>. name_id = None if not nameid_value else NameID( text=nameid_value, format=nameid_format, sp_name_qualifier=None, name_qualifier=None, ) msg = "returning attributes {}".format(json.dumps(ava)) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) policies = self.idp_config.get('service', {}).get('idp', {}).get('policy', {}) sp_policy = policies.get('default', {}) sp_policy.update(policies.get(sp_entity_id, {})) sign_assertion = sp_policy.get('sign_assertion', False) sign_response = sp_policy.get('sign_response', True) sign_alg = sp_policy.get('sign_alg', 'SIG_RSA_SHA256') digest_alg = sp_policy.get('digest_alg', 'DIGEST_SHA256') encrypt_assertion = sp_policy.get('encrypt_assertion', False) encrypted_advice_attributes = sp_policy.get( 'encrypted_advice_attributes', False) # Construct arguments for method create_authn_response # on IdP Server instance args = { 'identity': ava, 'name_id': name_id, 'authn': auth_info, 'sign_response': sign_response, 'sign_assertion': sign_assertion, 'encrypt_assertion': encrypt_assertion, 'encrypted_advice_attributes': encrypted_advice_attributes } # Add the SP details args.update(**resp_args) try: args['sign_alg'] = getattr(xmldsig, sign_alg) except AttributeError as e: msg = "Unsupported sign algorithm {}".format(sign_alg) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.error(logline) raise Exception(msg) from e else: msg = "signing with algorithm {}".format(args['sign_alg']) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) try: args['digest_alg'] = getattr(xmldsig, digest_alg) except AttributeError as e: msg = "Unsupported digest algorithm {}".format(digest_alg) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.error(logline) raise Exception(msg) from e else: msg = "using digest algorithm {}".format(args['digest_alg']) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) resp = idp.create_authn_response(**args) http_args = idp.apply_binding(resp_args["binding"], str(resp), resp_args["destination"], request_state["relay_state"], response=True) # Set the common domain cookie _saml_idp if so configured. if self.config.get('common_domain_cookie'): self._set_common_domain_cookie(internal_response, http_args, context) del context.state[self.name] return make_saml_response(resp_args["binding"], http_args)
def _set_common_domain_cookie(self, internal_response, http_args, context): """ """ # Find any existing common domain cookie and deconsruct it to # obtain the list of IdPs. cookie = SimpleCookie(context.cookie) if '_saml_idp' in cookie: common_domain_cookie = cookie['_saml_idp'] msg = "Found existing common domain cookie {}".format( common_domain_cookie) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) space_separated_b64_idp_string = unquote( common_domain_cookie.value) b64_idp_list = space_separated_b64_idp_string.split() idp_list = [ urlsafe_b64decode(b64_idp).decode('utf-8') for b64_idp in b64_idp_list ] else: msg = "No existing common domain cookie found" logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) idp_list = [] msg = "Common domain cookie list of IdPs is {}".format(idp_list) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) # Identity the current IdP just used for authentication in this flow. this_flow_idp = internal_response.auth_info.issuer # Remove all occurrences of the current IdP from the list of IdPs. idp_list = [idp for idp in idp_list if idp != this_flow_idp] # Append the current IdP. idp_list.append(this_flow_idp) msg = "Added IdP {} to common domain cookie list of IdPs".format( this_flow_idp) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) msg = "Common domain cookie list of IdPs is now {}".format(idp_list) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) # Construct the cookie. b64_idp_list = [ urlsafe_b64encode(idp.encode()).decode("utf-8") for idp in idp_list ] space_separated_b64_idp_string = " ".join(b64_idp_list) url_encoded_space_separated_b64_idp_string = quote( space_separated_b64_idp_string) cookie = SimpleCookie() cookie['_saml_idp'] = url_encoded_space_separated_b64_idp_string cookie['_saml_idp']['path'] = '/' # Use the domain from configuration if present else use the domain # from the base URL for the front end. domain = urlparse(self.base_url).netloc if isinstance(self.config['common_domain_cookie'], dict): if 'domain' in self.config['common_domain_cookie']: domain = self.config['common_domain_cookie']['domain'] # Ensure that the domain begins with a '.' if domain[0] != '.': domain = '.' + domain cookie['_saml_idp']['domain'] = domain cookie['_saml_idp']['secure'] = True # Set the cookie. msg = "Setting common domain cookie with {}".format(cookie.output()) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) http_args['headers'].append(tuple(cookie.output().split(": ", 1)))
def constructPrimaryIdentifier(self, data, ordered_identifier_candidates): """ Construct and return a primary identifier value from the data asserted by the IdP using the ordered list of candidates from the configuration. """ logprefix = PrimaryIdentifier.logprefix context = self.context attributes = data.attributes msg = "{} Input attributes {}".format(logprefix, attributes) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) value = None for candidate in ordered_identifier_candidates: msg = "{} Considering candidate {}".format(logprefix, candidate) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) # Get the values asserted by the IdP for the configured list of attribute names for this candidate # and substitute None if the IdP did not assert any value for a configured attribute. values = [ attributes.get(attribute_name, [None])[0] for attribute_name in candidate['attribute_names'] if attribute_name != 'name_id' ] msg = "{} Found candidate values {}".format(logprefix, values) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) # If one of the configured attribute names is name_id then if there is also a configured # name_id_format add the value for the NameID of that format if it was asserted by the IdP # or else add the value None. if 'name_id' in candidate['attribute_names']: candidate_nameid_value = None candidate_nameid_value = None candidate_name_id_format = candidate.get('name_id_format') name_id_value = data.subject_id name_id_format = data.subject_type if (name_id_value and candidate_name_id_format and candidate_name_id_format == name_id_format): msg = "{} IdP asserted NameID {}".format( logprefix, name_id_value) logline = lu.LOG_FMT.format(id=lu.get_session_id( context.state), message=msg) logger.debug(logline) candidate_nameid_value = name_id_value # Only add the NameID value asserted by the IdP if it is not already # in the list of values. This is necessary because some non-compliant IdPs # have been known, for example, to assert the value of eduPersonPrincipalName # in the value for SAML2 persistent NameID as well as asserting # eduPersonPrincipalName. if candidate_nameid_value not in values: msg = "{} Added NameID {} to candidate values".format( logprefix, candidate_nameid_value) logline = lu.LOG_FMT.format(id=lu.get_session_id( context.state), message=msg) logger.debug(logline) values.append(candidate_nameid_value) else: msg = "{} NameID {} value also asserted as attribute value".format( logprefix, candidate_nameid_value) logline = logline = lu.LOG_FMT.format(id=lu.get_session_id( context.state), message=msg) logger.warn(logline) # If no value was asserted by the IdP for one of the configured list of attribute names # for this candidate then go onto the next candidate. if None in values: msg = "{} Candidate is missing value so skipping".format( logprefix) logline = logline = lu.LOG_FMT.format(id=lu.get_session_id( context.state), message=msg) logger.debug(logline) continue # All values for the configured list of attribute names are present # so we can create a primary identifer. Add a scope if configured # to do so. if 'add_scope' in candidate: if candidate['add_scope'] == 'issuer_entityid': scope = data.auth_info.issuer else: scope = candidate['add_scope'] msg = "{} Added scope {} to values".format(logprefix, scope) logline = lu.LOG_FMT.format(id=lu.get_session_id( context.state), message=msg) logger.debug(logline) values.append(scope) # Concatenate all values to create the primary identifier. value = ''.join(values) break return value
def process(self, context, data): logprefix = PrimaryIdentifier.logprefix self.context = context # Initialize the configuration to use as the default configuration # that is passed during initialization. config = self.config msg = "{} Using default configuration {}".format(logprefix, config) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) # Find the entityID for the SP that initiated the flow try: spEntityID = context.state.state_dict['SATOSA_BASE']['requester'] except KeyError as err: msg = "{} Unable to determine the entityID for the SP requester".format( logprefix) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.error(logline) return super().process(context, data) msg = "{} entityID for the SP requester is {}".format( logprefix, spEntityID) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) # Find the entityID for the IdP that issued the assertion try: idpEntityID = data.auth_info.issuer except KeyError as err: msg = "{} Unable to determine the entityID for the IdP issuer".format( logprefix) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.error(logline) return super().process(context, data) # Examine our configuration to determine if there is a per-IdP configuration if idpEntityID in self.config: config = self.config[idpEntityID] msg = "{} For IdP {} using configuration {}".format( logprefix, idpEntityID, config) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) # Examine our configuration to determine if there is a per-SP configuration. # An SP configuration overrides an IdP configuration when there is a conflict. if spEntityID in self.config: config = self.config[spEntityID] msg = "{} For SP {} using configuration {}".format( logprefix, spEntityID, config) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) # Obtain configuration details from the per-SP configuration or the default configuration try: if 'ordered_identifier_candidates' in config: ordered_identifier_candidates = config[ 'ordered_identifier_candidates'] else: ordered_identifier_candidates = self.config[ 'ordered_identifier_candidates'] if 'primary_identifier' in config: primary_identifier = config['primary_identifier'] elif 'primary_identifier' in self.config: primary_identifier = self.config['primary_identifier'] else: primary_identifier = 'uid' if 'clear_input_attributes' in config: clear_input_attributes = config['clear_input_attributes'] elif 'clear_input_attributes' in self.config: clear_input_attributes = self.config['clear_input_attributes'] else: clear_input_attributes = False if 'replace_subject_id' in config: replace_subject_id = config['replace_subject_id'] elif 'replace_subject_id' in self.config: replace_subject_id = self.config['replace_subject_id'] else: replace_subject_id = False if 'ignore' in config: ignore = True else: ignore = False if 'on_error' in config: on_error = config['on_error'] elif 'on_error' in self.config: on_error = self.config['on_error'] else: on_error = None except KeyError as err: msg = "{} Configuration '{}' is missing".format(logprefix, err) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.error(logline) return super().process(context, data) # Ignore this SP entirely if so configured. if ignore: msg = "{} Ignoring SP {}".format(logprefix, spEntityID) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.info(logline) return super().process(context, data) # Construct the primary identifier. msg = "{} Constructing primary identifier".format(logprefix) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) primary_identifier_val = self.constructPrimaryIdentifier( data, ordered_identifier_candidates) if not primary_identifier_val: msg = "{} No primary identifier found".format(logprefix) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.warn(logline) if on_error: # Redirect to the configured error handling service with # the entityIDs for the target SP and IdP used by the user # as query string parameters (URL encoded). encodedSpEntityID = urllib.parse.quote_plus(spEntityID) encodedIdpEntityID = urllib.parse.quote_plus( data.auth_info.issuer) url = "{}?sp={}&idp={}".format(on_error, encodedSpEntityID, encodedIdpEntityID) msg = "{} Redirecting to {}".format(logprefix, url) logline = lu.LOG_FMT.format(id=lu.get_session_id( context.state), message=msg) logger.info(logline) return Redirect(url) msg = "{} Found primary identifier: {}".format(logprefix, primary_identifier_val) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.info(logline) # Clear input attributes if so configured. if clear_input_attributes: msg = "{} Clearing values for these input attributes: {}".format( logprefix, data.attributes.keys()) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) data.attributes = {} if primary_identifier: # Set the primary identifier attribute to the value found. data.attributes[primary_identifier] = primary_identifier_val msg = "{} Setting attribute {} to value {}".format( logprefix, primary_identifier, primary_identifier_val) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) # Replace subject_id with the constructed primary identifier if so configured. if replace_subject_id: msg = "{} Setting subject_id to value {}".format( logprefix, primary_identifier_val) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) data.subject_id = primary_identifier_val msg = "{} returning data.attributes {}".format(logprefix, str(data.attributes)) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) return super().process(context, data)
def process(self, context, data): """ Default interface for microservices. Process the input data for the input context. """ state = context.state session_id = lu.get_session_id(state) requester = data.requester issuer = data.auth_info.issuer frontend_name = state.get(ROUTING_STATE_KEY) co_entity_id_key = SAMLVirtualCoFrontend.KEY_CO_ENTITY_ID co_entity_id = state.get(frontend_name, {}).get(co_entity_id_key) entity_ids = [requester, issuer, co_entity_id, "default"] config, entity_id = next( (self.config.get(e), e) for e in entity_ids if self.config.get(e)) msg = { "message": "entityID for the involved entities", "requester": requester, "issuer": issuer, "config": self._filter_config(config), } if co_entity_id: msg["co_entity_id"] = co_entity_id logline = lu.LOG_FMT.format(id=session_id, message=msg) logger.debug(logline) # Ignore this entityID entirely if so configured. if config["ignore"]: msg = "Ignoring entityID {}".format(entity_id) logline = lu.LOG_FMT.format(id=session_id, message=msg) logger.info(logline) return super().process(context, data) # The list of values for the LDAP search filters that will be tried in # order to find the LDAP directory record for the user. filter_values = [ filter_value for candidate in config["ordered_identifier_candidates"] # Consider and find asserted values to construct the ordered list # of values for the LDAP search filters. for filter_value in [ self._construct_filter_value( candidate, data.subject_id, data.subject_type, issuer, data.attributes, ) ] # If we have constructed a non empty value then add it as the next # filter value to use when searching for the user record. if filter_value ] msg = {"message": "Search filters", "filter_values": filter_values} logline = lu.LOG_FMT.format(id=session_id, message=msg) logger.debug(logline) # Initialize an empty LDAP record. The first LDAP record found using # the ordered # list of search filter values will be the record used. record = None results = None exp_msg = None connection = config["connection"] msg = { "message": "LDAP server host", "server host": connection.server.host, } logline = lu.LOG_FMT.format(id=session_id, message=msg) logger.debug(logline) for filter_val in filter_values: ldap_ident_attr = config["ldap_identifier_attribute"] search_filter = "({0}={1})".format(ldap_ident_attr, filter_val) msg = { "message": "LDAP query with constructed search filter", "search filter": search_filter, } logline = lu.LOG_FMT.format(id=session_id, message=msg) logger.debug(logline) attributes = ( config["query_return_attributes"] if config["query_return_attributes"] # Deprecated configuration. Will be removed in future. else config["search_return_attributes"].keys()) try: results = connection.search(config["search_base"], search_filter, attributes=attributes) except LDAPException as err: exp_msg = "Caught LDAP exception: {}".format(err) except LdapAttributeStoreError as err: exp_msg = "Caught LDAP Attribute Store exception: {}" exp_msg = exp_msg.format(err) except Exception as err: exp_msg = "Caught unhandled exception: {}".format(err) if exp_msg: logline = lu.LOG_FMT.format(id=session_id, message=exp_msg) logger.error(logline) return super().process(context, data) if not results: msg = "Querying LDAP server: No results for {}." msg = msg.format(filter_val) logline = lu.LOG_FMT.format(id=session_id, message=msg) logger.debug(logline) continue if isinstance(results, bool): responses = connection.entries else: responses = connection.get_response(results)[0] msg = "Done querying LDAP server" logline = lu.LOG_FMT.format(id=session_id, message=msg) logger.debug(logline) msg = "LDAP server returned {} records".format(len(responses)) logline = lu.LOG_FMT.format(id=session_id, message=msg) logger.info(logline) # For now consider only the first record found (if any). if len(responses) > 0: if len(responses) > 1: msg = "LDAP server returned {} records using search filter" msg = msg + " value {}" msg = msg.format(len(responses), filter_val) logline = lu.LOG_FMT.format(id=session_id, message=msg) logger.warning(logline) record = responses[0] break # Before using a found record, if any, to populate attributes # clear any attributes incoming to this microservice if so configured. if config["clear_input_attributes"]: msg = "Clearing values for these input attributes: {}" msg = msg.format(data.attributes) logline = lu.LOG_FMT.format(id=session_id, message=msg) logger.debug(logline) data.attributes = {} # This adapts records with different search and connection strategy # (sync without pool), it should be tested with anonimous bind with # message_id. if isinstance(results, bool) and record: record = { "dn": record.entry_dn if hasattr(record, "entry_dn") else "", "attributes": (record.entry_attributes_as_dict if hasattr( record, "entry_attributes_as_dict") else {}), } # Use a found record, if any, to populate attributes and input for # NameID if record: msg = { "message": "Using record with DN and attributes", "DN": record["dn"], "attributes": record["attributes"], } logline = lu.LOG_FMT.format(id=session_id, message=msg) logger.debug(logline) # Populate attributes as configured. new_attrs = self._populate_attributes(config, record) overwrite = config["overwrite_existing_attributes"] for attr, values in new_attrs.items(): if not overwrite: values = list(set(data.attributes.get(attr, []) + values)) data.attributes[attr] = values # Populate input for NameID if configured. SATOSA core does the # hashing of input to create a persistent NameID. user_ids = self._populate_input_for_name_id(config, record, data) if user_ids: data.subject_id = "".join(user_ids) msg = "NameID value is {}".format(data.subject_id) logger.debug(msg) # Add the record to the context so that later microservices # may use it if required. context.decorate(KEY_FOUND_LDAP_RECORD, record) msg = "Added record {} to context".format(record) logline = lu.LOG_FMT.format(id=session_id, message=msg) logger.debug(logline) else: msg = "No record found in LDAP so no attributes will be added" logline = lu.LOG_FMT.format(id=session_id, message=msg) logger.warning(logline) on_ldap_search_result_empty = config["on_ldap_search_result_empty"] if on_ldap_search_result_empty: # Redirect to the configured URL with # the entityIDs for the target SP and IdP used by the user # as query string parameters (URL encoded). encoded_sp_entity_id = urllib.parse.quote_plus(requester) encoded_idp_entity_id = urllib.parse.quote_plus(issuer) url = "{}?sp={}&idp={}".format( on_ldap_search_result_empty, encoded_sp_entity_id, encoded_idp_entity_id, ) msg = "Redirecting to {}".format(url) logline = lu.LOG_FMT.format(id=session_id, message=msg) logger.info(logline) return Redirect(url) msg = "Returning data.attributes {}".format(data.attributes) logline = lu.LOG_FMT.format(id=session_id, message=msg) logger.debug(logline) return super().process(context, data)
def authn_request(self, context, entity_id): """ Do an authorization request on idp with given entity id. This is the start of the authorization. :type context: satosa.context.Context :type entity_id: str :rtype: satosa.response.Response :param context: The current context :param entity_id: Target IDP entity id :return: response to the user agent """ # If IDP blacklisting is enabled and the selected IDP is blacklisted, # stop here if self.idp_blacklist_file: with open(self.idp_blacklist_file) as blacklist_file: blacklist_array = json.load(blacklist_file)['blacklist'] if entity_id in blacklist_array: msg = "IdP with EntityID {} is blacklisted".format( entity_id) logline = lu.LOG_FMT.format(id=lu.get_session_id( context.state), message=msg) logger.debug(logline, exc_info=False) raise SATOSAAuthenticationError( context.state, "Selected IdP is blacklisted for this backend") kwargs = {} authn_context = self.construct_requested_authn_context(entity_id) if authn_context: kwargs["requested_authn_context"] = authn_context if self.config.get(SAMLBackend.KEY_MIRROR_FORCE_AUTHN): kwargs["force_authn"] = get_force_authn(context, self.config, self.sp.config) try: binding, destination = self.sp.pick_binding( "single_sign_on_service", None, "idpsso", entity_id=entity_id) msg = "binding: {}, destination: {}".format(binding, destination) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) acs_endp, response_binding = self.sp.config.getattr( "endpoints", "sp")["assertion_consumer_service"][0] req_id, req = self.sp.create_authn_request( destination, binding=response_binding, **kwargs) relay_state = util.rndstr() ht_args = self.sp.apply_binding(binding, "%s" % req, destination, relay_state=relay_state) msg = "ht_args: {}".format(ht_args) logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline) except Exception as exc: msg = "Failed to construct the AuthnRequest for state" logline = lu.LOG_FMT.format(id=lu.get_session_id(context.state), message=msg) logger.debug(logline, exc_info=True) raise SATOSAAuthenticationError( context.state, "Failed to construct the AuthnRequest") from exc if self.sp.config.getattr('allow_unsolicited', 'sp') is False: if req_id in self.outstanding_queries: msg = "Request with duplicate id {}".format(req_id) logline = lu.LOG_FMT.format(id=lu.get_session_id( context.state), message=msg) logger.debug(logline) raise SATOSAAuthenticationError(context.state, msg) self.outstanding_queries[req_id] = req context.state[self.name] = {"relay_state": relay_state} return make_saml_response(binding, ht_args)