def test_handle_authn_response_returns_id_token_for_verified_affiliation( self, signing_key_path, context, scope_value, affiliation): authn_req = AuthorizationRequest( scope='openid ' + scope_value, client_id='client1', redirect_uri='https://client.example.com', response_type='id_token') context.state[self.frontend.name] = { 'oidc_request': authn_req.to_urlencoded() } internal_response = InternalResponse( AuthenticationInformation(None, str(datetime.now()), 'https://idp.example.com')) internal_response.attributes['affiliation'] = [affiliation] internal_response.user_id = 'user1' resp = self.frontend.handle_authn_response(context, internal_response) auth_resp = AuthorizationResponse().from_urlencoded( urlparse(resp.message).fragment) id_token = IdToken().from_jwt( auth_resp['id_token'], key=[RSAKey(key=rsa_load(signing_key_path))]) assert id_token['iss'] == self.frontend.base_url assert id_token['aud'] == ['client1'] assert id_token['auth_time'] == internal_response.auth_info.timestamp
def handle_response(self, context): auth_info = AuthenticationInformation("test", str(datetime.now()), "test_issuer") internal_resp = InternalResponse(auth_info=auth_info) internal_resp.attributes = context.request internal_resp.user_id = "test_user" return self.auth_callback_func(context, internal_resp)
def setup(self, signing_key_path): self.account_linking_config = { "enable": True, "rest_uri": "https://localhost:8167", "redirect": "https://localhost:8167/approve", "endpoint": "handle_account_linking", "sign_key": signing_key_path, "verify_ssl": False } self.satosa_config = { "BASE": "https://proxy.example.com", "USER_ID_HASH_SALT": "qwerty", "COOKIE_STATE_NAME": "SATOSA_SATE", "STATE_ENCRYPTION_KEY": "ASDasd123", "PLUGIN_PATH": "", "BACKEND_MODULES": "", "FRONTEND_MODULES": "", "INTERNAL_ATTRIBUTES": {}, "ACCOUNT_LINKING": self.account_linking_config } self.callback_func = MagicMock() self.context = Context() state = State() self.context.state = state auth_info = AuthenticationInformation("auth_class_ref", "timestamp", "issuer") self.internal_response = InternalResponse(auth_info=auth_info)
def internal_response(self): auth_info = AuthenticationInformation("auth_class_ref", "timestamp", "issuer") internal_response = InternalResponse(auth_info=auth_info) internal_response.requester = "client" internal_response.attributes = ATTRIBUTES return internal_response
def internal_response(self, idp_conf): auth_info = AuthenticationInformation(PASSWORD, "2015-09-30T12:21:37Z", idp_conf["entityid"]) internal_response = InternalResponse(auth_info=auth_info) internal_response.attributes = AttributeMapper( INTERNAL_ATTRIBUTES).to_internal("saml", USERS["testuser1"]) return internal_response
def test_auth_resp_callback_func_user_id_from_attrs_is_used_to_override_user_id( self, context, satosa_config): satosa_config["INTERNAL_ATTRIBUTES"]["user_id_from_attrs"] = [ "user_id", "domain" ] base = SATOSABase(satosa_config) internal_resp = InternalResponse(AuthenticationInformation("", "", "")) internal_resp.attributes = { "user_id": ["user"], "domain": ["@example.com"] } internal_resp.requester = "test_requester" context.state[satosa.base.STATE_KEY] = {"requester": "test_requester"} context.state[satosa.routing. STATE_KEY] = satosa_config["FRONTEND_MODULES"][0]["name"] UserIdHasher.save_state(InternalRequest(UserIdHashType.persistent, ""), context.state) base._auth_resp_callback_func(context, internal_resp) expected_user_id = UserIdHasher.hash_data( satosa_config["USER_ID_HASH_SALT"], "*****@*****.**") expected_user_id = UserIdHasher.hash_id( satosa_config["USER_ID_HASH_SALT"], expected_user_id, internal_resp.requester, context.state) assert internal_resp.user_id == expected_user_id
def setup_for_authn_response(self, context, frontend, auth_req): context.state[frontend.name] = {"oidc_request": auth_req.to_urlencoded()} auth_info = AuthenticationInformation(PASSWORD, "2015-09-30T12:21:37Z", "unittest_idp.xml") internal_response = InternalResponse(auth_info=auth_info) internal_response.attributes = AttributeMapper(INTERNAL_ATTRIBUTES).to_internal("saml", USERS["testuser1"]) internal_response.user_id = USERS["testuser1"]["eduPersonTargetedID"][0] return internal_response
def test_filter_attribute_not_in_response(self): attribute_filters = {"": {"": {"a0": "foo:bar"}}} filter_service = self.create_filter_service(attribute_filters) resp = InternalResponse(AuthenticationInformation(None, None, None)) resp.attributes = { "a1": ["abc:xyz", "1:foo:bar:2"], } filtered = filter_service.process(None, resp) assert filtered.attributes == {"a1": ["abc:xyz", "1:foo:bar:2"]}
def test_filter_one_attribute_from_all_target_providers_for_all_requesters( self): attribute_filters = {"": {"": {"a2": "^foo:bar$"}}} filter_service = self.create_filter_service(attribute_filters) resp = InternalResponse(AuthenticationInformation(None, None, None)) resp.attributes = { "a1": ["abc:xyz"], "a2": ["foo:bar", "1:foo:bar:2"], } filtered = filter_service.process(None, resp) assert filtered.attributes == {"a1": ["abc:xyz"], "a2": ["foo:bar"]}
def test_auth_resp_callback_func_respects_user_id_to_attr(self, context, satosa_config): satosa_config["INTERNAL_ATTRIBUTES"]["user_id_to_attr"] = "user_id" base = SATOSABase(satosa_config) internal_resp = InternalResponse(AuthenticationInformation("", "", "")) internal_resp.user_id = "user1234" context.state[satosa.base.STATE_KEY] = {"requester": "test_requester"} context.state[satosa.routing.STATE_KEY] = satosa_config["FRONTEND_MODULES"][0]["name"] UserIdHasher.save_state(InternalRequest(UserIdHashType.transient, ""), context.state) base._auth_resp_callback_func(context, internal_resp) assert internal_resp.attributes["user_id"] == [internal_resp.user_id]
def test_filter_one_attribute_for_one_target_provider(self): target_provider = "test_provider" attribute_filters = {target_provider: {"": {"a1": "foo:bar"}}} filter_service = self.create_filter_service(attribute_filters) resp = InternalResponse( AuthenticationInformation(None, None, target_provider)) resp.attributes = { "a1": ["abc:xyz", "1:foo:bar:2"], } filtered = filter_service.process(None, resp) assert filtered.attributes == {"a1": ["1:foo:bar:2"]}
def test_handle_authn_response_returns_error_access_denied_for_wrong_affiliation(self, context, scope_value, affiliation): authn_req = AuthorizationRequest(scope='openid ' + scope_value, client_id='client1', redirect_uri='https://client.example.com', response_type='id_token') context.state[self.frontend.name] = {'oidc_request': authn_req.to_urlencoded()} internal_response = InternalResponse() internal_response.attributes['affiliation'] = [affiliation] internal_response.user_id = 'user1' resp = self.frontend.handle_authn_response(context, internal_response) auth_resp = AuthorizationErrorResponse().from_urlencoded(urlparse(resp.message).fragment) assert auth_resp['error'] == 'access_denied'
def _translate_response(self, response, issuer, subject_type): """ Translates oidc response to SATOSA internal response. :type response: dict[str, str] :type issuer: str :type subject_type: str :rtype: InternalResponse :param response: Dictioary with attribute name as key. :param issuer: The oidc op that gave the repsonse. :param subject_type: public or pairwise according to oidc standard. :return: A SATOSA internal response. """ oidc_clients = self.get_oidc_clients() subject_type = subject_type auth_info = AuthenticationInformation(UNSPECIFIED, str(datetime.now()), issuer) internal_resp = InternalResponse(auth_info=auth_info) internal_resp.add_attributes( self.converter.to_internal("openid", response)) internal_resp.set_user_id(response["sub"]) if self.config.USER_ID_PARAMAS: user_id = "" for param in self.config.USER_ID_PARAMAS: try: user_id += response[param] except Exception as error: raise SATOSAAuthenticationError from error internal_resp.set_user_id(user_id) return internal_resp
def setup_for_authn_response(self, context, frontend, auth_req): context.state[frontend.name] = { "oidc_request": auth_req.to_urlencoded() } auth_info = AuthenticationInformation(PASSWORD, "2015-09-30T12:21:37Z", "unittest_idp.xml") internal_response = InternalResponse(auth_info=auth_info) internal_response.attributes = AttributeMapper( INTERNAL_ATTRIBUTES).to_internal("saml", USERS["testuser1"]) internal_response.user_id = USERS["testuser1"]["eduPersonTargetedID"][ 0] return internal_response
def test_auth_resp_callback_func_respects_user_id_to_attr( self, context, satosa_config): satosa_config["INTERNAL_ATTRIBUTES"]["user_id_to_attr"] = "user_id" base = SATOSABase(satosa_config) internal_resp = InternalResponse(AuthenticationInformation("", "", "")) internal_resp.user_id = "user1234" context.state[satosa.base.STATE_KEY] = {"requester": "test_requester"} context.state[satosa.routing. STATE_KEY] = satosa_config["FRONTEND_MODULES"][0]["name"] UserIdHasher.save_state(InternalRequest(UserIdHashType.transient, ""), context.state) base._auth_resp_callback_func(context, internal_resp) assert internal_resp.attributes["user_id"] == [internal_resp.user_id]
def test_authz_deny_fail(self): attribute_deny = {"": {"default": {"a0": ['foo1', 'foo2']}}} attribute_allow = {} authz_service = self.create_authz_service(attribute_allow, attribute_deny) resp = InternalResponse(AuthenticationInformation(None, None, None)) resp.attributes = { "a0": ["foo3"], } try: ctx = Context() ctx.state = dict() authz_service.process(ctx, resp) except SATOSAAuthenticationError as ex: assert False
def setup_for_authn_response(self, auth_req): context = Context() context.state = self.create_state(auth_req) auth_info = AuthenticationInformation(PASSWORD, "2015-09-30T12:21:37Z", "unittest_idp.xml") internal_response = InternalResponse(auth_info=auth_info) internal_response.add_attributes( DataConverter(INTERNAL_ATTRIBUTES).to_internal("saml", USERS["testuser1"])) internal_response.set_user_id(USERS["testuser1"]["eduPersonTargetedID"][0]) self.instance.provider.cdb = { "client1": {"response_types": ["id_token"], "redirect_uris": [(auth_req["redirect_uri"], None)], "client_salt": "salt"}} return context, internal_response
def change_language(self, context): consent_state = context.state[consent.STATE_KEY] saved_resp = consent_state['internal_response'] internal_response = InternalResponse.from_dict(saved_resp) lang = context.request.get('lang', 'en') return self.render_consent(consent_state, internal_response, lang)
def change_language(self, context): consent_state = context.state[self.name] saved_resp = consent_state['internal_response'] internal_response = InternalResponse.from_dict(saved_resp) lang = context.request.get('lang', 'en') return self.render_consent(internal_response, lang)
def accept_consent(self, context): """ Endpoint for handling accepted consent. :type context: satosa.context.Context :rtype: satosa.response.Response :param context: response context :return: response """ consent_state = context.state[STATE_KEY] saved_resp = consent_state['internal_response'] internal_response = InternalResponse.from_dict(saved_resp) del context.state[STATE_KEY] log = {} log['router'] = context.state.state_dict['ROUTER'] log['sessionid'] = context.state.state_dict['SESSION_ID'] log['timestamp'] = saved_resp['auth_info'].get('timestamp') log['idp'] = saved_resp['auth_info'].get('issuer', None) log['rp'] = saved_resp.get('to', None) log['attr'] = saved_resp.get('attr', None) satosa_logging(logger, logging.INFO, "log: {}".format(log), context.state) print(json.dumps(log), file=self.loghandle, end="\n") self.loghandle.flush() transaction_log(context.state, self.config.get("consent_exit_order", 1000), "user_consent", "accept", "exit", "success", '', '', 'Consent given by the user') return super().process(context, internal_response)
def handle_consent(self, context): consent_state = context.state[STATE_KEY] saved_resp = consent_state['internal_response'] internal_response = InternalResponse.from_dict(saved_resp) lang = context.request.get('lang', 'en') return self.render_consent(consent_state, internal_response, lang)
def test_auth_resp_callback_func_hashes_all_specified_attributes(self, context, satosa_config): satosa_config["INTERNAL_ATTRIBUTES"]["hash"] = ["user_id", "mail"] base = SATOSABase(satosa_config) attributes = {"user_id": ["user"], "mail": ["*****@*****.**", "*****@*****.**"]} internal_resp = InternalResponse(AuthenticationInformation("", "", "")) internal_resp.attributes = copy.copy(attributes) internal_resp.user_id = "test_user" UserIdHasher.save_state(InternalRequest(UserIdHashType.transient, ""), context.state) context.state[satosa.base.STATE_KEY] = {"requester": "test_requester"} context.state[satosa.routing.STATE_KEY] = satosa_config["FRONTEND_MODULES"][0]["name"] base._auth_resp_callback_func(context, internal_resp) for attr in satosa_config["INTERNAL_ATTRIBUTES"]["hash"]: assert internal_resp.attributes[attr] == [UserIdHasher.hash_data(satosa_config["USER_ID_HASH_SALT"], v) for v in attributes[attr]]
def authn_response(self, context): """ Handles the authentication response from the OP. :type context: satosa.context.Context :rtype: satosa.response.Response :param context: The context in SATOSA :return: A SATOSA response. This method is only responsible to call the callback function which generates the Response object. """ state = context.state try: state_data = state.get(self.config["state_id"]) consumer = self.get_consumer() request = context.request aresp = consumer.parse_response(AuthorizationResponse, info=json.dumps(request)) self.verify_state(aresp, state_data, state) rargs = { "code": aresp["code"], "redirect_uri": self.redirect_url, "state": state_data["state"] } atresp = consumer.do_access_token_request(request_args=rargs, state=aresp["state"]) if ("verify_accesstoken_state" not in self.config or self.config["verify_accesstoken_state"]): self.verify_state(atresp, state_data, state) user_info = self.user_information(atresp["access_token"]) internal_response = InternalResponse( auth_info=self.auth_info(request)) internal_response.add_attributes( self.converter.to_internal(self.external_type, user_info)) internal_response.set_user_id(user_info[self.user_id_attr]) if "user_id_params" in self.config: user_id = "" for param in self.config["user_id_params"]: try: user_id += user_info[param] except Exception as error: raise SATOSAAuthenticationError from error internal_response.set_user_id(user_id) context.state.remove(self.config["state_id"]) return self.auth_callback_func(context, internal_response) except Exception as error: satosa_logging(LOGGER, logging.DEBUG, "Not a valid authentication", state, exc_info=True) if isinstance(error, SATOSAError): raise error if state is not None: raise SATOSAAuthenticationError( state, "Not a valid authentication") from error raise
def test_set_user_id(): uid = "my_id" attributes = {"attr_1": "v1", "attr_2": "v2", "attr_3": "v3"} internal_response = InternalResponse(UserIdHashType.persistent) internal_response.add_attributes(attributes) internal_response.set_user_id(uid) assert uid == internal_response.get_user_id()
def _translate_response(self, response, state): """ Translates a saml authorization response to an internal response :type response: saml2.response.AuthnResponse :rtype: satosa.internal_data.InternalResponse :param response: The saml authorization response :return: A translated internal response """ _authn_info = response.authn_info()[0] timestamp = response.assertion.authn_statement[0].authn_instant issuer = response.response.issuer.text auth_class_ref = _authn_info[0] auth_info = AuthenticationInformation(auth_class_ref, timestamp, issuer) internal_resp = InternalResponse(auth_info=auth_info) internal_resp.set_user_id(response.get_subject().text) if "user_id_params" in self.config: user_id = "" for param in self.config["user_id_params"]: try: user_id += response.ava[param] except Exception as error: raise SATOSAAuthenticationError from error internal_resp.set_user_id(user_id) internal_resp.add_attributes(self.converter.to_internal(self.attribute_profile, response.ava)) satosa_logging(LOGGER, logging.DEBUG, "received attributes:\n%s" % json.dumps(response.ava, indent=4), state) return internal_resp
def _translate_response(self, response, issuer, subject_type): """ Translates oidc response to SATOSA internal response. :type response: dict[str, str] :type issuer: str :type subject_type: str :rtype: InternalResponse :param response: Dictioary with attribute name as key. :param issuer: The oidc op that gave the repsonse. :param subject_type: public or pairwise according to oidc standard. :return: A SATOSA internal response. """ oidc_clients = self.get_oidc_clients() subject_type = subject_type auth_info = AuthenticationInformation(UNSPECIFIED, str(datetime.now()), issuer) internal_resp = InternalResponse( auth_info=auth_info ) internal_resp.add_attributes(self.converter.to_internal("openid", response)) internal_resp.set_user_id(response["sub"]) if self.config.USER_ID_PARAMAS: user_id = "" for param in self.config.USER_ID_PARAMAS: try: user_id += response[param] except Exception as error: raise SATOSAAuthenticationError from error internal_resp.set_user_id(user_id) return internal_resp
def test_handle_authn_response_returns_error_access_denied_for_wrong_affiliation( self, context, scope_value, affiliation): authn_req = AuthorizationRequest( scope='openid ' + scope_value, client_id='client1', redirect_uri='https://client.example.com', response_type='id_token') context.state[self.frontend.name] = { 'oidc_request': authn_req.to_urlencoded() } internal_response = InternalResponse() internal_response.attributes['affiliation'] = [affiliation] internal_response.user_id = 'user1' resp = self.frontend.handle_authn_response(context, internal_response) auth_resp = AuthorizationErrorResponse().from_urlencoded( urlparse(resp.message).fragment) assert auth_resp['error'] == 'access_denied'
def test_handle_authn_response_returns_id_token_for_verified_affiliation( self, signing_key_path, context, scope_value, affiliation): authn_req = AuthorizationRequest(scope='openid ' + scope_value, client_id='client1', redirect_uri='https://client.example.com', response_type='id_token') context.state[self.frontend.name] = {'oidc_request': authn_req.to_urlencoded()} internal_response = InternalResponse(AuthenticationInformation(None, str(datetime.now()), 'https://idp.example.com')) internal_response.attributes['affiliation'] = [affiliation] internal_response.user_id = 'user1' resp = self.frontend.handle_authn_response(context, internal_response) auth_resp = AuthorizationResponse().from_urlencoded(urlparse(resp.message).fragment) id_token = IdToken().from_jwt(auth_resp['id_token'], key=[RSAKey(key=rsa_load(signing_key_path))]) assert id_token['iss'] == self.frontend.base_url assert id_token['aud'] == ['client1'] assert id_token['auth_time'] == internal_response.auth_info.timestamp
def test_auth_resp_callback_func_user_id_from_attrs_is_used_to_override_user_id(self, context, satosa_config): satosa_config["INTERNAL_ATTRIBUTES"]["user_id_from_attrs"] = ["user_id", "domain"] base = SATOSABase(satosa_config) internal_resp = InternalResponse(AuthenticationInformation("", "", "")) internal_resp.attributes = {"user_id": ["user"], "domain": ["@example.com"]} internal_resp.requester = "test_requester" context.state[satosa.base.STATE_KEY] = {"requester": "test_requester"} context.state[satosa.routing.STATE_KEY] = satosa_config["FRONTEND_MODULES"][0]["name"] UserIdHasher.save_state(InternalRequest(UserIdHashType.persistent, ""), context.state) base._auth_resp_callback_func(context, internal_resp) expected_user_id = UserIdHasher.hash_data(satosa_config["USER_ID_HASH_SALT"], "*****@*****.**") expected_user_id = UserIdHasher.hash_id(satosa_config["USER_ID_HASH_SALT"], expected_user_id, internal_resp.requester, context.state) assert internal_resp.user_id == expected_user_id
def _handle_endpoint(self, context): """ The resume endpoint handler. It's main duty is restoring internal_response and checking the resume condition """ logger.info("Handle BreakOut endpoint") breakout_state = context.state[STATE_KEY] saved_response = breakout_state["internal_resp"] logger.info("internal_resp: %s" % saved_response) internal_response = InternalResponse.from_dict(saved_response) self.resumed = True return self._check_requirement(context, internal_response)
def test_handle_authn_request_without_name_id_policy(self, idp_conf, sp_conf): """ Performs a complete test for the module. The flow should be accepted. """ context, samlfrontend = self.setup_for_authn_req(idp_conf, sp_conf, "") _, internal_req = samlfrontend.handle_authn_request(context, BINDING_HTTP_REDIRECT) assert internal_req.requestor == sp_conf["entityid"] auth_info = AuthenticationInformation(PASSWORD, "2015-09-30T12:21:37Z", "unittest_idp.xml") internal_response = InternalResponse(auth_info=auth_info) internal_response.set_user_id_hash_type(internal_req.user_id_hash_type) internal_response.add_attributes(USERS["testuser1"]) resp = samlfrontend.handle_authn_response(context, internal_response) resp_dict = parse_qs(urlparse(resp.message).query) fakesp = FakeSP(None, config=SPConfig().load(sp_conf, metadata_construction=False)) resp = fakesp.parse_authn_request_response(resp_dict['SAMLResponse'][0], BINDING_HTTP_REDIRECT) for key in resp.ava: assert USERS["testuser1"][key] == resp.ava[key]
def test_handle_authn_request_without_name_id_policy( self, idp_conf, sp_conf): """ Performs a complete test for the module. The flow should be accepted. """ context, samlfrontend = self.setup_for_authn_req(idp_conf, sp_conf, "") _, internal_req = samlfrontend.handle_authn_request( context, BINDING_HTTP_REDIRECT) assert internal_req.requestor == sp_conf["entityid"] auth_info = AuthenticationInformation(PASSWORD, "2015-09-30T12:21:37Z", "unittest_idp.xml") internal_response = InternalResponse(auth_info=auth_info) internal_response.set_user_id_hash_type(internal_req.user_id_hash_type) internal_response.add_attributes(USERS["testuser1"]) resp = samlfrontend.handle_authn_response(context, internal_response) resp_dict = parse_qs(urlparse(resp.message).query) fakesp = FakeSP(None, config=SPConfig().load(sp_conf, metadata_construction=False)) resp = fakesp.parse_authn_request_response( resp_dict['SAMLResponse'][0], BINDING_HTTP_REDIRECT) for key in resp.ava: assert USERS["testuser1"][key] == resp.ava[key]
def _handle_al_response(self, context): """ Endpoint for handling account linking service response :type context: satosa.context.Context :rtype: satosa.response.Response :param context: The current context :return: response """ saved_state = context.state.get(AccountLinkingModule.STATE_KEY) internal_response = InternalResponse.from_dict(saved_state) return self.manage_al(context, internal_response)
def test_respect_sp_entity_categories(self, entity_category, expected_attributes, idp_conf, sp_conf): base = self.construct_base_url_from_entity_id(idp_conf["entityid"]) conf = {"idp_config": idp_conf, "endpoints": ENDPOINTS, "base": base, "state_id": "state_id"} internal_attributes = {attr_name: {"saml": [attr_name.lower()]} for attr_name in expected_attributes} samlfrontend = SamlFrontend(None, dict(attributes=internal_attributes), conf) samlfrontend.register_endpoints(["foo"]) idp_metadata_str = create_metadata_from_config_dict(samlfrontend.idp_config) sp_conf["metadata"]["inline"].append(idp_metadata_str) sp_conf["entity_category"] = entity_category fakesp = FakeSP(None, config=SPConfig().load(sp_conf, metadata_construction=False)) auth_info = AuthenticationInformation(PASSWORD, "2015-09-30T12:21:37Z", idp_conf["entityid"]) internal_response = InternalResponse(auth_info=auth_info) user_attributes = {k: "foo" for k in expected_attributes} user_attributes.update({k: "bar" for k in ["extra", "more", "stuff"]}) internal_response.add_attributes(user_attributes) context = Context() context.state = State() resp_args = { "name_id_policy": NameIDPolicy(format=NAMEID_FORMAT_TRANSIENT), "in_response_to": None, "destination": "", "sp_entity_id": None, "binding": BINDING_HTTP_REDIRECT } request_state = samlfrontend.save_state(context, resp_args, "") context.state.add(conf["state_id"], request_state) resp = samlfrontend.handle_authn_response(context, internal_response) resp_dict = parse_qs(urlparse(resp.message).query) resp = fakesp.parse_authn_request_response(resp_dict['SAMLResponse'][0], BINDING_HTTP_REDIRECT) assert Counter(resp.ava.keys()) == Counter(expected_attributes)
def internal_response(): auth_info = AuthenticationInformation("auth_class_ref", "timestamp", "issuer") internal_response = InternalResponse(auth_info=auth_info) internal_response.set_user_id_hash_type(UserIdHashType.persistent) internal_response.add_attributes( {"displayName": "Test", "co": "example", "sn": "removed_by_filter"}) internal_response.user_id = "usrID" return internal_response
def test_acr_mapping_per_idp_in_authn_response(self, idp_conf, sp_conf): expected_loa = "LoA1" loa = { "": "http://eidas.europa.eu/LoA/low", idp_conf["entityid"]: expected_loa } base = self.construct_base_url_from_entity_id(idp_conf["entityid"]) conf = { "idp_config": idp_conf, "endpoints": ENDPOINTS, "base": base, "state_id": "state_id", "acr_mapping": loa } samlfrontend = SamlFrontend(None, INTERNAL_ATTRIBUTES, conf) samlfrontend.register_endpoints(["foo"]) idp_metadata_str = create_metadata_from_config_dict( samlfrontend.config) sp_conf["metadata"]["inline"].append(idp_metadata_str) fakesp = FakeSP(None, config=SPConfig().load(sp_conf, metadata_construction=False)) auth_info = AuthenticationInformation(PASSWORD, "2015-09-30T12:21:37Z", idp_conf["entityid"]) internal_response = InternalResponse(auth_info=auth_info) context = Context() context.state = State() resp_args = { "name_id_policy": NameIDPolicy(format=NAMEID_FORMAT_TRANSIENT), "in_response_to": None, "destination": "", "sp_entity_id": None, "binding": BINDING_HTTP_REDIRECT } request_state = samlfrontend.save_state(context, resp_args, "") context.state.add(conf["state_id"], request_state) resp = samlfrontend.handle_authn_response(context, internal_response) resp_dict = parse_qs(urlparse(resp.message).query) resp = fakesp.parse_authn_request_response( resp_dict['SAMLResponse'][0], BINDING_HTTP_REDIRECT) assert len(resp.assertion.authn_statement) == 1 authn_context_class_ref = resp.assertion.authn_statement[ 0].authn_context.authn_context_class_ref assert authn_context_class_ref.text == expected_loa
def internal_response(): auth_info = AuthenticationInformation("auth_class_ref", "timestamp", "issuer") internal_response = InternalResponse(auth_info=auth_info) internal_response.set_user_id_hash_type(UserIdHashType.persistent) internal_response.add_attributes({ "displayName": "Test", "co": "example", "sn": "removed_by_filter" }) internal_response.user_id = "usrID" return internal_response
def accept_consent(self, context): """ Endpoint for handling accepted consent. :type context: satosa.context.Context :rtype: satosa.response.Response :param context: response context :return: response """ consent_state = context.state[self.name] saved_resp = consent_state['internal_response'] internal_response = InternalResponse.from_dict(saved_resp) del context.state[self.name] return super().process(context, internal_response)
def accept_consent(self, context): """ Endpoint for handling accepted consent. :type context: satosa.context.Context :rtype: satosa.response.Response :param context: response context :return: response """ consent_state = context.state[consent.STATE_KEY] saved_resp = consent_state['internal_response'] internal_response = InternalResponse.from_dict(saved_resp) del context.state[consent.STATE_KEY] return super().process(context, internal_response)
def test_auth_resp_callback_func_hashes_all_specified_attributes( self, context, satosa_config): satosa_config["INTERNAL_ATTRIBUTES"]["hash"] = ["user_id", "mail"] base = SATOSABase(satosa_config) attributes = { "user_id": ["user"], "mail": ["*****@*****.**", "*****@*****.**"] } internal_resp = InternalResponse(AuthenticationInformation("", "", "")) internal_resp.attributes = copy.copy(attributes) internal_resp.user_id = "test_user" UserIdHasher.save_state(InternalRequest(UserIdHashType.transient, ""), context.state) context.state[satosa.base.STATE_KEY] = {"requester": "test_requester"} context.state[satosa.routing. STATE_KEY] = satosa_config["FRONTEND_MODULES"][0]["name"] base._auth_resp_callback_func(context, internal_resp) for attr in satosa_config["INTERNAL_ATTRIBUTES"]["hash"]: assert internal_resp.attributes[attr] == [ UserIdHasher.hash_data(satosa_config["USER_ID_HASH_SALT"], v) for v in attributes[attr] ]
def authn_response(self, context): """ Handles the authentication response from the OP. :type context: satosa.context.Context :rtype: satosa.response.Response :param context: The context in SATOSA :return: A SATOSA response. This method is only responsible to call the callback function which generates the Response object. """ state = context.state try: state_data = state.get(self.config["state_id"]) consumer = self.get_consumer() request = context.request aresp = consumer.parse_response(AuthorizationResponse, info=json.dumps(request)) self.verify_state(aresp, state_data, state) rargs = {"code": aresp["code"], "redirect_uri": self.redirect_url, "state": state_data["state"]} atresp = consumer.do_access_token_request(request_args=rargs, state=aresp["state"]) if ("verify_accesstoken_state" not in self.config or self.config["verify_accesstoken_state"]): self.verify_state(atresp, state_data, state) user_info = self.user_information(atresp["access_token"]) internal_response = InternalResponse(auth_info=self.auth_info(request)) internal_response.add_attributes(self.converter.to_internal(self.external_type, user_info)) internal_response.set_user_id(user_info[self.user_id_attr]) if "user_id_params" in self.config: user_id = "" for param in self.config["user_id_params"]: try: user_id += user_info[param] except Exception as error: raise SATOSAAuthenticationError from error internal_response.set_user_id(user_id) context.state.remove(self.config["state_id"]) return self.auth_callback_func(context, internal_response) except Exception as error: satosa_logging(LOGGER, logging.DEBUG, "Not a valid authentication", state, exc_info=True) if isinstance(error, SATOSAError): raise error if state is not None: raise SATOSAAuthenticationError(state, "Not a valid authentication") from error raise
def _translate_response(self, response, state): """ Translates a saml authorization response to an internal response :type response: saml2.response.AuthnResponse :rtype: satosa.internal_data.InternalResponse :param response: The saml authorization response :return: A translated internal response """ _authn_info = response.authn_info()[0] timestamp = response.assertion.authn_statement[0].authn_instant issuer = response.response.issuer.text auth_class_ref = _authn_info[0] auth_info = AuthenticationInformation(auth_class_ref, timestamp, issuer) internal_resp = InternalResponse(auth_info=auth_info) internal_resp.set_user_id(response.get_subject().text) if "user_id_params" in self.config: user_id = "" for param in self.config["user_id_params"]: try: user_id += response.ava[param] except Exception as error: raise SATOSAAuthenticationError from error internal_resp.set_user_id(user_id) internal_resp.add_attributes( self.converter.to_internal(self.attribute_profile, response.ava)) satosa_logging( LOGGER, logging.DEBUG, "received attributes:\n%s" % json.dumps(response.ava, indent=4), state) return internal_resp
def _handle_consent_response(self, context): """ Endpoint for handling consent service response :type context: satosa.context.Context :rtype: satosa.response.Response :param context: response context :return: response """ # Handle answer from consent service state = context.state consent_state = state.get(ConsentModule.STATE_KEY) saved_resp = consent_state["internal_resp"] # rebuild internal_response from state internal_response = InternalResponse.from_dict(saved_resp) requestor = internal_response.to_requestor hash_id = self._get_consent_id(requestor, internal_response.get_user_id(), internal_response.get_attributes()) try: consent_attributes = self._verify_consent(hash_id) except ConnectionError: satosa_logging(LOGGER, logging.ERROR, "Consent service is not reachable, no consent given.", state) # Send an internal_response without any attributes consent_attributes = None if consent_attributes is None: satosa_logging(LOGGER, logging.INFO, "Consent was NOT given", state) # If consent was not given, then don't send any attributes consent_attributes = [] else: satosa_logging(LOGGER, logging.INFO, "Consent was given", state) internal_response = self._filter_attributes(internal_response, consent_attributes) return self._end_consent(context, internal_response)
def internal_response(self): auth_info = AuthenticationInformation("auth_class_ref", "timestamp", "issuer") internal_response = InternalResponse(auth_info=auth_info) internal_response.user_id = "user1" return internal_response
class TestAccountLinking(): @pytest.fixture(autouse=True) def setup(self, signing_key_path): self.account_linking_config = { "enable": True, "rest_uri": "https://localhost:8167", "redirect": "https://localhost:8167/approve", "endpoint": "handle_account_linking", "sign_key": signing_key_path, "verify_ssl": False } self.satosa_config = { "BASE": "https://proxy.example.com", "USER_ID_HASH_SALT": "qwerty", "COOKIE_STATE_NAME": "SATOSA_SATE", "STATE_ENCRYPTION_KEY": "ASDasd123", "PLUGIN_PATH": "", "BACKEND_MODULES": "", "FRONTEND_MODULES": "", "INTERNAL_ATTRIBUTES": {}, "ACCOUNT_LINKING": self.account_linking_config } self.callback_func = MagicMock() self.context = Context() state = State() self.context.state = state auth_info = AuthenticationInformation("auth_class_ref", "timestamp", "issuer") self.internal_response = InternalResponse(auth_info=auth_info) def test_disable_account_linking(self): self.account_linking_config['enable'] = False config = SATOSAConfig(self.satosa_config) account_linking = AccountLinkingModule(config, self.callback_func) account_linking.manage_al(None, None) assert self.callback_func.called @responses.activate def test_store_existing_uuid_in_internal_attributes(self): uuid = "uuid" responses.add(responses.GET, "%s/get_id" % self.account_linking_config['rest_uri'], status=200, body=uuid, content_type='text/html') account_linking = AccountLinkingModule( SATOSAConfig(self.satosa_config), self.callback_func) account_linking.manage_al(self.context, self.internal_response) assert self.internal_response.get_user_id() == uuid @responses.activate def test_account_link_does_not_exists(self): ticket = "ticket" responses.add(responses.GET, "%s/get_id" % self.account_linking_config['rest_uri'], status=404, body=ticket, content_type='text/html') account_linking = AccountLinkingModule( SATOSAConfig(self.satosa_config), self.callback_func) result = account_linking.manage_al(self.context, self.internal_response) assert isinstance(result, Redirect) assert self.account_linking_config["redirect"] in result.message @responses.activate def test_handle_failed_connection(self): exception = requests.ConnectionError("No connection") responses.add(responses.GET, "%s/get_id" % self.account_linking_config['rest_uri'], body=exception) account_linking = AccountLinkingModule( SATOSAConfig(self.satosa_config), self.callback_func) with pytest.raises(SATOSAAuthenticationError): account_linking.manage_al(self.context, self.internal_response)
def internal_resp(self): resp = InternalResponse(AuthenticationInformation(None, str(datetime.now()), 'https://idp.example.com')) resp.requester = 'client1' resp.user_id = 'user1' resp.attributes['affiliation'] = ['student'] return resp
def internal_response(self, idp_conf): auth_info = AuthenticationInformation(PASSWORD, "2015-09-30T12:21:37Z", idp_conf["entityid"]) internal_response = InternalResponse(auth_info=auth_info) internal_response.attributes = AttributeMapper(INTERNAL_ATTRIBUTES).to_internal("saml", USERS["testuser1"]) return internal_response
class TestAccountLinking(): @pytest.fixture(autouse=True) def setup(self, signing_key_path): self.account_linking_config = { "enable": True, "rest_uri": "https://localhost:8167", "redirect": "https://localhost:8167/approve", "endpoint": "handle_account_linking", "sign_key": signing_key_path, "verify_ssl": False } self.satosa_config = { "BASE": "https://proxy.example.com", "USER_ID_HASH_SALT": "qwerty", "COOKIE_STATE_NAME": "SATOSA_SATE", "STATE_ENCRYPTION_KEY": "ASDasd123", "PLUGIN_PATH": "", "BACKEND_MODULES": "", "FRONTEND_MODULES": "", "INTERNAL_ATTRIBUTES": {}, "ACCOUNT_LINKING": self.account_linking_config } self.callback_func = MagicMock() self.context = Context() state = State() self.context.state = state auth_info = AuthenticationInformation("auth_class_ref", "timestamp", "issuer") self.internal_response = InternalResponse(auth_info=auth_info) def test_disable_account_linking(self): self.account_linking_config['enable'] = False config = SATOSAConfig(self.satosa_config) account_linking = AccountLinkingModule(config, self.callback_func) account_linking.manage_al(None, None) assert self.callback_func.called @responses.activate def test_store_existing_uuid_in_internal_attributes(self): uuid = "uuid" responses.add( responses.GET, "%s/get_id" % self.account_linking_config['rest_uri'], status=200, body=uuid, content_type='text/html' ) account_linking = AccountLinkingModule( SATOSAConfig(self.satosa_config), self.callback_func ) account_linking.manage_al(self.context, self.internal_response) assert self.internal_response.get_user_id() == uuid @responses.activate def test_account_link_does_not_exists(self): ticket = "ticket" responses.add( responses.GET, "%s/get_id" % self.account_linking_config['rest_uri'], status=404, body=ticket, content_type='text/html' ) account_linking = AccountLinkingModule( SATOSAConfig(self.satosa_config), self.callback_func ) result = account_linking.manage_al(self.context, self.internal_response) assert isinstance(result, Redirect) assert self.account_linking_config["redirect"] in result.message @responses.activate def test_handle_failed_connection(self): exception = requests.ConnectionError("No connection") responses.add(responses.GET, "%s/get_id" % self.account_linking_config['rest_uri'], body=exception) account_linking = AccountLinkingModule( SATOSAConfig(self.satosa_config), self.callback_func ) with pytest.raises(SATOSAAuthenticationError): account_linking.manage_al(self.context, self.internal_response)