From 1f1b1aedca949a52a592f8aefc523ad47b054ed9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Caune?= Date: Thu, 4 Jul 2019 09:36:27 +0300 Subject: [PATCH] Don't include ping URLs in API responses when the read-only key is used --- CHANGELOG.md | 1 + hc/api/decorators.py | 2 ++ hc/api/models.py | 26 +++++++++++++++++--------- hc/api/tests/test_list_channels.py | 7 ------- hc/api/tests/test_list_checks.py | 3 +++ hc/api/views.py | 4 ++-- 6 files changed, 25 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e706b1fa..5bc38e33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file. - Show check's code instead of full URL on 992px - 1200px wide screens. (#253) - Add WhatsApp integration (uses Twilio same as the SMS integration) - Webhooks support the $TAGS placeholder +- Don't include ping URLs in API responses when the read-only key is used ### Bug Fixes - Fix badges for tags containing special characters (#240, #237) diff --git a/hc/api/decorators.py b/hc/api/decorators.py index bf6c5718..b686f349 100644 --- a/hc/api/decorators.py +++ b/hc/api/decorators.py @@ -27,6 +27,7 @@ def authorize(f): except Project.DoesNotExist: return error("wrong api key", 401) + request.readonly = False return f(request, *args, **kwds) return wrapper @@ -50,6 +51,7 @@ def authorize_read(f): except Project.DoesNotExist: return error("wrong api key", 401) + request.readonly = api_key == request.project.api_key_readonly return f(request, *args, **kwds) return wrapper diff --git a/hc/api/models.py b/hc/api/models.py index 08daa68c..62b80f2d 100644 --- a/hc/api/models.py +++ b/hc/api/models.py @@ -167,26 +167,34 @@ class Check(models.Model): def matches_tag_set(self, tag_set): return tag_set.issubset(self.tags_list()) - def to_dict(self): - update_rel_url = reverse("hc-api-update", args=[self.code]) - pause_rel_url = reverse("hc-api-pause", args=[self.code]) - channel_codes = [str(ch.code) for ch in self.channel_set.all()] + def channels_str(self): + """ Return a comma-separated string of assigned channel codes. """ + + codes = self.channel_set.order_by("code").values_list("code", flat=True) + return ",".join(map(str, codes)) + + def to_dict(self, readonly=False): result = { "name": self.name, - "ping_url": self.url(), - "update_url": settings.SITE_ROOT + update_rel_url, - "pause_url": settings.SITE_ROOT + pause_rel_url, "tags": self.tags, "grace": int(self.grace.total_seconds()), "n_pings": self.n_pings, "status": self.get_status(), - "channels": ",".join(sorted(channel_codes)), "last_ping": isostring(self.last_ping), "next_ping": isostring(self.get_grace_start()), - "desc": self.desc, } + if not readonly: + update_rel_url = reverse("hc-api-update", args=[self.code]) + pause_rel_url = reverse("hc-api-pause", args=[self.code]) + + result["ping_url"] = self.url() + result["update_url"] = settings.SITE_ROOT + update_rel_url + result["pause_url"] = settings.SITE_ROOT + pause_rel_url + result["channels"] = self.channels_str() + result["desc"] = self.desc + if self.kind == "simple": result["timeout"] = int(self.timeout.total_seconds()) elif self.kind == "cron": diff --git a/hc/api/tests/test_list_channels.py b/hc/api/tests/test_list_channels.py index 0458aed6..17bfa65d 100644 --- a/hc/api/tests/test_list_channels.py +++ b/hc/api/tests/test_list_channels.py @@ -51,10 +51,3 @@ class ListChannelsTestCase(BaseTestCase): self.assertEqual(r.status_code, 200) self.assertContains(r, "Email to Alice") - - def test_readonly_key_works(self): - self.project.api_key_readonly = "R" * 32 - self.project.save() - - r = self.client.get("/api/v1/channels/", HTTP_X_API_KEY="R" * 32) - self.assertEqual(r.status_code, 200) diff --git a/hc/api/tests/test_list_checks.py b/hc/api/tests/test_list_checks.py index e6c69074..c887e267 100644 --- a/hc/api/tests/test_list_checks.py +++ b/hc/api/tests/test_list_checks.py @@ -150,3 +150,6 @@ class ListChecksTestCase(BaseTestCase): r = self.client.get("/api/v1/checks/", HTTP_X_API_KEY="R" * 32) self.assertEqual(r.status_code, 200) + + # When using readonly keys, the ping URLs should not be exposed: + self.assertNotContains(r, self.a1.url()) diff --git a/hc/api/views.py b/hc/api/views.py index 38a5a2e6..0520f1d9 100644 --- a/hc/api/views.py +++ b/hc/api/views.py @@ -121,7 +121,7 @@ def get_checks(request): for check in q: # precise, final filtering if not tags or check.matches_tag_set(tags): - checks.append(check.to_dict()) + checks.append(check.to_dict(readonly=request.readonly)) return JsonResponse({"checks": checks}) @@ -153,7 +153,7 @@ def checks(request): @cors("GET") @validate_json() -@authorize_read +@authorize def channels(request): q = Channel.objects.filter(project=request.project) channels = [ch.to_dict() for ch in q]