Skip to content

Commit e7ee368

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

6 files changed

Lines changed: 401 additions & 2 deletions

File tree

docs/user/rest-api.rst

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,3 +961,66 @@ 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 user group assignment.
1015+
1016+
======== ==============================================
1017+
Param Description
1018+
======== ==============================================
1019+
group UUID of the RADIUS group to assign (optional)
1020+
priority Priority of the assignment (optional, integer)
1021+
======== ==============================================
1022+
1023+
DELETE
1024+
^^^^^^
1025+
1026+
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: 102 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,99 @@ 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+
if getattr(self, "swagger_fake_view", False):
966+
return super().get_queryset()
967+
return qs.filter(user_id=self.kwargs["user_pk"])
968+
969+
def get_parent_queryset(self):
970+
"""Get the parent user from the URL."""
971+
return User.objects.filter(pk=self.kwargs["user_pk"])
972+
973+
def get_organization_queryset(self, qs):
974+
"""Filter users by organizations the request user manages."""
975+
orgs = self.request.user.organizations_managed
976+
app_label = User._meta.app_config.label
977+
filter_kwargs = {
978+
# exclude superusers
979+
"is_superuser": False,
980+
# ensure user is member of the org
981+
f"{app_label}_organizationuser__organization_id__in": orgs,
982+
}
983+
return qs.filter(**filter_kwargs).distinct()
984+
985+
986+
@method_decorator(
987+
name="get",
988+
decorator=swagger_auto_schema(
989+
operation_description="""
990+
Returns the list of RADIUS user groups for a specific user.
991+
""",
992+
),
993+
)
994+
@method_decorator(
995+
name="post",
996+
decorator=swagger_auto_schema(
997+
operation_description="""
998+
Creates a new RADIUS user group assignment for the user.
999+
""",
1000+
),
1001+
)
1002+
class RadiusUserGroupListCreateView(BaseRadiusUserGroupView, ListCreateAPIView):
1003+
pagination_class = RadiusGroupPaginator
1004+
1005+
1006+
radius_user_group_list = RadiusUserGroupListCreateView.as_view()
1007+
1008+
1009+
@method_decorator(
1010+
name="get",
1011+
decorator=swagger_auto_schema(
1012+
operation_description="""
1013+
Returns a single RADIUS user group by its UUID.
1014+
""",
1015+
),
1016+
)
1017+
@method_decorator(
1018+
name="put",
1019+
decorator=swagger_auto_schema(
1020+
operation_description="""
1021+
Updates a RADIUS user group identified by its UUID.
1022+
""",
1023+
),
1024+
)
1025+
@method_decorator(
1026+
name="patch",
1027+
decorator=swagger_auto_schema(
1028+
operation_description="""
1029+
Partially updates a RADIUS user group identified by its UUID.
1030+
""",
1031+
),
1032+
)
1033+
@method_decorator(
1034+
name="delete",
1035+
decorator=swagger_auto_schema(
1036+
operation_description="""
1037+
Deletes a RADIUS user group identified by its UUID.
1038+
""",
1039+
),
1040+
)
1041+
class RadiusUserGroupDetailView(BaseRadiusUserGroupView, RetrieveUpdateDestroyAPIView):
1042+
organization_field = "group__organization"
1043+
1044+
1045+
radius_user_group_detail = RadiusUserGroupDetailView.as_view()

0 commit comments

Comments
 (0)