diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index b60546123a..5925488470 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -15,6 +15,7 @@ from pretix.base.email import CustomSMTPBackend from pretix.base.i18n import I18nCharField from pretix.base.models.base import LoggedModel from pretix.base.settings import SettingsProxy +from pretix.base.validators import EventSlugBlacklistValidator from .auth import User from .organizer import Organizer @@ -65,7 +66,8 @@ class Event(LoggedModel): RegexValidator( regex="^[a-zA-Z0-9.-]+$", message=_("The slug may only contain letters, numbers, dots and dashes."), - ) + ), + EventSlugBlacklistValidator() ], verbose_name=_("Slug"), ) diff --git a/src/pretix/base/models/organizer.py b/src/pretix/base/models/organizer.py index 93cce5e536..baf6fb915c 100644 --- a/src/pretix/base/models/organizer.py +++ b/src/pretix/base/models/organizer.py @@ -5,6 +5,7 @@ from django.utils.translation import ugettext_lazy as _ from pretix.base.models.base import LoggedModel from pretix.base.settings import SettingsProxy +from pretix.base.validators import OrganizerSlugBlacklistValidator from .auth import User from .settings import OrganizerSetting @@ -34,7 +35,8 @@ class Organizer(LoggedModel): RegexValidator( regex="^[a-zA-Z0-9.-]+$", message=_("The slug may only contain letters, numbers, dots and dashes.") - ) + ), + OrganizerSlugBlacklistValidator() ], verbose_name=_("Slug"), ) diff --git a/src/pretix/base/validators.py b/src/pretix/base/validators.py new file mode 100644 index 0000000000..4f5688cc78 --- /dev/null +++ b/src/pretix/base/validators.py @@ -0,0 +1,48 @@ +from django.core.exceptions import ValidationError +from django.utils.translation import ugettext_lazy as _ + + +class BlacklistValidator: + + blacklist = [] + + def __call__(self, value): + # Validation logic + if value in self.blacklist: + raise ValidationError( + _('This slug has an invalid value: %(value)s.'), + code='invalid', + params={'value': value}, + ) + + +class EventSlugBlacklistValidator(BlacklistValidator): + + blacklist = [ + 'download', + 'healthcheck', + 'locale', + 'control', + 'redirect', + 'jsi18n', + 'metrics', + '_global', + '__debug__' + ] + + +class OrganizerSlugBlacklistValidator(BlacklistValidator): + + blacklist = [ + 'download', + 'healthcheck', + 'locale', + 'control', + 'pretixdroid', + 'redirect', + 'jsi18n', + 'metrics', + '_global', + '__debug__', + 'about' + ] diff --git a/src/tests/base/test_models.py b/src/tests/base/test_models.py index 172398eec0..ad79d44f0a 100644 --- a/src/tests/base/test_models.py +++ b/src/tests/base/test_models.py @@ -1,3 +1,4 @@ +import datetime import sys from datetime import timedelta @@ -601,6 +602,16 @@ class EventTest(TestCase): self.assertIn('presale_end', str(context.exception)) + def test_slug_validation(self): + event = Event( + organizer=self.organizer, name='Download', slug='download', + date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc) + ) + with self.assertRaises(ValidationError) as context: + event.full_clean() + + self.assertIn('slug', str(context.exception)) + class CachedFileTestCase(TestCase): def test_file_handling(self): diff --git a/src/tests/presale/test_event.py b/src/tests/presale/test_event.py index 40b33098a5..d7d1e309ba 100644 --- a/src/tests/presale/test_event.py +++ b/src/tests/presale/test_event.py @@ -2,6 +2,7 @@ import datetime from decimal import Decimal from django.core import mail +from django.core.exceptions import ValidationError from django.test import TestCase from django.utils.timezone import now from tests.base import SoupTest @@ -469,3 +470,19 @@ class TestResendLink(EventTestMixin, SoupTest): self.assertEqual(len(mail.outbox), 1) self.assertIn('DUMMY1', mail.outbox[0].body) self.assertIn('DUMMY2', mail.outbox[0].body) + + +class EventSlugBlacklistValidatorTest(EventTestMixin, SoupTest): + def test_slug_validation(self): + event = Event( + organizer=self.orga, + name='download', + slug='download', + date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc), + live=True + ) + with self.assertRaises(ValidationError): + if event.full_clean(): + event.save() + + self.assertEqual(Event.objects.filter(name='download').count(), 0)