diff --git a/InvenTree/InvenTree/fields.py b/InvenTree/InvenTree/fields.py index 6c6691ec1f..70ca133a80 100644 --- a/InvenTree/InvenTree/fields.py +++ b/InvenTree/InvenTree/fields.py @@ -11,6 +11,7 @@ from djmoney.forms.fields import MoneyField from djmoney.models.fields import MoneyField as ModelMoneyField from djmoney.models.validators import MinMoneyValidator from rest_framework.fields import URLField as RestURLField +from rest_framework.fields import empty import InvenTree.helpers @@ -29,6 +30,20 @@ class InvenTreeRestURLField(RestURLField): super().__init__(**kwargs) self.validators[-1].schemes = allowable_url_schemes() + def run_validation(self, data=empty): + """Override default validation behaviour for this field type""" + + import common.models + + strict_urls = common.models.InvenTreeSetting.get_setting('INVENTREE_STRICT_URLS', True, cache=False) + + if not strict_urls and data is not empty: + if '://' not in data: + # Validate as if there were a schema provided + data = 'http://' + data + + return super().run_validation(data=data) + class InvenTreeURLField(models.URLField): """Custom URL field which has custom scheme validators.""" diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py index b0cdab5006..5c7fe65c02 100644 --- a/InvenTree/InvenTree/tests.py +++ b/InvenTree/InvenTree/tests.py @@ -203,6 +203,37 @@ class ValidatorTest(TestCase): with self.assertRaises(django_exceptions.ValidationError): validate_overage("aaaa") + def test_url_validation(self): + """Test for AllowedURLValidator""" + + from common.models import InvenTreeSetting + from part.models import Part, PartCategory + + # Without strict URL validation + InvenTreeSetting.set_setting('INVENTREE_STRICT_URLS', False, None) + + n = Part.objects.count() + cat = PartCategory.objects.first() + + # Should pass, even without a schema + Part.objects.create( + name=f'Part {n}', + description='Link without schema', + category=cat, + link='www.google.com', + ) + + # With strict URL validation + InvenTreeSetting.set_setting('INVENTREE_STRICT_URLS', True, None) + + with self.assertRaises(ValidationError): + Part.objects.create( + name=f'Part {n + 1}', + description='Link without schema', + category=cat, + link='www.google.com', + ) + class FormatTest(TestCase): """Unit tests for custom string formatting functionality""" diff --git a/InvenTree/InvenTree/validators.py b/InvenTree/InvenTree/validators.py index 9a4b5f32e5..ea9111585d 100644 --- a/InvenTree/InvenTree/validators.py +++ b/InvenTree/InvenTree/validators.py @@ -60,9 +60,23 @@ def allowable_url_schemes(): class AllowedURLValidator(validators.URLValidator): """Custom URL validator to allow for custom schemes.""" + def __call__(self, value): """Validate the URL.""" + + import common.models + self.schemes = allowable_url_schemes() + + # Determine if 'strict' URL validation is required (i.e. if the URL must have a schema prefix) + strict_urls = common.models.InvenTreeSetting.get_setting('INVENTREE_STRICT_URLS', True, cache=False) + + if not strict_urls: + # Allow URLs which do not have a provided schema + if '://' not in value: + # Validate as if it were http + value = 'http://' + value + super().__call__(value) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 702fbe5887..e177f17d57 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -1175,6 +1175,13 @@ class InvenTreeSetting(BaseInvenTreeSetting): 'default': '', }, + 'INVENTREE_STRICT_URLS': { + 'name': _('Strict URL Validation'), + 'description': _('Require schema specification when validating URLs'), + 'validator': bool, + 'default': True, + }, + 'INVENTREE_REQUIRE_CONFIRM': { 'name': _('Require confirm'), 'description': _('Require explicit user confirmation for certain action.'), diff --git a/InvenTree/templates/InvenTree/settings/global.html b/InvenTree/templates/InvenTree/settings/global.html index 6554faf9e6..d532bc5727 100644 --- a/InvenTree/templates/InvenTree/settings/global.html +++ b/InvenTree/templates/InvenTree/settings/global.html @@ -24,6 +24,7 @@ {% include "InvenTree/settings/setting.html" with key="INVENTREE_DOWNLOAD_IMAGE_MAX_SIZE" icon="fa-server" %} {% include "InvenTree/settings/setting.html" with key="INVENTREE_DOWNLOAD_FROM_URL_USER_AGENT" icon="fa-server" %} {% include "InvenTree/settings/setting.html" with key="INVENTREE_REQUIRE_CONFIRM" icon="fa-check" %} + {% include "InvenTree/settings/setting.html" with key="INVENTREE_STRICT_URLS" icon="fa-link" %} {% include "InvenTree/settings/setting.html" with key="INVENTREE_TREE_DEPTH" icon="fa-sitemap" %} {% include "InvenTree/settings/setting.html" with key="INVENTREE_BACKUP_ENABLE" icon="fa-hdd" %} {% include "InvenTree/settings/setting.html" with key="INVENTREE_BACKUP_DAYS" icon="fa-calendar-alt" %}