fix(hashview): accept native-JSON customer arrays from /v1/customers

The server now returns `users` as a native JSON array (issue #229,
no double-decode), but list_customers unconditionally ran
json.loads() on it, raising TypeError and breaking the entire
customer->hashfile enumeration flow. Accept both the native array
and the legacy double-encoded string, mirroring list_wordlists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Justin Bollinger
2026-07-21 17:51:20 -04:00
co-authored by Claude Opus 4.8
parent 17a1d6794a
commit ace699b872
2 changed files with 25 additions and 1 deletions
+5 -1
View File
@@ -1169,7 +1169,11 @@ class HashviewAPI:
resp.raise_for_status()
data = resp.json()
if "users" in data:
customers = json.loads(data["users"])
customers = data["users"]
# Newer servers return a native JSON array (issue #229); older ones
# double-encode it as a JSON string. Support both.
if isinstance(customers, str):
customers = json.loads(customers)
return {"customers": customers}
return data
+20
View File
@@ -176,6 +176,26 @@ class TestHashviewAPI:
captured = capsys.readouterr()
assert "No customers found" in captured.out
def test_list_customers_native_json_array(self, api):
"""Server returns `users` as a native JSON array (issue #229, no double-decode)."""
mock_resp = Mock()
mock_resp.json.return_value = {"users": [{"id": 1, "name": "Acme"}]}
mock_resp.raise_for_status = Mock()
api.session.get.return_value = mock_resp
result = api.list_customers()
assert result["customers"] == [{"id": 1, "name": "Acme"}]
def test_list_customers_legacy_json_string(self, api):
"""Older servers double-encode `users` as a JSON string; still supported."""
mock_resp = Mock()
mock_resp.json.return_value = {"users": json.dumps([{"id": 2, "name": "Beta"}])}
mock_resp.raise_for_status = Mock()
api.session.get.return_value = mock_resp
result = api.list_customers()
assert result["customers"] == [{"id": 2, "name": "Beta"}]
def test_upload_cracked_hashes_success(self, api, tmp_path):
"""Test uploading cracked hashes with valid lines (real API if possible)."""
hashview_url, hashview_api_key = self._get_hashview_config()