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