From 04fa1f2f06075f2ebdd9512134432e9e63d81dc8 Mon Sep 17 00:00:00 2001 From: ashnaaseth2325-oss Date: Mon, 23 Mar 2026 19:27:30 +0000 Subject: [PATCH 1/3] fix: catch ValueError for malformed channel_id in remove_self Signed-off-by: ashnaaseth2325-oss --- contentcuration/contentcuration/viewsets/user.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contentcuration/contentcuration/viewsets/user.py b/contentcuration/contentcuration/viewsets/user.py index 81855739aa..8827aa5a1e 100644 --- a/contentcuration/contentcuration/viewsets/user.py +++ b/contentcuration/contentcuration/viewsets/user.py @@ -346,6 +346,8 @@ def remove_self(self, request, pk=None): channel = Channel.objects.get(id=channel_id) except Channel.DoesNotExist: return HttpResponseNotFound("Channel not found {}".format(channel_id)) + except ValueError: + return HttpResponseBadRequest("Invalid channel ID: {}".format(channel_id)) if request.user != user and not request.user.can_edit(channel_id): return HttpResponseForbidden( From 6d791dfbbea74ab623790de04bfc92beea1f2dae Mon Sep 17 00:00:00 2001 From: ashnaaseth2325-oss Date: Tue, 24 Mar 2026 18:21:03 +0000 Subject: [PATCH 2/3] fix: validate channel_id as UUID before querying in remove_self --- contentcuration/contentcuration/viewsets/user.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/contentcuration/contentcuration/viewsets/user.py b/contentcuration/contentcuration/viewsets/user.py index 8827aa5a1e..1405be5b84 100644 --- a/contentcuration/contentcuration/viewsets/user.py +++ b/contentcuration/contentcuration/viewsets/user.py @@ -51,7 +51,7 @@ from contentcuration.viewsets.sync.constants import DELETED from contentcuration.viewsets.sync.constants import EDITOR_M2M from contentcuration.viewsets.sync.constants import VIEWER_M2M - +import uuid logger = logging.getLogger(__name__) @@ -341,6 +341,10 @@ def remove_self(self, request, pk=None): if not channel_id: return HttpResponseBadRequest("Channel ID is required.") + try: + uuid.UUID(channel_id) + except ValueError: + return HttpResponseBadRequest("Invalid channel ID") try: channel = Channel.objects.get(id=channel_id) From 1bfd670b8157155d6aba9ed9f0cb0d8bcc94e521 Mon Sep 17 00:00:00 2001 From: Blaine Jester Date: Wed, 10 Jun 2026 12:49:50 -0700 Subject: [PATCH 3/3] Cleanup validation and add test coverage --- .../tests/viewsets/test_user.py | 23 +++++++++++++++++++ .../contentcuration/viewsets/user.py | 7 +++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/contentcuration/contentcuration/tests/viewsets/test_user.py b/contentcuration/contentcuration/tests/viewsets/test_user.py index 1f801bd182..4e050888d2 100644 --- a/contentcuration/contentcuration/tests/viewsets/test_user.py +++ b/contentcuration/contentcuration/tests/viewsets/test_user.py @@ -6,6 +6,7 @@ from contentcuration.models import Change from contentcuration.tests import testdata from contentcuration.tests.base import StudioAPITestCase +from contentcuration.tests.helpers import reverse_with_query from contentcuration.tests.viewsets.base import generate_create_event from contentcuration.tests.viewsets.base import generate_delete_event from contentcuration.tests.viewsets.base import SyncTestMixin @@ -367,6 +368,28 @@ def test_fetch_users_no_permissions(self): self.assertEqual(response.status_code, 200, response.content) self.assertEqual(response.json(), []) + def test_remove_self_with_invalid_channel_id_returns_bad_request(self): + self.client.force_authenticate(user=self.user) + response = self.client.delete( + reverse_with_query( + "channeluser-remove-self", + kwargs={"pk": self.user.id}, + query={"channel_id": "not-a-valid-uuid"}, + ) + ) + self.assertEqual(response.status_code, 400, response.content) + + def test_remove_self_with_missing_channel_returns_not_found(self): + self.client.force_authenticate(user=self.user) + response = self.client.delete( + reverse_with_query( + "channeluser-remove-self", + kwargs={"pk": self.user.id}, + query={"channel_id": "00000000-0000-0000-0000-000000000000"}, + ) + ) + self.assertEqual(response.status_code, 404, response.content) + class MarkReadNotificationsTimestampTestCase(StudioAPITestCase): def setUp(self): diff --git a/contentcuration/contentcuration/viewsets/user.py b/contentcuration/contentcuration/viewsets/user.py index 1405be5b84..126a342319 100644 --- a/contentcuration/contentcuration/viewsets/user.py +++ b/contentcuration/contentcuration/viewsets/user.py @@ -1,5 +1,6 @@ import csv import logging +import uuid from datetime import date from functools import reduce @@ -51,7 +52,7 @@ from contentcuration.viewsets.sync.constants import DELETED from contentcuration.viewsets.sync.constants import EDITOR_M2M from contentcuration.viewsets.sync.constants import VIEWER_M2M -import uuid + logger = logging.getLogger(__name__) @@ -342,7 +343,7 @@ def remove_self(self, request, pk=None): if not channel_id: return HttpResponseBadRequest("Channel ID is required.") try: - uuid.UUID(channel_id) + channel_id = uuid.UUID(channel_id).hex except ValueError: return HttpResponseBadRequest("Invalid channel ID") @@ -350,8 +351,6 @@ def remove_self(self, request, pk=None): channel = Channel.objects.get(id=channel_id) except Channel.DoesNotExist: return HttpResponseNotFound("Channel not found {}".format(channel_id)) - except ValueError: - return HttpResponseBadRequest("Invalid channel ID: {}".format(channel_id)) if request.user != user and not request.user.can_edit(channel_id): return HttpResponseForbidden(