Browse Source

Validate channel identifiers before creating/updating a check. Fixes #335

pull/340/head
Pēteris Caune 5 years ago
parent
commit
5e051d53f8
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
4 changed files with 45 additions and 21 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +8
    -0
      hc/api/tests/test_create_check.py
  3. +15
    -1
      hc/api/tests/test_update_check.py
  4. +21
    -20
      hc/api/views.py

+ 1
- 0
CHANGELOG.md View File

@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file.
- API: don't let SuspiciousOperation bubble up when validating channel ids - API: don't let SuspiciousOperation bubble up when validating channel ids
- API security: check channel ownership when setting check's channels - API security: check channel ownership when setting check's channels
- API: update check's "alert_after" field when changing schedule - API: update check's "alert_after" field when changing schedule
- API: validate channel identifiers before creating/updating a check (#335)
## v1.13.0 - 2020-02-13 ## v1.13.0 - 2020-02-13


+ 8
- 0
hc/api/tests/test_create_check.py View File

@ -84,6 +84,14 @@ class CreateCheckTestCase(BaseTestCase):
check = Check.objects.get() check = Check.objects.get()
self.assertEqual(check.channel_set.get(), channel) self.assertEqual(check.channel_set.get(), channel)
def test_it_rejects_bad_channel_code(self):
r = self.post({"api_key": "X" * 32, "channels": "abc"})
self.assertEqual(r.status_code, 400)
self.assertEqual(r.json()["error"], "invalid channel identifier: abc")
# The check should not have been saved
self.assertFalse(Check.objects.exists())
def test_it_supports_unique(self): def test_it_supports_unique(self):
Check.objects.create(project=self.project, name="Foo") Check.objects.create(project=self.project, name="Foo")


+ 15
- 1
hc/api/tests/test_update_check.py View File

@ -119,6 +119,15 @@ class UpdateCheckTestCase(BaseTestCase):
self.assertEqual(self.check.channel_set.count(), 1) self.assertEqual(self.check.channel_set.count(), 1)
self.assertEqual(self.check.channel_set.first().code, channel.code) self.assertEqual(self.check.channel_set.first().code, channel.code)
def test_it_sets_the_channel_only_once(self):
channel = Channel.objects.create(project=self.project)
duplicates = "%s,%s" % (channel.code, channel.code)
r = self.post(self.check.code, {"api_key": "X" * 32, "channels": duplicates})
self.assertEqual(r.status_code, 200)
self.check.refresh_from_db()
self.assertEqual(self.check.channel_set.count(), 1)
def test_it_handles_comma_separated_channel_codes(self): def test_it_handles_comma_separated_channel_codes(self):
c1 = Channel.objects.create(project=self.project) c1 = Channel.objects.create(project=self.project)
c2 = Channel.objects.create(project=self.project) c2 = Channel.objects.create(project=self.project)
@ -152,10 +161,15 @@ class UpdateCheckTestCase(BaseTestCase):
self.assertEqual(check.channel_set.count(), 1) self.assertEqual(check.channel_set.count(), 1)
def test_it_rejects_bad_channel_code(self): def test_it_rejects_bad_channel_code(self):
r = self.post(self.check.code, {"api_key": "X" * 32, "channels": "abc"})
payload = {"api_key": "X" * 32, "channels": "abc", "name": "New Name"}
r = self.post(self.check.code, payload,)
self.assertEqual(r.status_code, 400) self.assertEqual(r.status_code, 400)
self.assertEqual(r.json()["error"], "invalid channel identifier: abc") self.assertEqual(r.json()["error"], "invalid channel identifier: abc")
# The name should be unchanged
self.check.refresh_from_db()
self.assertEqual(self.check.name, "")
def test_it_rejects_missing_channel(self): def test_it_rejects_missing_channel(self):
code = str(uuid.uuid4()) code = str(uuid.uuid4())
r = self.post(self.check.code, {"api_key": "X" * 32, "channels": code}) r = self.post(self.check.code, {"api_key": "X" * 32, "channels": code})


+ 21
- 20
hc/api/views.py View File

@ -67,6 +67,21 @@ def _lookup(project, spec):
def _update(check, spec): def _update(check, spec):
channels = set()
# First, validate the supplied channel codes
if "channels" in spec and spec["channels"] not in ("*", ""):
q = Channel.objects.filter(project=check.project)
for s in spec["channels"].split(","):
try:
code = uuid.UUID(s)
except ValueError:
raise BadChannelException("invalid channel identifier: %s" % s)
try:
channels.add(q.get(code=code))
except Channel.DoesNotExist:
raise BadChannelException("invalid channel identifier: %s" % s)
if "name" in spec: if "name" in spec:
check.name = spec["name"] check.name = spec["name"]
@ -94,26 +109,12 @@ def _update(check, spec):
# This needs to be done after saving the check, because of # This needs to be done after saving the check, because of
# the M2M relation between checks and channels: # the M2M relation between checks and channels:
if "channels" in spec:
if spec["channels"] == "*":
check.assign_all_channels()
elif spec["channels"] == "":
check.channel_set.clear()
else:
channels = []
channel_query = Channel.objects.filter(project=check.project)
for chunk in spec["channels"].split(","):
try:
chunk = uuid.UUID(chunk)
except ValueError:
raise BadChannelException("invalid channel identifier: %s" % chunk)
try:
channels.append(channel_query.get(code=chunk))
except Channel.DoesNotExist:
raise BadChannelException("invalid channel identifier: %s" % chunk)
check.channel_set.set(channels)
if spec.get("channels") == "*":
check.assign_all_channels()
elif spec.get("channels") == "":
check.channel_set.clear()
elif channels:
check.channel_set.set(channels)
return check return check


Loading…
Cancel
Save