Skip to content

Commit dc8d445

Browse files
committed
[feature] Added API endpoint to manage RADIUS groups of users #676
Closes #676
1 parent 33bbd55 commit dc8d445

6 files changed

Lines changed: 404 additions & 2 deletions

File tree

docs/user/rest-api.rst

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,3 +961,70 @@ DELETE
961961
^^^^^^
962962

963963
Deletes a RADIUS group identified by its UUID.
964+
965+
RADIUS User Groups
966+
++++++++++++++++++
967+
968+
.. code-block:: text
969+
970+
/api/v1/users/user/<user_pk>/radius-group/
971+
972+
GET
973+
^^^
974+
975+
Returns the list of RADIUS group assignments for the specified user.
976+
Pagination is provided using page number pagination; default page size is
977+
20 and can be overridden with the ``page_size`` parameter (maximum 100).
978+
979+
.. code-block:: text
980+
981+
GET /api/v1/users/user/<user_pk>/radius-group/
982+
983+
POST
984+
^^^^
985+
986+
Creates a RADIUS user group assignment for the specified user.
987+
988+
======== ==============================================
989+
Param Description
990+
======== ==============================================
991+
group UUID of the RADIUS group to assign (required)
992+
priority Priority of the assignment (optional, integer)
993+
======== ==============================================
994+
995+
.. note::
996+
997+
The provided ``group`` must belong to the same organization as the
998+
user; attempting to assign a group from another organization will
999+
return a ``400`` error with ``does_not_exist`` for the ``group``
1000+
field.
1001+
1002+
.. code-block:: text
1003+
1004+
/api/v1/users/user/<user_pk>/radius-group/<uuid>/
1005+
1006+
GET (detail)
1007+
^^^^^^^^^^^^
1008+
1009+
Returns a single RADIUS user group assignment by its UUID.
1010+
1011+
PATCH
1012+
^^^^^
1013+
1014+
Partially updates a RADIUS group identified by its UUID.
1015+
1016+
Partially or fully updates the RADIUS user group assignment. Supported
1017+
updatable fields: ``group`` and ``priority``. When changing ``group``, the
1018+
same-organization constraint applies.
1019+
1020+
======== ==============================================
1021+
Param Description
1022+
======== ==============================================
1023+
group UUID of the RADIUS group to assign (required)
1024+
priority Priority of the assignment (optional, integer)
1025+
======== ==============================================
1026+
1027+
DELETE
1028+
^^^^^^
1029+
1030+
Deletes the RADIUS user group assignment identified by the UUID.

openwisp_radius/api/serializers.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,24 @@ def validate(self, data):
365365
return data
366366

367367

368+
class RadiusUserGroupSerializer(FilterSerializerByOrgManaged, ValidatedModelSerializer):
369+
"""Serializer for RadiusUserGroup model."""
370+
371+
class Meta:
372+
model = RadiusUserGroup
373+
fields = ("id", "group", "priority", "created", "modified")
374+
read_only_fields = ("id", "created", "modified")
375+
376+
def validate(self, data):
377+
view = self.context.get("view")
378+
user = view.get_parent_queryset().first()
379+
if "username" not in data:
380+
data["username"] = user.username
381+
if "user" not in data:
382+
data["user"] = user
383+
return super().validate(data)
384+
385+
368386
class GroupSerializer(serializers.ModelSerializer):
369387
class Meta:
370388
model = Group

openwisp_radius/api/urls.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,16 @@ def get_api_urls(api_views=None):
9898
api_views.radius_group_detail,
9999
name="radius_group_detail",
100100
),
101+
path(
102+
"users/user/<str:user_pk>/radius-group/",
103+
api_views.radius_user_group_list,
104+
name="radius_user_group_list",
105+
),
106+
path(
107+
"users/user/<str:user_pk>/radius-group/<uuid:pk>/",
108+
api_views.radius_user_group_detail,
109+
name="radius_user_group_detail",
110+
),
101111
]
102112
else:
103113
return []

openwisp_radius/api/views.py

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@
4848
from openwisp_radius.api.serializers import RadiusUserSerializer
4949
from openwisp_users.api.authentication import BearerAuthentication, SesameAuthentication
5050
from openwisp_users.api.filters import OrganizationManagedFilter
51-
from openwisp_users.api.mixins import FilterByOrganizationManaged, ProtectedAPIMixin
51+
from openwisp_users.api.mixins import (
52+
FilterByOrganizationManaged,
53+
FilterByParentManaged,
54+
ProtectedAPIMixin,
55+
)
5256
from openwisp_users.api.permissions import IsOrganizationManager
5357
from openwisp_users.api.views import ChangePasswordView as BasePasswordChangeView
5458
from openwisp_users.backends import UsersAuthenticationBackend
@@ -69,6 +73,7 @@
6973
RadiusAccountingSerializer,
7074
RadiusBatchSerializer,
7175
RadiusGroupSerializer,
76+
RadiusUserGroupSerializer,
7277
UserRadiusUsageSerializer,
7378
ValidatePhoneTokenSerializer,
7479
)
@@ -942,3 +947,97 @@ class RadiusGroupDetailView(
942947

943948

944949
radius_group_detail = RadiusGroupDetailView.as_view()
950+
951+
952+
class BaseRadiusUserGroupView(ProtectedAPIMixin, FilterByParentManaged):
953+
"""
954+
Base view for RadiusUserGroup management.
955+
Provides user parent filtering and queryset logic.
956+
"""
957+
958+
serializer_class = RadiusUserGroupSerializer
959+
queryset = RadiusUserGroup.objects.select_related("group", "user").order_by(
960+
"-created"
961+
)
962+
963+
def get_queryset(self):
964+
qs = super().get_queryset()
965+
return qs.filter(user_id=self.kwargs["user_pk"])
966+
967+
def get_parent_queryset(self):
968+
"""Get the parent user from the URL."""
969+
return User.objects.filter(pk=self.kwargs["user_pk"])
970+
971+
def get_organization_queryset(self, qs):
972+
"""Filter users by organizations the request user manages."""
973+
orgs = self.request.user.organizations_managed
974+
app_label = User._meta.app_config.label
975+
filter_kwargs = {
976+
# exclude superusers
977+
"is_superuser": False,
978+
# ensure user is member of the org
979+
f"{app_label}_organizationuser__organization_id__in": orgs,
980+
}
981+
return qs.filter(**filter_kwargs).distinct()
982+
983+
984+
@method_decorator(
985+
name="get",
986+
decorator=swagger_auto_schema(
987+
operation_description="""
988+
Returns the list of RADIUS user groups for a specific user.
989+
""",
990+
),
991+
)
992+
@method_decorator(
993+
name="post",
994+
decorator=swagger_auto_schema(
995+
operation_description="""
996+
Creates a new RADIUS user group assignment for the user.
997+
""",
998+
),
999+
)
1000+
class RadiusUserGroupListCreateView(BaseRadiusUserGroupView, ListCreateAPIView):
1001+
pagination_class = RadiusGroupPaginator
1002+
1003+
1004+
radius_user_group_list = RadiusUserGroupListCreateView.as_view()
1005+
1006+
1007+
@method_decorator(
1008+
name="get",
1009+
decorator=swagger_auto_schema(
1010+
operation_description="""
1011+
Returns a single RADIUS user group by its UUID.
1012+
""",
1013+
),
1014+
)
1015+
@method_decorator(
1016+
name="put",
1017+
decorator=swagger_auto_schema(
1018+
operation_description="""
1019+
Updates a RADIUS user group identified by its UUID.
1020+
""",
1021+
),
1022+
)
1023+
@method_decorator(
1024+
name="patch",
1025+
decorator=swagger_auto_schema(
1026+
operation_description="""
1027+
Partially updates a RADIUS user group identified by its UUID.
1028+
""",
1029+
),
1030+
)
1031+
@method_decorator(
1032+
name="delete",
1033+
decorator=swagger_auto_schema(
1034+
operation_description="""
1035+
Deletes a RADIUS user group identified by its UUID.
1036+
""",
1037+
),
1038+
)
1039+
class RadiusUserGroupDetailView(BaseRadiusUserGroupView, RetrieveUpdateDestroyAPIView):
1040+
organization_field = "group__organization"
1041+
1042+
1043+
radius_user_group_detail = RadiusUserGroupDetailView.as_view()

0 commit comments

Comments
 (0)