Skip to content

Commit 8a089a3

Browse files
authored
Builds: healthcheck permissions (#12423)
We want to skip the default permsissions check here (done at `get_queryset()` method) because it filters the object by build API key and/or user access. Since we are making an anonymous request without a build API key, we need to skip this check and base it only on the `?builder=` GET attribute.
1 parent 36ee040 commit 8a089a3

File tree

2 files changed

+46
-0
lines changed

2 files changed

+46
-0
lines changed

readthedocs/api/v2/views/model_views.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,9 @@ def concurrent(self, request, **kwargs):
304304
# We make this endpoint public because we don't want to expose the build API key inside the user's container.
305305
# To emulate "auth" we check for the builder hostname to match the `Build.builder` defined in the database.
306306
permission_classes=[],
307+
# We can't use the default `get_queryset()` method because it's filtered by build API key and/or user access.
308+
# Since we don't want to check for permissions here we need to use a custom queryset here.
309+
get_queryset=lambda: Build.objects.all(),
307310
methods=["post"],
308311
)
309312
def healthcheck(self, request, **kwargs):

readthedocs/rtd_tests/tests/test_api.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,49 @@ def setUp(self):
101101
self.project = get(Project, users=[self.user])
102102
self.version = self.project.versions.get(slug=LATEST)
103103

104+
def test_healthcheck(self):
105+
# Build cloning state
106+
build = get(
107+
Build,
108+
project=self.project,
109+
version=self.version,
110+
state=BUILD_STATE_CLONING,
111+
builder="build-a1b2c3",
112+
success=False,
113+
)
114+
self.assertIsNone(build.healthcheck)
115+
116+
client = APIClient()
117+
r = client.post(reverse("build-healthcheck", args=(build.pk,), query={"builder": "build-a1b2c3"}))
118+
build.refresh_from_db()
119+
120+
self.assertEqual(r.status_code, 204)
121+
self.assertIsNotNone(build.healthcheck)
122+
123+
# Build invalid builder
124+
build.healthcheck = None
125+
build.save()
126+
127+
client = APIClient()
128+
r = client.post(reverse("build-healthcheck", args=(build.pk,), query={"builder": "build-invalid"}))
129+
build.refresh_from_db()
130+
131+
self.assertEqual(r.status_code, 404)
132+
self.assertIsNone(build.healthcheck)
133+
134+
# Build finished state
135+
build.state = BUILD_STATE_FINISHED
136+
build.healthcheck = None
137+
build.save()
138+
139+
client = APIClient()
140+
r = client.post(reverse("build-healthcheck", args=(build.pk,), query={"builder": "build-a1b2c3"}))
141+
build.refresh_from_db()
142+
143+
self.assertEqual(r.status_code, 404)
144+
self.assertIsNone(build.healthcheck)
145+
146+
104147
def test_reset_build(self):
105148
build = get(
106149
Build,

0 commit comments

Comments
 (0)