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