Skip to content

Commit 33bbd55

Browse files
pranshustuffpandafynemesifier
authored
[feature] Added REST API endpoints for Radius Groups #553
Closes #553 --------- Co-authored-by: Gagan Deep <pandafy.dev@gmail.com> Co-authored-by: Federico Capoano <f.capoano@openwisp.io>
1 parent 001a0c7 commit 33bbd55

6 files changed

Lines changed: 442 additions & 0 deletions

File tree

docs/user/rest-api.rst

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,3 +871,93 @@ users for a specific batch user creation operation. Example:
871871
curl -X GET \
872872
'http://127.0.0.1:8000/api/v1/radius/organization/default/batch/f4943c8a-462e-40ba-89b6-91a2541c9cf4/csv/' \
873873
-H 'Authorization: Bearer your-token-here'
874+
875+
RADIUS Groups
876+
+++++++++++++
877+
878+
.. code-block:: text
879+
880+
/api/v1/radius/group/
881+
882+
GET
883+
^^^
884+
885+
This allows listing of RADIUS groups. It supports sorting by organization
886+
id and group name.
887+
888+
.. code-block:: text
889+
890+
/api/v1/radius/group?search=<group_name>
891+
/api/v1/radius/group?organization=<org_id>
892+
893+
Filters
894+
"""""""
895+
896+
================= ============================
897+
Filter Parameter Description
898+
================= ============================
899+
search Search groups by name
900+
organization Filter organizations by id
901+
organization_slug Filter organizations by slug
902+
================= ============================
903+
904+
Pagination
905+
""""""""""
906+
907+
Pagination is provided using page number pagination, the default page size
908+
is 20, which can be overridden using the ``page_size`` parameter (maximum
909+
100).
910+
911+
.. code-block:: text
912+
913+
{
914+
"count": 42,
915+
"next": "http://example.com/api/v1/radius/group/?page=2",
916+
"previous": null,
917+
"results": [...]
918+
}
919+
920+
POST
921+
^^^^
922+
923+
Creates RADIUS Group.
924+
925+
============ ====================
926+
Param Description
927+
============ ====================
928+
name Name of group
929+
organization Organization UUID
930+
description Description of group
931+
============ ====================
932+
933+
.. note::
934+
935+
The group name is automatically prefixed with the organization slug
936+
when stored (for example: ``org-slug-Staff``).
937+
938+
GET (detail)
939+
^^^^^^^^^^^^
940+
941+
Returns a single RADIUS Group by its UUID.
942+
943+
.. code-block:: text
944+
945+
/api/v1/radius/group/<uuid>
946+
947+
PATCH
948+
^^^^^
949+
950+
Partially updates a RADIUS group identified by its UUID.
951+
952+
============ ====================
953+
Param Description
954+
============ ====================
955+
name Name of group
956+
organization Organization UUID
957+
description Description of group
958+
============ ====================
959+
960+
DELETE
961+
^^^^^^
962+
963+
Deletes a RADIUS group identified by its UUID.

openwisp_radius/api/serializers.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
from rest_framework.fields import empty
3030

3131
from openwisp_radius.api.exceptions import CrossOrgRegistrationException
32+
from openwisp_users.api.mixins import FilterSerializerByOrgManaged
3233
from openwisp_users.backends import UsersAuthenticationBackend
34+
from openwisp_utils.api.serializers import ValidatedModelSerializer
3335

3436
from .. import settings as app_settings
3537
from ..base.forms import PasswordResetForm
@@ -48,6 +50,7 @@
4850
RadiusPostAuth = load_model("RadiusPostAuth")
4951
RadiusAccounting = load_model("RadiusAccounting")
5052
RadiusBatch = load_model("RadiusBatch")
53+
RadiusGroup = load_model("RadiusGroup")
5154
RadiusToken = load_model("RadiusToken")
5255
RadiusGroupCheck = load_model("RadiusGroupCheck")
5356
RadiusUserGroup = load_model("RadiusUserGroup")
@@ -340,6 +343,28 @@ def to_representation(self, obj):
340343
return {"checks": checks_data}
341344

342345

346+
class RadiusGroupSerializer(FilterSerializerByOrgManaged, ValidatedModelSerializer):
347+
class Meta:
348+
model = RadiusGroup
349+
fields = "__all__"
350+
read_only_fields = ("created", "modified")
351+
352+
def validate(self, data):
353+
"""Validate and capture prefixed name from clean()."""
354+
data = super().validate(data)
355+
# For updates, ensure organization is in the data
356+
if self.instance and "organization" not in data:
357+
data["organization"] = self.instance.organization
358+
# Only apply name prefixing if name is being changed or created
359+
if "name" in data:
360+
# Create a temporary instance to get the prefixed name
361+
temp_instance = self.Meta.model(**data)
362+
temp_instance.clean()
363+
# Update data with the prefixed name
364+
data["name"] = temp_instance.name
365+
return data
366+
367+
343368
class GroupSerializer(serializers.ModelSerializer):
344369
class Meta:
345370
model = Group

openwisp_radius/api/urls.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,16 @@ def get_api_urls(api_views=None):
8888
api_views.radius_accounting,
8989
name="radius_accounting_list",
9090
),
91+
path(
92+
"radius/group/",
93+
api_views.radius_group_list,
94+
name="radius_group_list",
95+
),
96+
path(
97+
"radius/group/<uuid:pk>/",
98+
api_views.radius_group_detail,
99+
name="radius_group_detail",
100+
),
91101
]
92102
else:
93103
return []

openwisp_radius/api/views.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,24 @@
1919
from django.utils.translation import gettext_lazy as _
2020
from django.utils.translation.trans_real import get_language_from_request
2121
from django.views.decorators.csrf import csrf_exempt
22+
from django_filters import rest_framework as filters
2223
from django_filters.rest_framework import CharFilter, DjangoFilterBackend
2324
from drf_yasg.utils import no_body, swagger_auto_schema
2425
from rest_framework import serializers, status
2526
from rest_framework.authentication import SessionAuthentication
2627
from rest_framework.authtoken.models import Token as UserToken
2728
from rest_framework.authtoken.views import ObtainAuthToken as BaseObtainAuthToken
2829
from rest_framework.exceptions import NotFound, ParseError, PermissionDenied
30+
from rest_framework.filters import SearchFilter
2931
from rest_framework.generics import (
3032
CreateAPIView,
3133
GenericAPIView,
3234
ListAPIView,
35+
ListCreateAPIView,
3336
RetrieveAPIView,
37+
RetrieveUpdateDestroyAPIView,
3438
)
39+
from rest_framework.pagination import PageNumberPagination
3540
from rest_framework.permissions import (
3641
DjangoModelPermissions,
3742
IsAdminUser,
@@ -42,6 +47,7 @@
4247

4348
from openwisp_radius.api.serializers import RadiusUserSerializer
4449
from openwisp_users.api.authentication import BearerAuthentication, SesameAuthentication
50+
from openwisp_users.api.filters import OrganizationManagedFilter
4551
from openwisp_users.api.mixins import FilterByOrganizationManaged, ProtectedAPIMixin
4652
from openwisp_users.api.permissions import IsOrganizationManager
4753
from openwisp_users.api.views import ChangePasswordView as BasePasswordChangeView
@@ -62,6 +68,7 @@
6268
ChangePhoneNumberSerializer,
6369
RadiusAccountingSerializer,
6470
RadiusBatchSerializer,
71+
RadiusGroupSerializer,
6572
UserRadiusUsageSerializer,
6673
ValidatePhoneTokenSerializer,
6774
)
@@ -84,6 +91,7 @@
8491
RadiusToken = load_model("RadiusToken")
8592
RadiusBatch = load_model("RadiusBatch")
8693
RadiusUserGroup = load_model("RadiusUserGroup")
94+
RadiusGroup = load_model("RadiusGroup")
8795
RadiusGroupCheck = load_model("RadiusGroupCheck")
8896
auth_backend = UsersAuthenticationBackend()
8997

@@ -847,3 +855,90 @@ class RadiusAccountingView(ProtectedAPIMixin, FilterByOrganizationManaged, ListA
847855

848856

849857
radius_accounting = RadiusAccountingView.as_view()
858+
859+
860+
class RadiusGroupFilter(OrganizationManagedFilter, filters.FilterSet):
861+
"""
862+
Filter RADIUS groups by organizations managed by the user.
863+
"""
864+
865+
class Meta(OrganizationManagedFilter.Meta):
866+
model = RadiusGroup
867+
868+
869+
class RadiusGroupPaginator(PageNumberPagination):
870+
page_size = 20
871+
page_size_query_param = "page_size"
872+
max_page_size = 100
873+
874+
875+
@method_decorator(
876+
name="get",
877+
decorator=swagger_auto_schema(
878+
operation_description="""
879+
Returns a list of RADIUS groups for the organizations managed by the user.
880+
""",
881+
),
882+
)
883+
@method_decorator(
884+
name="post",
885+
decorator=swagger_auto_schema(
886+
operation_description="""
887+
Creates a new RADIUS group for an organization managed by the user.
888+
""",
889+
),
890+
)
891+
class RadiusGroupListView(
892+
ProtectedAPIMixin, FilterByOrganizationManaged, ListCreateAPIView
893+
):
894+
serializer_class = RadiusGroupSerializer
895+
queryset = RadiusGroup.objects.order_by("name")
896+
filterset_class = RadiusGroupFilter
897+
filter_backends = [DjangoFilterBackend, SearchFilter]
898+
search_fields = ["name"]
899+
pagination_class = RadiusGroupPaginator
900+
901+
902+
radius_group_list = RadiusGroupListView.as_view()
903+
904+
905+
@method_decorator(
906+
name="get",
907+
decorator=swagger_auto_schema(
908+
operation_description="""
909+
Returns a single RADIUS group by its UUID.
910+
""",
911+
),
912+
)
913+
@method_decorator(
914+
name="put",
915+
decorator=swagger_auto_schema(
916+
operation_description="""
917+
Updates a RADIUS group identified by its UUID.
918+
""",
919+
),
920+
)
921+
@method_decorator(
922+
name="patch",
923+
decorator=swagger_auto_schema(
924+
operation_description="""
925+
Partially updates a RADIUS group identified by its UUID.
926+
""",
927+
),
928+
)
929+
@method_decorator(
930+
name="delete",
931+
decorator=swagger_auto_schema(
932+
operation_description="""
933+
Deletes a RADIUS group identified by its UUID.
934+
""",
935+
),
936+
)
937+
class RadiusGroupDetailView(
938+
ProtectedAPIMixin, FilterByOrganizationManaged, RetrieveUpdateDestroyAPIView
939+
):
940+
serializer_class = RadiusGroupSerializer
941+
queryset = RadiusGroup.objects.order_by("name")
942+
943+
944+
radius_group_detail = RadiusGroupDetailView.as_view()

0 commit comments

Comments
 (0)