Skip to content

Commit 75e8147

Browse files
Merge pull request #184 from GitGuardian/benjaminrigaud/end-590-harden-api-tokens-non-json
fix(client): return Detail on non-JSON 2xx for api_tokens and honeytoken-with-context
2 parents 2c89d05 + d1486fc commit 75e8147

3 files changed

Lines changed: 54 additions & 3 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
### Fixed
2+
3+
- `api_tokens()` and `create_honeytoken_with_context()` now return a `Detail` instead of raising a raw `JSONDecodeError` when the server answers a `2xx` with a non-JSON body (e.g. an HTML page served when the instance URL is wrong). Like the other endpoints, they now gate JSON parsing on the response status and content type.

pygitguardian/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ def api_tokens(
425425
result = Detail("The request timed out.")
426426
result.status_code = 504
427427
else:
428-
if resp.ok:
428+
if is_ok(resp):
429429
result = APITokensResponse.from_dict(resp.json())
430430
else:
431431
result = load_detail(resp)
@@ -741,7 +741,7 @@ def create_honeytoken_with_context(
741741
result = Detail("The request timed out.")
742742
result.status_code = 504
743743
else:
744-
if resp.ok:
744+
if is_create_ok(resp):
745745
result = HoneytokenWithContextResponse.from_dict(resp.json())
746746
else:
747747
result = load_detail(resp)

tests/test_client.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1003,7 +1003,7 @@ def test_api_tokens(client: GGClient, token):
10031003
mock_response = responses.get(
10041004
url=client._url_from_endpoint(f"api_tokens/{token}", "v1"),
10051005
content_type="application/json",
1006-
status=201,
1006+
status=200,
10071007
json={
10081008
"id": "5ddaad0c-5a0c-4674-beb5-1cd198d13360",
10091009
"name": "myTokenName",
@@ -1050,6 +1050,28 @@ def test_api_tokens_error(
10501050
assert isinstance(result, Detail)
10511051

10521052

1053+
@responses.activate
1054+
def test_api_tokens_non_json_body(client: GGClient):
1055+
"""
1056+
GIVEN an api_tokens endpoint answering 200 with a non-JSON body
1057+
(e.g. the dashboard SPA's HTML, served when the instance URL is wrong)
1058+
WHEN calling api_tokens
1059+
THEN it returns a Detail instead of raising a raw JSONDecodeError
1060+
"""
1061+
mock_response = responses.get(
1062+
url=client._url_from_endpoint("api_tokens/self", "v1"),
1063+
content_type="text/html",
1064+
status=200,
1065+
body="<!doctype html><html><body>GitGuardian</body></html>",
1066+
)
1067+
1068+
result = client.api_tokens()
1069+
1070+
assert mock_response.call_count == 1
1071+
assert isinstance(result, Detail)
1072+
assert result.status_code == 200
1073+
1074+
10531075
@responses.activate
10541076
def test_create_honeytoken(
10551077
client: GGClient,
@@ -1190,6 +1212,32 @@ def test_create_honeytoken_with_context_error(
11901212
assert isinstance(result, Detail)
11911213

11921214

1215+
@responses.activate
1216+
def test_create_honeytoken_with_context_non_json_body(client: GGClient):
1217+
"""
1218+
GIVEN the honeytoken with-context endpoint answering 2xx with a non-JSON body
1219+
WHEN calling create_honeytoken_with_context
1220+
THEN it returns a Detail instead of raising a raw JSONDecodeError
1221+
"""
1222+
mock_response = responses.post(
1223+
url=client._url_from_endpoint("honeytokens/with-context", "v1"),
1224+
content_type="text/html",
1225+
status=201,
1226+
body="<!doctype html><html></html>",
1227+
)
1228+
1229+
result = client.create_honeytoken_with_context(
1230+
name="honeytoken A",
1231+
description="honeytoken used in the repository AA",
1232+
type_="AWS",
1233+
filename="aws.yaml",
1234+
)
1235+
1236+
assert mock_response.call_count == 1
1237+
assert isinstance(result, Detail)
1238+
assert result.status_code == 201
1239+
1240+
11931241
@responses.activate
11941242
def test_create_jwt(
11951243
client: GGClient,

0 commit comments

Comments
 (0)