Browse Source

Stricter cron validation, reject schedules like "At midnight of February 31"

pull/328/head
Pēteris Caune 5 years ago
parent
commit
ccd30ac239
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
7 changed files with 28 additions and 9 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +13
    -3
      hc/api/tests/test_update_check.py
  3. +1
    -1
      hc/front/tests/test_update_timeout.py
  4. +4
    -1
      hc/front/validators.py
  5. +4
    -1
      hc/lib/jsonschema.py
  6. +4
    -2
      hc/lib/tests/test_jsonschema.py
  7. +1
    -1
      requirements.txt

+ 1
- 0
CHANGELOG.md View File

@ -20,6 +20,7 @@ All notable changes to this project will be documented in this file.
- Make sure Check.last_ping and Ping.created timestamps match exactly - Make sure Check.last_ping and Ping.created timestamps match exactly
- Don't trigger "down" notifications when changing schedule interactively in web UI - Don't trigger "down" notifications when changing schedule interactively in web UI
- Fix sendalerts crash loop when encountering a bad cron schedule - Fix sendalerts crash loop when encountering a bad cron schedule
- Stricter cron validation, reject schedules like "At midnight of February 31"
## v1.12.0 - 2020-01-02 ## v1.12.0 - 2020-01-02


+ 13
- 3
hc/api/tests/test_update_check.py View File

@ -166,10 +166,20 @@ class UpdateCheckTestCase(BaseTestCase):
self.assertEqual(r.status_code, 400) self.assertEqual(r.status_code, 400)
def test_it_rejects_non_string_desc(self): def test_it_rejects_non_string_desc(self):
r = self.post(
self.check.code, {"api_key": "X" * 32, "desc": 123}
)
r = self.post(self.check.code, {"api_key": "X" * 32, "desc": 123})
self.assertEqual(r.status_code, 400) self.assertEqual(r.status_code, 400)
def test_it_validates_cron_expression(self):
self.check.kind = "cron"
self.check.schedule = "5 * * * *"
self.check.save()
samples = ["* invalid *", "1,2 3,* * * *", "0 0 31 2 *"]
for sample in samples:
r = self.post(self.check.code, {"api_key": "X" * 32, "schedule": sample})
self.assertEqual(r.status_code, 400, "Did not reject '%s'" % sample)
# Schedule should be unchanged
self.check.refresh_from_db() self.check.refresh_from_db()
self.assertEqual(self.check.schedule, "5 * * * *")

+ 1
- 1
hc/front/tests/test_update_timeout.py View File

@ -74,7 +74,7 @@ class UpdateTimeoutTestCase(BaseTestCase):
def test_it_validates_cron_expression(self): def test_it_validates_cron_expression(self):
self.client.login(username="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
samples = ["* invalid *", "1,2 3,* * * *"]
samples = ["* invalid *", "1,2 3,* * * *", "0 0 31 2 *"]
for sample in samples: for sample in samples:
payload = {"kind": "cron", "schedule": sample, "tz": "UTC", "grace": 60} payload = {"kind": "cron", "schedule": sample, "tz": "UTC", "grace": 60}


+ 4
- 1
hc/front/validators.py View File

@ -25,7 +25,10 @@ class CronExpressionValidator(object):
raise ValidationError(message=self.message) raise ValidationError(message=self.message)
try: try:
croniter(value)
# Does croniter accept the schedule?
it = croniter(value)
# Can it calculate the next datetime?
it.next()
except: except:
raise ValidationError(message=self.message) raise ValidationError(message=self.message)


+ 4
- 1
hc/lib/jsonschema.py View File

@ -22,7 +22,10 @@ def validate(obj, schema, obj_name="value"):
raise ValidationError("%s is too long" % obj_name) raise ValidationError("%s is too long" % obj_name)
if schema.get("format") == "cron": if schema.get("format") == "cron":
try: try:
croniter(obj)
# Does croniter accept the schedule?
it = croniter(obj)
# Can it calculate the next datetime?
it.next()
except: except:
raise ValidationError("%s is not a valid cron expression" % obj_name) raise ValidationError("%s is not a valid cron expression" % obj_name)
if schema.get("format") == "timezone" and obj not in all_timezones: if schema.get("format") == "timezone" and obj not in all_timezones:


+ 4
- 2
hc/lib/tests/test_jsonschema.py View File

@ -74,8 +74,10 @@ class JsonSchemaTestCase(TestCase):
validate("baz", {"enum": ["foo", "bar"]}) validate("baz", {"enum": ["foo", "bar"]})
def test_it_checks_cron_format(self): def test_it_checks_cron_format(self):
with self.assertRaises(ValidationError):
validate("x * * * *", {"type": "string", "format": "cron"})
samples = ["x * * * *", "0 0 31 2 *"]
for sample in samples:
with self.assertRaises(ValidationError):
validate(sample, {"type": "string", "format": "cron"})
def test_it_checks_timezone_format(self): def test_it_checks_timezone_format(self):
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):


+ 1
- 1
requirements.txt View File

@ -4,4 +4,4 @@ django-compressor==2.4
psycopg2==2.8.4 psycopg2==2.8.4
pytz==2019.3 pytz==2019.3
requests==2.22.0 requests==2.22.0
statsd==3.3.0
statsd==3.3.0

Loading…
Cancel
Save