def create_module(self, consent_config): self.consent_module = Consent(consent_config, internal_attributes={ "attributes": {}, "user_id_to_attr": USER_ID_ATTR }, name="Consent", base_url="https://satosa.example.com") self.consent_module.next = lambda ctx, data: (ctx, data)
def create_module(self, consent_config): self.consent_module = Consent(consent_config, internal_attributes={"attributes": {}, "user_id_to_attr": USER_ID_ATTR}, name="Consent", base_url="https://satosa.example.com") self.consent_module.next = lambda ctx, data: (ctx, data)
class TestConsent: @pytest.fixture def consent_config(self, signing_key_path): consent_config = { "api_url": CONSENT_SERVICE_URL, "redirect_url": "{}/consent".format(CONSENT_SERVICE_URL), "sign_key": signing_key_path, } return consent_config @pytest.fixture(autouse=True) def create_module(self, consent_config): self.consent_module = Consent(consent_config, internal_attributes={ "attributes": {}, "user_id_to_attr": USER_ID_ATTR }, name="Consent", base_url="https://satosa.example.com") self.consent_module.next = lambda ctx, data: (ctx, data) @pytest.fixture def internal_response(self): auth_info = AuthenticationInformation("auth_class_ref", "timestamp", "issuer") internal_response = InternalData(auth_info=auth_info) internal_response.requester = "client" internal_response.attributes = ATTRIBUTES return internal_response @pytest.fixture def internal_request(self): req = InternalData( subject_type=NAMEID_FORMAT_PERSISTENT, requester="example_requester", ) req.attributes = FILTER + ["sn"] return req @pytest.fixture(scope="session") def consent_verify_endpoint_regex(self): return re.compile(r"{}/verify/.*".format(CONSENT_SERVICE_URL)) @pytest.fixture(scope="session") def consent_registration_endpoint_regex(self): return re.compile(r"{}/creq/.*".format(CONSENT_SERVICE_URL)) def assert_redirect(self, redirect_resp, expected_ticket): assert isinstance(redirect_resp, Redirect) path = urlparse(redirect_resp.message).path assert path == "/consent/" + expected_ticket def assert_registration_req(self, request, internal_response, sign_key_path, base_url, requester_name): split_path = request.path_url.lstrip("/").split("/") assert len(split_path) == 2 jwks = split_path[1] # Verify signature sign_key = RSAKey(key=rsa_load(sign_key_path), use="sig") jws = JWS() jws.verify_compact(jwks, [sign_key]) consent_args = jws.msg assert consent_args["attr"] == internal_response.attributes assert consent_args[ "redirect_endpoint"] == base_url + "/consent/handle_consent" assert consent_args["requester_name"] == requester_name assert consent_args["locked_attrs"] == [USER_ID_ATTR] assert "id" in consent_args @responses.activate def test_verify_consent_false_on_http_400(self, consent_config): consent_id = "1234" responses.add(responses.GET, "{}/verify/{}".format(consent_config["api_url"], consent_id), status=400) assert not self.consent_module._verify_consent(consent_id) @responses.activate def test_verify_consent(self, consent_config): consent_id = "1234" responses.add(responses.GET, "{}/verify/{}".format(consent_config["api_url"], consent_id), status=200, body=json.dumps(FILTER)) assert self.consent_module._verify_consent(consent_id) == FILTER @pytest.mark.parametrize('status', [401, 404, 418, 500]) @responses.activate def test_consent_registration_raises_on_unexpected_status_code( self, status, consent_config): responses.add(responses.GET, re.compile(r"{}/creq/.*".format( consent_config["api_url"])), status=status) with pytest.raises(UnexpectedResponseError): self.consent_module._consent_registration({}) @responses.activate def test_consent_registration(self, consent_config): responses.add(responses.GET, re.compile(r"{}/creq/.*".format( consent_config["api_url"])), status=200, body="ticket") assert self.consent_module._consent_registration({}) == "ticket" @responses.activate def test_consent_handles_connection_error(self, context, internal_response, internal_request, consent_verify_endpoint_regex): responses.add(responses.GET, consent_verify_endpoint_regex, body=requests.ConnectionError("No connection")) context.state[consent.STATE_KEY] = {"filter": []} with responses.RequestsMock( assert_all_requests_are_fired=True) as rsps: rsps.add(responses.GET, consent_verify_endpoint_regex, body=requests.ConnectionError("No connection")) context, internal_response = self.consent_module.process( context, internal_response) assert context assert not internal_response.attributes @responses.activate def test_consent_prev_given(self, context, internal_response, internal_request, consent_verify_endpoint_regex): responses.add(responses.GET, consent_verify_endpoint_regex, status=200, body=json.dumps(FILTER)) context.state[consent.STATE_KEY] = { "filter": internal_request.attributes } context, internal_response = self.consent_module.process( context, internal_response) assert context assert "displayName" in internal_response.attributes def test_consent_full_flow(self, context, consent_config, internal_response, internal_request, consent_verify_endpoint_regex, consent_registration_endpoint_regex): expected_ticket = "my_ticket" requester_name = internal_response.requester_name context.state[consent.STATE_KEY] = { "filter": internal_request.attributes, "requester_name": requester_name } with responses.RequestsMock() as rsps: rsps.add(responses.GET, consent_verify_endpoint_regex, status=401) rsps.add(responses.GET, consent_registration_endpoint_regex, status=200, body=expected_ticket) resp = self.consent_module.process(context, internal_response) self.assert_redirect(resp, expected_ticket) self.assert_registration_req(rsps.calls[1].request, internal_response, consent_config["sign_key"], self.consent_module.base_url, requester_name) with responses.RequestsMock() as rsps: # Now consent has been given, consent service returns 200 OK rsps.add(responses.GET, consent_verify_endpoint_regex, status=200, body=json.dumps(FILTER)) context, internal_response = self.consent_module._handle_consent_response( context) assert internal_response.attributes["displayName"] == ["Test"] assert internal_response.attributes["co"] == ["example"] assert "sn" not in internal_response.attributes # 'sn' should be filtered @responses.activate def test_consent_not_given(self, context, consent_config, internal_response, internal_request, consent_verify_endpoint_regex, consent_registration_endpoint_regex): expected_ticket = "my_ticket" responses.add(responses.GET, consent_verify_endpoint_regex, status=401) responses.add(responses.GET, consent_registration_endpoint_regex, status=200, body=expected_ticket) requester_name = internal_response.requester_name context.state[consent.STATE_KEY] = {} resp = self.consent_module.process(context, internal_response) self.assert_redirect(resp, expected_ticket) self.assert_registration_req(responses.calls[1].request, internal_response, consent_config["sign_key"], self.consent_module.base_url, requester_name) new_context = Context() new_context.state = context.state # Verify endpoint of consent service still gives 401 (no consent given) context, internal_response = self.consent_module._handle_consent_response( context) assert not internal_response.attributes def test_get_consent_id(self): attributes = {"foo": ["bar", "123"], "abc": ["xyz", "456"]} id = self.consent_module._get_consent_id("test-requester", "user1", attributes) assert id == "ZTRhMTJmNWQ2Yjk2YWE0YzgyMzU4NTlmNjM3YjlhNmQ4ZjZiODMzOTQ0ZjNiMTVmODEwMDhmMDg5N2JlMDg0Y2ZkZGFkOTkzMDZiNDZiNjMxNzBkYzExOTcxN2RkMzJjMmY5NzRhZDA2NjYxMTg0NjkyYzdjN2IxNTRiZDkwNmM=" def test_filter_attributes(self): filtered_attributes = self.consent_module._filter_attributes( ATTRIBUTES, FILTER) assert Counter(filtered_attributes.keys()) == Counter(FILTER) @responses.activate def test_manage_consent_without_filter_passes_through_all_attributes( self, context, internal_response, consent_verify_endpoint_regex): # fake previous consent responses.add(responses.GET, consent_verify_endpoint_regex, status=200, body=json.dumps(list( internal_response.attributes.keys()))) context.state[consent.STATE_KEY] = {"filter": []} # No filter self.consent_module.process(context, internal_response) consent_hash = urlparse( responses.calls[0].request.url).path.split("/")[2] expected_hash = self.consent_module._get_consent_id( internal_response.requester, internal_response.subject_id, internal_response.attributes) assert consent_hash == expected_hash
class TestConsent: @pytest.fixture def consent_config(self, signing_key_path): consent_config = { "api_url": CONSENT_SERVICE_URL, "redirect_url": "{}/consent".format(CONSENT_SERVICE_URL), "sign_key": signing_key_path, } return consent_config @pytest.fixture(autouse=True) def create_module(self, consent_config): self.consent_module = Consent(consent_config, internal_attributes={"attributes": {}, "user_id_to_attr": USER_ID_ATTR}, name="Consent", base_url="https://satosa.example.com") self.consent_module.next = lambda ctx, data: (ctx, data) @pytest.fixture 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 @pytest.fixture def internal_request(self): req = InternalRequest(UserIdHashType.persistent, "example_requester") req.approved_attributes = FILTER + ["sn"] return req @pytest.fixture(scope="session") def consent_verify_endpoint_regex(self): return re.compile(r"{}/verify/.*".format(CONSENT_SERVICE_URL)) @pytest.fixture(scope="session") def consent_registration_endpoint_regex(self): return re.compile(r"{}/creq/.*".format(CONSENT_SERVICE_URL)) def assert_redirect(self, redirect_resp, expected_ticket): assert isinstance(redirect_resp, Redirect) path = urlparse(redirect_resp.message).path assert path == "/consent/" + expected_ticket def assert_registration_req(self, request, internal_response, sign_key_path, base_url, requester_name): split_path = request.path_url.lstrip("/").split("/") assert len(split_path) == 2 jwks = split_path[1] # Verify signature sign_key = RSAKey(key=rsa_load(sign_key_path), use="sig") jws = JWS() jws.verify_compact(jwks, [sign_key]) consent_args = jws.msg assert consent_args["attr"] == internal_response.attributes assert consent_args["redirect_endpoint"] == base_url + "/consent/handle_consent" assert consent_args["requester_name"] == requester_name assert consent_args["locked_attrs"] == [USER_ID_ATTR] assert "id" in consent_args @responses.activate def test_verify_consent_false_on_http_400(self, consent_config): consent_id = "1234" responses.add(responses.GET, "{}/verify/{}".format(consent_config["api_url"], consent_id), status=400) assert not self.consent_module._verify_consent(consent_id) @responses.activate def test_verify_consent(self, consent_config): consent_id = "1234" responses.add(responses.GET, "{}/verify/{}".format(consent_config["api_url"], consent_id), status=200, body=json.dumps(FILTER)) assert self.consent_module._verify_consent(consent_id) == FILTER @pytest.mark.parametrize('status', [ 401, 404, 418, 500 ]) @responses.activate def test_consent_registration_raises_on_unexpected_status_code(self, status, consent_config): responses.add(responses.GET, re.compile(r"{}/creq/.*".format(consent_config["api_url"])), status=status) with pytest.raises(UnexpectedResponseError): self.consent_module._consent_registration({}) @responses.activate def test_consent_registration(self, consent_config): responses.add(responses.GET, re.compile(r"{}/creq/.*".format(consent_config["api_url"])), status=200, body="ticket") assert self.consent_module._consent_registration({}) == "ticket" @responses.activate def test_consent_handles_connection_error(self, context, internal_response, internal_request, consent_verify_endpoint_regex): responses.add(responses.GET, consent_verify_endpoint_regex, body=requests.ConnectionError("No connection")) context.state[consent.STATE_KEY] = {"filter": []} with responses.RequestsMock(assert_all_requests_are_fired=True) as rsps: rsps.add(responses.GET, consent_verify_endpoint_regex, body=requests.ConnectionError("No connection")) context, internal_response = self.consent_module.process(context, internal_response) assert context assert not internal_response.attributes @responses.activate def test_consent_prev_given(self, context, internal_response, internal_request, consent_verify_endpoint_regex): responses.add(responses.GET, consent_verify_endpoint_regex, status=200, body=json.dumps(FILTER)) context.state[consent.STATE_KEY] = {"filter": internal_request.approved_attributes} context, internal_response = self.consent_module.process(context, internal_response) assert context assert "displayName" in internal_response.attributes def test_consent_full_flow(self, context, consent_config, internal_response, internal_request, consent_verify_endpoint_regex, consent_registration_endpoint_regex): expected_ticket = "my_ticket" requester_name = [{"lang": "en", "text": "test requester"}] context.state[consent.STATE_KEY] = {"filter": internal_request.approved_attributes, "requester_name": requester_name} with responses.RequestsMock() as rsps: rsps.add(responses.GET, consent_verify_endpoint_regex, status=401) rsps.add(responses.GET, consent_registration_endpoint_regex, status=200, body=expected_ticket) resp = self.consent_module.process(context, internal_response) self.assert_redirect(resp, expected_ticket) self.assert_registration_req(rsps.calls[1].request, internal_response, consent_config["sign_key"], self.consent_module.base_url, requester_name) with responses.RequestsMock() as rsps: # Now consent has been given, consent service returns 200 OK rsps.add(responses.GET, consent_verify_endpoint_regex, status=200, body=json.dumps(FILTER)) context, internal_response = self.consent_module._handle_consent_response(context) assert internal_response.attributes["displayName"] == ["Test"] assert internal_response.attributes["co"] == ["example"] assert "sn" not in internal_response.attributes # 'sn' should be filtered @responses.activate def test_consent_not_given(self, context, consent_config, internal_response, internal_request, consent_verify_endpoint_regex, consent_registration_endpoint_regex): expected_ticket = "my_ticket" responses.add(responses.GET, consent_verify_endpoint_regex, status=401) responses.add(responses.GET, consent_registration_endpoint_regex, status=200, body=expected_ticket) context.state[consent.STATE_KEY] = {"filter": [], "requester_name": None} resp = self.consent_module.process(context, internal_response) self.assert_redirect(resp, expected_ticket) self.assert_registration_req(responses.calls[1].request, internal_response, consent_config["sign_key"], self.consent_module.base_url, None) new_context = Context() new_context.state = context.state # Verify endpoint of consent service still gives 401 (no consent given) context, internal_response = self.consent_module._handle_consent_response(context) assert not internal_response.attributes def test_get_consent_id(self): attributes = {"foo": ["bar", "123"], "abc": ["xyz", "456"]} id = self.consent_module._get_consent_id("test-requester", "user1", attributes) assert id == "ZTRhMTJmNWQ2Yjk2YWE0YzgyMzU4NTlmNjM3YjlhNmQ4ZjZiODMzOTQ0ZjNiMTVmODEwMDhmMDg5N2JlMDg0Y2ZkZGFkOTkzMDZiNDZiNjMxNzBkYzExOTcxN2RkMzJjMmY5NzRhZDA2NjYxMTg0NjkyYzdjN2IxNTRiZDkwNmM=" def test_filter_attributes(self): filtered_attributes = self.consent_module._filter_attributes(ATTRIBUTES, FILTER) assert Counter(filtered_attributes.keys()) == Counter(FILTER) @responses.activate def test_manage_consent_filters_attributes_before_send_to_consent_service(self, context, internal_request, internal_response, consent_verify_endpoint_regex): approved_attributes = ["foo", "bar"] # fake previous consent responses.add(responses.GET, consent_verify_endpoint_regex, status=200, body=json.dumps(approved_attributes)) attributes = {"foo": "123", "bar": "456", "abc": "should be filtered"} internal_response.attributes = attributes context.state[consent.STATE_KEY] = {"filter": approved_attributes} self.consent_module.process(context, internal_response) consent_hash = urlparse(responses.calls[0].request.url).path.split("/")[2] expected_hash = self.consent_module._get_consent_id(internal_response.requester, internal_response.user_id, {k: v for k, v in attributes.items() if k in approved_attributes}) assert consent_hash == expected_hash @responses.activate def test_manage_consent_without_filter_passes_through_all_attributes(self, context, internal_response, consent_verify_endpoint_regex): # fake previous consent responses.add(responses.GET, consent_verify_endpoint_regex, status=200, body=json.dumps(list(internal_response.attributes.keys()))) context.state[consent.STATE_KEY] = {"filter": []} # No filter self.consent_module.process(context, internal_response) consent_hash = urlparse(responses.calls[0].request.url).path.split("/")[2] expected_hash = self.consent_module._get_consent_id(internal_response.requester, internal_response.user_id, internal_response.attributes) assert consent_hash == expected_hash