Browse Source

Don't include ping URLs in API responses when the read-only key is used

pull/272/head
Pēteris Caune 5 years ago
parent
commit
1f1b1aedca
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
6 changed files with 25 additions and 18 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +2
    -0
      hc/api/decorators.py
  3. +17
    -9
      hc/api/models.py
  4. +0
    -7
      hc/api/tests/test_list_channels.py
  5. +3
    -0
      hc/api/tests/test_list_checks.py
  6. +2
    -2
      hc/api/views.py

+ 1
- 0
CHANGELOG.md View File

@ -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) - Show check's code instead of full URL on 992px - 1200px wide screens. (#253)
- Add WhatsApp integration (uses Twilio same as the SMS integration) - Add WhatsApp integration (uses Twilio same as the SMS integration)
- Webhooks support the $TAGS placeholder - Webhooks support the $TAGS placeholder
- Don't include ping URLs in API responses when the read-only key is used
### Bug Fixes ### Bug Fixes
- Fix badges for tags containing special characters (#240, #237) - Fix badges for tags containing special characters (#240, #237)


+ 2
- 0
hc/api/decorators.py View File

@ -27,6 +27,7 @@ def authorize(f):
except Project.DoesNotExist: except Project.DoesNotExist:
return error("wrong api key", 401) return error("wrong api key", 401)
request.readonly = False
return f(request, *args, **kwds) return f(request, *args, **kwds)
return wrapper return wrapper
@ -50,6 +51,7 @@ def authorize_read(f):
except Project.DoesNotExist: except Project.DoesNotExist:
return error("wrong api key", 401) return error("wrong api key", 401)
request.readonly = api_key == request.project.api_key_readonly
return f(request, *args, **kwds) return f(request, *args, **kwds)
return wrapper return wrapper


+ 17
- 9
hc/api/models.py View File

@ -167,26 +167,34 @@ class Check(models.Model):
def matches_tag_set(self, tag_set): def matches_tag_set(self, tag_set):
return tag_set.issubset(self.tags_list()) 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 = { result = {
"name": self.name, "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, "tags": self.tags,
"grace": int(self.grace.total_seconds()), "grace": int(self.grace.total_seconds()),
"n_pings": self.n_pings, "n_pings": self.n_pings,
"status": self.get_status(), "status": self.get_status(),
"channels": ",".join(sorted(channel_codes)),
"last_ping": isostring(self.last_ping), "last_ping": isostring(self.last_ping),
"next_ping": isostring(self.get_grace_start()), "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": if self.kind == "simple":
result["timeout"] = int(self.timeout.total_seconds()) result["timeout"] = int(self.timeout.total_seconds())
elif self.kind == "cron": elif self.kind == "cron":


+ 0
- 7
hc/api/tests/test_list_channels.py View File

@ -51,10 +51,3 @@ class ListChannelsTestCase(BaseTestCase):
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
self.assertContains(r, "Email to Alice") 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)

+ 3
- 0
hc/api/tests/test_list_checks.py View File

@ -150,3 +150,6 @@ class ListChecksTestCase(BaseTestCase):
r = self.client.get("/api/v1/checks/", HTTP_X_API_KEY="R" * 32) r = self.client.get("/api/v1/checks/", HTTP_X_API_KEY="R" * 32)
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
# When using readonly keys, the ping URLs should not be exposed:
self.assertNotContains(r, self.a1.url())

+ 2
- 2
hc/api/views.py View File

@ -121,7 +121,7 @@ def get_checks(request):
for check in q: for check in q:
# precise, final filtering # precise, final filtering
if not tags or check.matches_tag_set(tags): 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}) return JsonResponse({"checks": checks})
@ -153,7 +153,7 @@ def checks(request):
@cors("GET") @cors("GET")
@validate_json() @validate_json()
@authorize_read
@authorize
def channels(request): def channels(request):
q = Channel.objects.filter(project=request.project) q = Channel.objects.filter(project=request.project)
channels = [ch.to_dict() for ch in q] channels = [ch.to_dict() for ch in q]


Loading…
Cancel
Save