From 559e7668d14a94eacbba59d37689fab71bc41a1c Mon Sep 17 00:00:00 2001 From: Ayaan Ahmad Date: Thu, 12 Mar 2026 22:18:45 +0530 Subject: [PATCH 1/2] [fix] Use UUID converters for users API detail routes #487 User and organization API detail routes currently use string path converters even though the underlying resources are UUID-backed. This allows malformed IDs to resolve to DRF views and return 401 instead of being rejected at the routing boundary with 404. Update the five affected routes to use uuid converters and add a regression test covering malformed UUID paths. Closes #487 --- openwisp_users/api/urls.py | 10 +++++----- openwisp_users/tests/test_api/test_views.py | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/openwisp_users/api/urls.py b/openwisp_users/api/urls.py index a17e50be..3b681def 100644 --- a/openwisp_users/api/urls.py +++ b/openwisp_users/api/urls.py @@ -16,7 +16,7 @@ def get_api_urls(api_views=None): name="organization_list", ), path( - "users/organization//", + "users/organization//", views.organization_detail, name="organization_detail", ), @@ -25,19 +25,19 @@ def get_api_urls(api_views=None): views.user_list, name="user_list", ), - path("users/user//", views.user_detail, name="user_detail"), + path("users/user//", views.user_detail, name="user_detail"), path( - "users/user//password/", + "users/user//password/", views.change_password, name="change_password", ), path( - "users/user//email/", + "users/user//email/", views.email_list, name="email_list", ), path( - "users/user//email//", + "users/user//email//", views.email_update, name="email_update", ), diff --git a/openwisp_users/tests/test_api/test_views.py b/openwisp_users/tests/test_api/test_views.py index 067aefb7..bc81795b 100644 --- a/openwisp_users/tests/test_api/test_views.py +++ b/openwisp_users/tests/test_api/test_views.py @@ -24,3 +24,17 @@ def test_protected_api_mixin_view(self): self.assertEqual(response.headers["WWW-Authenticate"], "Bearer") self.assertEqual(response.data["detail"], auth_error) self.assertEqual(response.status_code, 401) + + def test_invalid_uuid_routes_return_404(self): + invalid_uuid_paths = ( + "/api/v1/users/user/not-a-uuid/", + "/api/v1/users/organization/not-a-uuid/", + "/api/v1/users/user/not-a-uuid/password/", + "/api/v1/users/user/not-a-uuid/email/", + "/api/v1/users/user/not-a-uuid/email/1/", + ) + + for path in invalid_uuid_paths: + with self.subTest(path=path): + response = self.client.get(path) + self.assertEqual(response.status_code, 404) From 3bb4ee51664152d12b7b8e85154b3978bd405641 Mon Sep 17 00:00:00 2001 From: Ayaan Ahmad Date: Thu, 12 Mar 2026 22:29:58 +0530 Subject: [PATCH 2/2] [fix] Compare UUID values in password permission check #487 After switching the users API detail routes to uuid converters, ChangePasswordView receives a UUID object in kwargs['pk'] instead of a string. The self-password permission check was still comparing str(self.request.user.id) to that UUID, which broke self password changes and caused API test regressions. Compare UUID values directly so self-password requests keep using the intended IsAuthenticated permission path. Related to #487 --- openwisp_users/api/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openwisp_users/api/views.py b/openwisp_users/api/views.py index daad88c7..b0eff948 100644 --- a/openwisp_users/api/views.py +++ b/openwisp_users/api/views.py @@ -158,7 +158,7 @@ def get_permissions(self): class if loggedin user wants to change his own password. """ - if str(self.request.user.id) == self.kwargs["pk"]: + if self.request.user.id == self.kwargs["pk"]: self.permission_classes = [IsAuthenticated] else: self.permission_classes = [IsAuthenticated, DjangoModelPermissions]