Return payload as text only for text/plain secrets

currently SDK always returns Response.text for secret payload,
which is problematic for pure binary secrets,
as the value returned is decoded as whatever encoding
`chardet` lib has detected on this random binary data.

This makes SDK basically unusable for retreving and using binary
secrets, as one can not make any educated guess using only sdk-provided
data on what encoding to use to get the payload as raw bytes.

Instead, do what barbicanclient does, and return raw bytes
(Response.content) for everything but content type "text/plain",
for which decode those bytes to UTF-8.

This will also make it easier to transition from barbicanclient to
openstacksdk if needed.

Change-Id: I0e5ac1288c0d0423fa3a7a4e63173675b78aae79
This commit is contained in:
Pavlo Shchelokovskyy 2024-09-05 18:21:52 +03:00 committed by Stephen Finucane
parent 93f44c3b62
commit 2692290776
3 changed files with 37 additions and 7 deletions

View File

@ -112,7 +112,10 @@ class Secret(resource.Resource):
headers={"Accept": content_type},
skip_cache=skip_cache,
)
response["payload"] = payload.text
if content_type == "text/plain":
response["payload"] = payload.content.decode("UTF-8")
else:
response["payload"] = payload.content
# We already have the JSON here so don't call into _translate_response
self._update_from_body_attrs(response)

View File

@ -100,16 +100,14 @@ class TestSecret(base.TestCase):
sess.get.assert_called_once_with("secrets/id")
def _test_payload(self, sot, metadata, content_type):
content_type = "some/type"
def _test_payload(self, sot, metadata, content_type="some/type"):
metadata_response = mock.Mock()
# Use copy because the dict gets consumed.
metadata_response.json = mock.Mock(return_value=metadata.copy())
payload_response = mock.Mock()
payload = "secret info"
payload_response.text = payload
payload = b"secret info"
payload_response.content = payload
sess = mock.Mock()
sess.get = mock.Mock(side_effect=[metadata_response, payload_response])
@ -129,7 +127,11 @@ class TestSecret(base.TestCase):
]
)
self.assertEqual(rv.payload, payload)
if content_type == "text/plain":
expected_payload = payload.decode("utf-8")
else:
expected_payload = payload
self.assertEqual(rv.payload, expected_payload)
self.assertEqual(rv.status, metadata["status"])
def test_get_with_payload_from_argument(self):
@ -146,3 +148,12 @@ class TestSecret(base.TestCase):
}
sot = secret.Secret(id="id")
self._test_payload(sot, metadata, content_type)
def test_get_with_text_payload(self):
content_type = "text/plain"
metadata = {
"status": "fine",
"content_types": {"default": content_type},
}
sot = secret.Secret(id="id")
self._test_payload(sot, metadata, content_type)

View File

@ -0,0 +1,16 @@
---
features:
- |
For Barbican secrets with detected or provided content type other than
"text/plain" SDK now returns the secret payload as raw bytes.
For secrets with content type "text/plain", the payload is returned
as string, decoded to UTF-8.
This behavior is following python-barbicanclient, and allows to use
SDK with Barbican secrets that have binary payloads
(e.g. "application/octet-stream").
upgrade:
- |
The payload of Barbican secrets with other than "text/plain" content type
is now returned as raw bytes.
For secrets with content type "text/plain", the payload is returned
as string, decoded to UTF-8.