diff --git a/.github/workflows/build-test-release.yml b/.github/workflows/build-test-release.yml index 55daf30..02df79f 100644 --- a/.github/workflows/build-test-release.yml +++ b/.github/workflows/build-test-release.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v4 - id: matrix - uses: splunk/addonfactory-test-matrix-action@v1.12 + uses: splunk/addonfactory-test-matrix-action@v1.13 fossa-scan: continue-on-error: true diff --git a/poetry.lock b/poetry.lock index ad77fdb..65f18ae 100644 --- a/poetry.lock +++ b/poetry.lock @@ -480,13 +480,13 @@ files = [ [[package]] name = "pytest" -version = "7.4.3" +version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, - {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] diff --git a/splunktaucclib/rest_handler/handler.py b/splunktaucclib/rest_handler/handler.py index 4990282..c8ab9bf 100644 --- a/splunktaucclib/rest_handler/handler.py +++ b/splunktaucclib/rest_handler/handler.py @@ -22,6 +22,7 @@ import json import traceback import urllib.parse +from defusedxml import ElementTree from functools import wraps from solnlib.splunk_rest_client import SplunkRestClient @@ -41,6 +42,23 @@ def _check_name_for_create(name): raise RestError(400, 'Name starting with "_" is not allowed for entity') +def _parse_error_msg(exc: binding.HTTPError) -> str: + permission_msg = "do not have permission to perform this operation" + try: + msgs = json.loads(exc.body)["messages"] + text = msgs[0]["text"] + except json.JSONDecodeError: + try: + text = ElementTree.fromstring(exc.body).findtext("./messages/msg") + except ElementTree.ParseError: + return exc.body.decode() + except (KeyError, IndexError): + return exc.body.decode() + if exc.status == 403 and permission_msg in text: + return "This operation is forbidden." + return text + + def _pre_request(existing): """ Encode payload before request. @@ -126,7 +144,7 @@ def wrapper(self, *args, **kwargs): except RestError: raise except binding.HTTPError as exc: - raise RestError(exc.status, str(exc)) + raise RestError(exc.status, _parse_error_msg(exc)) except Exception: raise RestError(500, traceback.format_exc()) diff --git a/tests/integration/test_rest_handler_handler.py b/tests/integration/test_rest_handler_handler.py index c0eff0a..8723421 100644 --- a/tests/integration/test_rest_handler_handler.py +++ b/tests/integration/test_rest_handler_handler.py @@ -57,8 +57,7 @@ def test_inputs_api_call(): def test_400_api_call(): expected_msg = """Unexpected error "<class 'splunktaucclib.rest_handler.error.RestError'>" -from python handler: "REST Error [400]: Bad Request -- HTTP 400 Bad Request -- -b'{"messages":[{"type":"ERROR","text":"Object id=demo://test_input cannot be deleted in config=inputs."}]}'". +from python handler: "REST Error [400]: Bad Request -- Object id=demo://test_input cannot be deleted in config=inputs.". See splunkd.log/python.log for more details.""" response = requests.delete( @@ -72,9 +71,7 @@ def test_400_api_call(): def test_403_api_call(): expected_msg = """Unexpected error "<class 'splunktaucclib.rest_handler.error.RestError'>" -from python handler: "REST Error [403]: Forbidden -- HTTP 403 Forbidden -- -b'{"messages":[{"type":"ERROR","text":"You (user=user) do not have permission to perform this operation -(requires capability: admin_all_objects)."}]}'". See splunkd.log/python.log for more details.""" +from python handler: "REST Error [403]: Forbidden -- This operation is forbidden.". See splunkd.log/python.log for more details.""" response = requests.post( f"https://{host}:{management_port}/servicesNS/-/demo/demo_demo", diff --git a/tests/unit/test_rest_handler_error.py b/tests/unit/test_rest_handler_error.py index 3321bdb..0fd5133 100644 --- a/tests/unit/test_rest_handler_error.py +++ b/tests/unit/test_rest_handler_error.py @@ -1,6 +1,29 @@ import pytest -from splunktaucclib.rest_handler import error +from splunktaucclib.rest_handler import error, handler +from splunklib import binding +from splunklib.data import record + + +def make_response_record(body, status=200): + class _MocBufReader: + def __init__(self, buf): + if isinstance(buf, str): + self._buf = buf.encode("utf-8") + else: + self._buf = buf + + def read(self, size=None): + return self._buf + + return record( + { + "body": binding.ResponseReader(_MocBufReader(body)), + "status": status, + "reason": "", + "headers": None, + } + ) @pytest.mark.parametrize( @@ -26,3 +49,46 @@ def test_rest_error(status_code, message, expected_message): with pytest.raises(Exception) as exc_info: raise error.RestError(status_code, "message") assert str(exc_info.value) == expected_message + + +def test_parse_err_msg_xml_forbidden(): + original_err_msg = """\n\n \n \ +You (user=user) do not have permission to perform this operation (requires capability: \ +list_storage_passwords OR edit_storage_passwords OR admin_all_objects).\n \n\n""" + expected_err_msg = "This operation is forbidden." + err = binding.HTTPError(make_response_record(original_err_msg, status=403)) + result = handler._parse_error_msg(err) + assert result == expected_err_msg + + +def test_parse_err_msg_xml_forbidden_invalid(): + original_err_msg = "Error message - wrong format" + err = binding.HTTPError(make_response_record(original_err_msg, status=403)) + result = handler._parse_error_msg(err) + assert result == original_err_msg + + +def test_parse_err_msg_json_forbidden(): + original_err_msg = """{"messages":[{"type":"ERROR","text":"You (user=user) do not have permission to \ +perform this operation (requires capability: admin_all_objects)."}]}""" + expected_err_msg = "This operation is forbidden." + err = binding.HTTPError(make_response_record(original_err_msg, status=403)) + result = handler._parse_error_msg(err) + assert result == expected_err_msg + + +def test_parse_err_msg_json_forbidden_invalid(): + original_err_msg = """{"messages":{"type":"ERROR","text":"You (user=user) do not have permission to \ +perform this operation (requires capability: admin_all_objects)."}}""" + err = binding.HTTPError(make_response_record(original_err_msg, status=400)) + result = handler._parse_error_msg(err) + assert result == original_err_msg + + +def test_parse_err_msg_json_bad_request(): + original_err_msg = """{"messages":[{"type":"ERROR","text":"\ +Object id=demo://test_input cannot be deleted in config=inputs."}]}""" + expected_err_msg = "Object id=demo://test_input cannot be deleted in config=inputs." + err = binding.HTTPError(make_response_record(original_err_msg, status=400)) + result = handler._parse_error_msg(err) + assert result == expected_err_msg