mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-09-16 14:48:00 +00:00
Fix: reject non-dict user_args/barcode_tag_mapping in config API (#14118)
JSONField(binary=True) accepts any JSON value, so a truthy non-dict (bool/int/list/string) silently passed validation. Additionally, require string substitutes in barcode_tag_mapping, a non-string substitute value made the barcode tag substitution fail inside get_tag_ids, and the tag was silently skipped. barcode_tag_mapping values must now be strings (empty still means "use the raw barcode").
This commit is contained in:
@@ -194,6 +194,70 @@ class TestApiAppConfig(DirectoriesMixin, APITestCase):
|
||||
self.assertEqual(config.language, None)
|
||||
self.assertEqual(config.barcode_tag_mapping, None)
|
||||
|
||||
def test_api_update_config_json_objects(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- API request to update app config with JSON objects for the
|
||||
user_args and barcode_tag_mapping JSONFields
|
||||
WHEN:
|
||||
- API is called
|
||||
THEN:
|
||||
- Correct HTTP response
|
||||
- Both objects are stored as sent
|
||||
"""
|
||||
user_args = {"unpaper_args": "--pre-rotate 90", "jobs": 2}
|
||||
barcode_tag_mapping = {"TAG:(.*)": "\\g<1>", "ASN12.*": ""}
|
||||
response = self.client.patch(
|
||||
f"{self.ENDPOINT}1/",
|
||||
json.dumps(
|
||||
{
|
||||
"user_args": json.dumps(user_args),
|
||||
"barcode_tag_mapping": json.dumps(barcode_tag_mapping),
|
||||
},
|
||||
),
|
||||
content_type="application/json",
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
config = ApplicationConfiguration.objects.first()
|
||||
assert config is not None
|
||||
self.assertEqual(config.user_args, user_args)
|
||||
self.assertEqual(config.barcode_tag_mapping, barcode_tag_mapping)
|
||||
|
||||
def test_api_update_config_rejects_invalid_json_objects(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
- API request to update app config with a JSON-encoded value that
|
||||
is not an object for user_args or barcode_tag_mapping, or a
|
||||
barcode_tag_mapping with a non-string substitute
|
||||
WHEN:
|
||||
- API is called
|
||||
THEN:
|
||||
- Request is rejected with a 400 naming the problem
|
||||
- Config is not updated
|
||||
"""
|
||||
not_objects = (True, 1, [1, 2, 3], "not a dict")
|
||||
cases = [
|
||||
(field, value, b"must be a JSON object")
|
||||
for field in ("user_args", "barcode_tag_mapping")
|
||||
for value in not_objects
|
||||
]
|
||||
cases += [
|
||||
("barcode_tag_mapping", {"TAG:(.*)": 5}, b"values must be strings"),
|
||||
("barcode_tag_mapping", {"TAG:(.*)": None}, b"values must be strings"),
|
||||
]
|
||||
for field, value, expected_message in cases:
|
||||
with self.subTest(field=field, value=value):
|
||||
response = self.client.patch(
|
||||
f"{self.ENDPOINT}1/",
|
||||
json.dumps({field: json.dumps(value)}),
|
||||
content_type="application/json",
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn(expected_message, response.content)
|
||||
config = ApplicationConfiguration.objects.first()
|
||||
assert config is not None
|
||||
self.assertIsNone(getattr(config, field))
|
||||
|
||||
def test_api_replace_app_logo(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
|
||||
@@ -235,6 +235,24 @@ class ApplicationConfigurationSerializer(
|
||||
) -> list[str]:
|
||||
return sorted(name for name in os.environ if name.startswith("PAPERLESS_"))
|
||||
|
||||
@staticmethod
|
||||
def _require_json_object(field: str, value: object) -> None:
|
||||
if value is not None and not isinstance(value, dict):
|
||||
raise serializers.ValidationError(f"{field} must be a JSON object.")
|
||||
|
||||
def validate_user_args(self, value):
|
||||
self._require_json_object("user_args", value)
|
||||
return value
|
||||
|
||||
def validate_barcode_tag_mapping(self, value):
|
||||
self._require_json_object("barcode_tag_mapping", value)
|
||||
# Each value is the regex substitute applied to a matching barcode
|
||||
if value is not None and not all(isinstance(v, str) for v in value.values()):
|
||||
raise serializers.ValidationError(
|
||||
"barcode_tag_mapping values must be strings.",
|
||||
)
|
||||
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"] == "":
|
||||
|
||||
Reference in New Issue
Block a user