From 8725c811443f886f4c557f59afa6e258549f3421 Mon Sep 17 00:00:00 2001
From: James Kirsop
Date: Thu, 11 Jun 2020 17:00:27 +1000
Subject: [PATCH 1/2] Implementing new changes discussed to resolve #370
---
CHANGELOG.md | 1 +
hc/api/tests/test_get_check.py | 17 +++++++++++++++++
hc/api/urls.py | 10 ++++++++++
hc/api/views.py | 13 ++++++++++++-
templates/docs/api.md | 7 ++++---
5 files changed, 44 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9fff38e6..46974611 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
### Improvements
- Paused ping handling can be controlled via API (#376)
- Add "Get a list of checks's logged pings" API call (#371)
+- Allowed the /api/v1/checks/ endpoint to receive a UUID or `unique_key` (#370)
### Bug Fixes
diff --git a/hc/api/tests/test_get_check.py b/hc/api/tests/test_get_check.py
index 1501c620..268f99eb 100644
--- a/hc/api/tests/test_get_check.py
+++ b/hc/api/tests/test_get_check.py
@@ -54,6 +54,23 @@ class GetCheckTestCase(BaseTestCase):
r = self.get(made_up_code)
self.assertEqual(r.status_code, 404)
+ def test_it_handles_unique_key(self):
+ r = self.get(self.a1.unique_key)
+ self.assertEqual(r.status_code, 200)
+ self.assertEqual(r["Access-Control-Allow-Origin"], "*")
+
+ doc = r.json()
+ self.assertEqual(len(doc), 14)
+
+ self.assertEqual(doc["timeout"], 3600)
+ self.assertEqual(doc["grace"], 900)
+ self.assertEqual(doc["ping_url"], self.a1.url())
+ self.assertEqual(doc["last_ping"], None)
+ self.assertEqual(doc["n_pings"], 0)
+ self.assertEqual(doc["status"], "new")
+ self.assertEqual(doc["channels"], str(self.c1.code))
+ self.assertEqual(doc["desc"], "This is description")
+
def test_readonly_key_works(self):
self.project.api_key_readonly = "R" * 32
self.project.save()
diff --git a/hc/api/urls.py b/hc/api/urls.py
index 1316602a..ce0541f8 100644
--- a/hc/api/urls.py
+++ b/hc/api/urls.py
@@ -13,8 +13,17 @@ class QuoteConverter:
def to_url(self, value):
return quote(value, safe="")
+class UniqueKeyConverter:
+ regex = "[A-z0-9]{40}"
+
+ def to_python(self, value):
+ return value
+
+ def to_url(self, value):
+ return value
register_converter(QuoteConverter, "quoted")
+register_converter(UniqueKeyConverter, "unique_key")
urlpatterns = [
path("ping//", views.ping, name="hc-ping-slash"),
@@ -23,6 +32,7 @@ urlpatterns = [
path("ping//start", views.ping, {"action": "start"}, name="hc-start"),
path("api/v1/checks/", views.checks),
path("api/v1/checks/", views.single, name="hc-api-single"),
+ path("api/v1/checks/", views.single, name="hc-api-single"),
path("api/v1/checks//pause", views.pause, name="hc-api-pause"),
path("api/v1/notifications//bounce", views.bounce, name="hc-api-bounce"),
path("api/v1/checks//pings/", views.pings, name="hc-api-pings"),
diff --git a/hc/api/views.py b/hc/api/views.py
index f174f989..71bbdcee 100644
--- a/hc/api/views.py
+++ b/hc/api/views.py
@@ -191,6 +191,14 @@ def get_check(request, code):
return JsonResponse(check.to_dict(readonly=request.readonly))
+@validate_json()
+@authorize_read
+def get_check_unique(request, code):
+ checks = Check.objects.filter(project=request.project.id)
+ for check in checks:
+ if check.unique_key == code:
+ return JsonResponse(check.to_dict(readonly=request.readonly))
+ return HttpResponseNotFound()
@validate_json(schemas.check)
@authorize
@@ -228,7 +236,10 @@ def single(request, code):
if request.method == "DELETE":
return delete_check(request, code)
- return get_check(request, code)
+ if type(code) == uuid.UUID:
+ return get_check(request, code)
+ else:
+ return get_check_unique(request, code)
@cors("POST")
diff --git a/templates/docs/api.md b/templates/docs/api.md
index 7f2c1222..caf74b6f 100644
--- a/templates/docs/api.md
+++ b/templates/docs/api.md
@@ -9,6 +9,7 @@ Endpoint Name | Endpoint Address
------------------------------------------------------|-------
[Get a list of existing checks](#list-checks) | `GET SITE_ROOT/api/v1/checks/`
[Get a single check](#get-check) | `GET SITE_ROOT/api/v1/checks/`
+[Get a single check (using Read Only API)](#get-check) | `GET SITE_ROOT/api/v1/checks/`
[Create a new check](#create-check) | `POST SITE_ROOT/api/v1/checks/`
[Update an existing check](#update-check) | `POST SITE_ROOT/api/v1/checks/`
[Pause monitoring of a check](#pause-check) | `POST SITE_ROOT/api/v1/checks//pause`
@@ -128,7 +129,7 @@ curl --header "X-Api-Key: your-api-key" SITE_ROOT/api/v1/checks/
When using the read-only API key, the following fields are omitted:
`ping_url`, `update_url`, `pause_url`, `channels`. An extra `unique_key` field
-is added. This identifier is stable across API calls. Example:
+is added which can be used [to `GET` a check](#get-check) in place of the `UUID`. The `unique_key` identifier is stable across API calls. Example:
```json
{
@@ -165,9 +166,9 @@ is added. This identifier is stable across API calls. Example:
```
## Get a Single Check {: #get-check .rule }
-`GET SITE_ROOT/api/v1/checks/`
+`GET SITE_ROOT/api/v1/checks/` OR `GET SITE_ROOT/api/v1/checks/`
-Returns a JSON representation of a single check.
+Returns a JSON representation of a single check. Can take either the UUID or the `unique_key` (see [information above](#list-checks)) as the identifier of the check to return.
### Response Codes
From cdafc06c65e33122ea2cfa64ef2a2292647369b6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C4=93teris=20Caune?=
Date: Thu, 11 Jun 2020 15:24:45 +0300
Subject: [PATCH 2/2] In urls.py, route "api/v1/checks/"
directly to the hc.api.views.get_check_by_unique_key view. Minor API
documentation edits.
---
CHANGELOG.md | 2 +-
hc/api/urls.py | 10 ++++++----
hc/api/views.py | 13 +++++++------
templates/docs/api.html | 13 ++++++++++---
templates/docs/api.md | 9 ++++++---
5 files changed, 30 insertions(+), 17 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 46974611..5932928d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,7 @@ All notable changes to this project will be documented in this file.
### Improvements
- Paused ping handling can be controlled via API (#376)
- Add "Get a list of checks's logged pings" API call (#371)
-- Allowed the /api/v1/checks/ endpoint to receive a UUID or `unique_key` (#370)
+- The /api/v1/checks/ endpoint now accepts either UUID or `unique_key` (#370)
### Bug Fixes
diff --git a/hc/api/urls.py b/hc/api/urls.py
index ce0541f8..422ee154 100644
--- a/hc/api/urls.py
+++ b/hc/api/urls.py
@@ -5,7 +5,7 @@ from hc.api import views
class QuoteConverter:
- regex = "[\w%~_.-]+"
+ regex = r"[\w%~_.-]+"
def to_python(self, value):
return unquote(value)
@@ -13,7 +13,8 @@ class QuoteConverter:
def to_url(self, value):
return quote(value, safe="")
-class UniqueKeyConverter:
+
+class SHA1Converter:
regex = "[A-z0-9]{40}"
def to_python(self, value):
@@ -22,8 +23,9 @@ class UniqueKeyConverter:
def to_url(self, value):
return value
+
register_converter(QuoteConverter, "quoted")
-register_converter(UniqueKeyConverter, "unique_key")
+register_converter(SHA1Converter, "sha1")
urlpatterns = [
path("ping//", views.ping, name="hc-ping-slash"),
@@ -32,7 +34,7 @@ urlpatterns = [
path("ping//start", views.ping, {"action": "start"}, name="hc-start"),
path("api/v1/checks/", views.checks),
path("api/v1/checks/", views.single, name="hc-api-single"),
- path("api/v1/checks/", views.single, name="hc-api-single"),
+ path("api/v1/checks/", views.get_check_by_unique_key),
path("api/v1/checks//pause", views.pause, name="hc-api-pause"),
path("api/v1/notifications//bounce", views.bounce, name="hc-api-bounce"),
path("api/v1/checks//pings/", views.pings, name="hc-api-pings"),
diff --git a/hc/api/views.py b/hc/api/views.py
index 71bbdcee..9c876fc2 100644
--- a/hc/api/views.py
+++ b/hc/api/views.py
@@ -191,15 +191,19 @@ def get_check(request, code):
return JsonResponse(check.to_dict(readonly=request.readonly))
+
+@cors("GET")
+@csrf_exempt
@validate_json()
@authorize_read
-def get_check_unique(request, code):
+def get_check_by_unique_key(request, unique_key):
checks = Check.objects.filter(project=request.project.id)
for check in checks:
- if check.unique_key == code:
+ if check.unique_key == unique_key:
return JsonResponse(check.to_dict(readonly=request.readonly))
return HttpResponseNotFound()
+
@validate_json(schemas.check)
@authorize
def update_check(request, code):
@@ -236,10 +240,7 @@ def single(request, code):
if request.method == "DELETE":
return delete_check(request, code)
- if type(code) == uuid.UUID:
- return get_check(request, code)
- else:
- return get_check_unique(request, code)
+ return get_check(request, code)
@cors("POST")
diff --git a/templates/docs/api.html b/templates/docs/api.html
index c613369e..0d0875f6 100644
--- a/templates/docs/api.html
+++ b/templates/docs/api.html
@@ -19,6 +19,10 @@ checks in user's account.
GET SITE_ROOT/api/v1/checks/<uuid> |
+Get a check by its unique_key |
+GET SITE_ROOT/api/v1/checks/<unique_key> |
+
+
Create a new check |
POST SITE_ROOT/api/v1/checks/ |
@@ -152,7 +156,7 @@ specified value.
When using the read-only API key, the following fields are omitted:
ping_url
, update_url
, pause_url
, channels
. An extra unique_key
field
-is added. This identifier is stable across API calls. Example:
+is added which can be used to GET
a check in place of the UUID
. The unique_key
identifier is stable across API calls. Example:
{
"checks": [
{
@@ -188,8 +192,11 @@ is added. This identifier is stable across API calls. Example:
Get a Single Check
-GET SITE_ROOT/api/v1/checks/<uuid>
-Returns a JSON representation of a single check.
+GET SITE_ROOT/api/v1/checks/<uuid>
+GET SITE_ROOT/api/v1/checks/<unique_key>
+Returns a JSON representation of a single check. Accepts either check's UUID or
+the unique_key
(a field derived from UUID, and returned by API responses when
+using the read-only API key) as an identifier.
Response Codes
- 200 OK
diff --git a/templates/docs/api.md b/templates/docs/api.md
index caf74b6f..678bc892 100644
--- a/templates/docs/api.md
+++ b/templates/docs/api.md
@@ -9,7 +9,7 @@ Endpoint Name | Endpoint Address
------------------------------------------------------|-------
[Get a list of existing checks](#list-checks) | `GET SITE_ROOT/api/v1/checks/`
[Get a single check](#get-check) | `GET SITE_ROOT/api/v1/checks/`
-[Get a single check (using Read Only API)](#get-check) | `GET SITE_ROOT/api/v1/checks/`
+[Get a check by its unique_key](#get-check) | `GET SITE_ROOT/api/v1/checks/`
[Create a new check](#create-check) | `POST SITE_ROOT/api/v1/checks/`
[Update an existing check](#update-check) | `POST SITE_ROOT/api/v1/checks/`
[Pause monitoring of a check](#pause-check) | `POST SITE_ROOT/api/v1/checks//pause`
@@ -166,9 +166,12 @@ is added which can be used [to `GET` a check](#get-check) in place of the `UUID`
```
## Get a Single Check {: #get-check .rule }
-`GET SITE_ROOT/api/v1/checks/` OR `GET SITE_ROOT/api/v1/checks/`
+`GET SITE_ROOT/api/v1/checks/`
+`GET SITE_ROOT/api/v1/checks/`
-Returns a JSON representation of a single check. Can take either the UUID or the `unique_key` (see [information above](#list-checks)) as the identifier of the check to return.
+Returns a JSON representation of a single check. Accepts either check's UUID or
+the `unique_key` (a field derived from UUID, and returned by API responses when
+using the read-only API key) as an identifier.
### Response Codes