Compare commits

...
Author SHA1 Message Date
Trenton HolmesandClaude Sonnet 5 f5c3a9cfe0 Fix: reject non-dict user_args/barcode_tag_mapping in config API
JSONField(binary=True) accepts any JSON value, so a truthy non-dict
(bool/int/list/string) silently passed validation and later crashed
tesseract.py's OCR arg merge or barcodes.py's Barcode.is_tag.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 13:31:39 -07:00
2 changed files with 64 additions and 0 deletions
@@ -194,6 +194,56 @@ class TestApiAppConfig(DirectoriesMixin, APITestCase):
self.assertEqual(config.language, None)
self.assertEqual(config.barcode_tag_mapping, None)
def test_api_update_config_rejects_non_dict_user_args(self) -> None:
"""
GIVEN:
- API request to update app config with a JSON-encoded non-dict
value (e.g. a bare string) for the user_args JSONField
WHEN:
- API is called
THEN:
- Request is rejected with a 400, not silently accepted
- Config is not updated
"""
response = self.client.patch(
f"{self.ENDPOINT}1/",
json.dumps(
{
"user_args": json.dumps("not a dict"),
},
),
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
config = ApplicationConfiguration.objects.first()
assert config is not None
self.assertEqual(config.user_args, None)
def test_api_update_config_rejects_non_dict_barcode_tag_mapping(self) -> None:
"""
GIVEN:
- API request to update app config with a JSON-encoded non-dict
value (e.g. a bare list) for the barcode_tag_mapping JSONField
WHEN:
- API is called
THEN:
- Request is rejected with a 400, not silently accepted
- Config is not updated
"""
response = self.client.patch(
f"{self.ENDPOINT}1/",
json.dumps(
{
"barcode_tag_mapping": json.dumps([1, 2, 3]),
},
),
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
config = ApplicationConfiguration.objects.first()
assert config is not None
self.assertEqual(config.barcode_tag_mapping, None)
def test_api_replace_app_logo(self) -> None:
"""
GIVEN:
+14
View File
@@ -235,6 +235,20 @@ class ApplicationConfigurationSerializer(
) -> list[str]:
return sorted(name for name in os.environ if name.startswith("PAPERLESS_"))
def validate_user_args(self, value):
if value is not None and not isinstance(value, dict):
raise serializers.ValidationError(
"user_args must be a JSON object.",
)
return value
def validate_barcode_tag_mapping(self, value):
if value is not None and not isinstance(value, dict):
raise serializers.ValidationError(
"barcode_tag_mapping must be a JSON object.",
)
return value
def run_validation(self, data):
# Empty strings treated as None to avoid unexpected behavior
if "user_args" in data and data["user_args"] == "":