From 73c0bb960301c54cbb3fa8864f663e7cb51df0f6 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Jul 2026 23:50:44 +1000 Subject: [PATCH] Improve validation for import session (#12492) - Ensure that validated data is a dict shape --- src/backend/InvenTree/importer/serializers.py | 15 +++++-- src/backend/InvenTree/importer/tests.py | 42 ++++++++++++++++++- src/backend/InvenTree/importer/validators.py | 9 ++-- 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/src/backend/InvenTree/importer/serializers.py b/src/backend/InvenTree/importer/serializers.py index 46bc8562fc..c738853e08 100644 --- a/src/backend/InvenTree/importer/serializers.py +++ b/src/backend/InvenTree/importer/serializers.py @@ -98,7 +98,10 @@ class DataImportSessionSerializer(InvenTreeModelSerializer): if type(defaults) is not dict: try: defaults = json.loads(str(defaults)) - except: + except json.JSONDecodeError: + raise ValidationError(_('Invalid field defaults')) + + if type(defaults) is not dict: raise ValidationError(_('Invalid field defaults')) return defaults @@ -111,7 +114,10 @@ class DataImportSessionSerializer(InvenTreeModelSerializer): if type(overrides) is not dict: try: overrides = json.loads(str(overrides)) - except: + except json.JSONDecodeError: + raise ValidationError(_('Invalid field overrides')) + + if type(overrides) is not dict: raise ValidationError(_('Invalid field overrides')) return overrides @@ -124,7 +130,10 @@ class DataImportSessionSerializer(InvenTreeModelSerializer): if type(filters) is not dict: try: filters = json.loads(str(filters)) - except: + except json.JSONDecodeError: + raise ValidationError(_('Invalid field filters')) + + if type(filters) is not dict: raise ValidationError(_('Invalid field filters')) return filters diff --git a/src/backend/InvenTree/importer/tests.py b/src/backend/InvenTree/importer/tests.py index 1b91baaa8a..f0dfde284d 100644 --- a/src/backend/InvenTree/importer/tests.py +++ b/src/backend/InvenTree/importer/tests.py @@ -152,7 +152,47 @@ class ImporterTest(ImporterMixin, InvenTreeTestCase): self.assertEqual(n + 12, Company.objects.count()) def test_field_defaults(self): - """Test default field values.""" + """Test default field values. + + Regression test for bug #30 (dev/todo/bugs.md): validate_field_defaults + must reject any parsed JSON value that isn't actually a dict, not just + values which fail to parse as JSON at all. + """ + from django.core.exceptions import ValidationError as DjangoValidationError + + def data_file(): + # full_clean() reads the file eagerly, so each session needs its own + # freshly-opened (binary) copy + content = self.helper_file('companies.csv').read() + return ContentFile(content.encode(), 'companies.csv') + + # A dict value is accepted as-is + session = DataImportSession( + data_file=data_file(), model_type='company', field_defaults={'name': 'Test'} + ) + session.full_clean() + + # A JSON-encoded dict string is also accepted (de-stringified) + session = DataImportSession( + data_file=data_file(), + model_type='company', + field_defaults='{"name": "Test"}', + ) + session.full_clean() + + # A JSON-encoded list is valid JSON, but not a dict - must be rejected + session = DataImportSession( + data_file=data_file(), model_type='company', field_defaults='[1, 2, 3]' + ) + with self.assertRaises(DjangoValidationError): + session.full_clean() + + # Garbage which does not even parse as JSON must also be rejected + session = DataImportSession( + data_file=data_file(), model_type='company', field_defaults='not valid json' + ) + with self.assertRaises(DjangoValidationError): + session.full_clean() def test_lookup_field_ambiguous_match(self): """Test the behavior of lookup_related_field for ambiguous and pinned matches.""" diff --git a/src/backend/InvenTree/importer/validators.py b/src/backend/InvenTree/importer/validators.py index 4c72f43615..5f81ff6dd5 100644 --- a/src/backend/InvenTree/importer/validators.py +++ b/src/backend/InvenTree/importer/validators.py @@ -6,7 +6,7 @@ from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ # Define maximum limits for imported file data -IMPORTER_MAX_FILE_SIZE = 32 * 1024 * 1042 +IMPORTER_MAX_FILE_SIZE = 32 * 1024 * 1024 IMPORTER_MAX_ROWS = 5000 IMPORTER_MAX_COLS = 1000 @@ -46,8 +46,11 @@ def validate_field_defaults(value): return if type(value) is not dict: - # OK if we can parse it as JSON + # OK if we can parse it as JSON - but the result must be a dict try: value = json.loads(value) - except json.JSONDecodeError: + except (TypeError, json.JSONDecodeError): + raise ValidationError(_('Value must be a valid dictionary object')) + + if type(value) is not dict: raise ValidationError(_('Value must be a valid dictionary object'))