diff --git a/hc/front/forms.py b/hc/front/forms.py index daeca968..0783523b 100644 --- a/hc/front/forms.py +++ b/hc/front/forms.py @@ -1,5 +1,5 @@ from django import forms -from hc.front.validators import WebhookValidator +from hc.front.validators import CronExpressionValidator, WebhookValidator from hc.api.models import CHECK_KINDS @@ -21,7 +21,8 @@ class NameTagsForm(forms.Form): class TimeoutForm(forms.Form): kind = forms.ChoiceField(choices=CHECK_KINDS) timeout = forms.IntegerField(min_value=60, max_value=2592000) - schedule = forms.CharField(required=False, max_length=100) + schedule = forms.CharField(required=False, max_length=100, + validators=[CronExpressionValidator()]) tz = forms.CharField(required=False, max_length=36) grace = forms.IntegerField(min_value=60, max_value=2592000) diff --git a/hc/front/tests/test_update_timeout.py b/hc/front/tests/test_update_timeout.py index cea51751..c0e12b76 100644 --- a/hc/front/tests/test_update_timeout.py +++ b/hc/front/tests/test_update_timeout.py @@ -31,7 +31,7 @@ class UpdateTimeoutTestCase(BaseTestCase): url = "/checks/%s/timeout/" % self.check.code payload = { "kind": "cron", - "schedule": "* * * * *", + "schedule": "5 * * * *", "tz": "UTC", "timeout": 60, "grace": 60 @@ -43,7 +43,28 @@ class UpdateTimeoutTestCase(BaseTestCase): self.check.refresh_from_db() self.assertEqual(self.check.kind, "cron") - self.assertEqual(self.check.schedule, "* * * * *") + self.assertEqual(self.check.schedule, "5 * * * *") + + def test_it_validates_cron_expression(self): + self.check.last_ping = None + self.check.save() + + url = "/checks/%s/timeout/" % self.check.code + payload = { + "kind": "cron", + "schedule": "* invalid *", + "tz": "UTC", + "timeout": 60, + "grace": 60 + } + + self.client.login(username="alice@example.org", password="password") + r = self.client.post(url, data=payload) + self.assertRedirects(r, "/checks/") + + # Check should still have its original data: + self.check.refresh_from_db() + self.assertEqual(self.check.kind, "simple") def test_team_access_works(self): url = "/checks/%s/timeout/" % self.check.code diff --git a/hc/front/validators.py b/hc/front/validators.py index bb9481fa..7cdefb9e 100644 --- a/hc/front/validators.py +++ b/hc/front/validators.py @@ -1,3 +1,4 @@ +from croniter import croniter from django.core.exceptions import ValidationError from six.moves.urllib_parse import urlparse @@ -12,3 +13,13 @@ class WebhookValidator(object): if parsed.hostname in ("127.0.0.1", "localhost"): raise ValidationError(message=self.message) + + +class CronExpressionValidator(object): + message = "Not a valid cron expression." + + def __call__(self, value): + try: + croniter(value) + except: + raise ValidationError(message=self.message) diff --git a/templates/front/my_checks.html b/templates/front/my_checks.html index 61281e88..d24153c2 100644 --- a/templates/front/my_checks.html +++ b/templates/front/my_checks.html @@ -28,12 +28,10 @@ {% endfor %} {% endif %} - +