Skip to content

Commit

Permalink
[feature] Published ProtectedApiMixin logic #337
Browse files Browse the repository at this point in the history
Closes #337
  • Loading branch information
Aryamanz29 authored Jan 18, 2023
1 parent 4f8e5f8 commit 7036fc0
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 10 deletions.
20 changes: 20 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,26 @@ Admin Multitenancy mixins
list_filter = [SubnetFilter]
# other options
ProtectedAPIMixin
~~~~~~~~~~~~~~~~~

This mixin provides a set of authentication and permission classes
that are used across various OpenWISP modules API views.

Usage example:

.. code-block:: python
# Used in openwisp-ipam
from openwisp_users.api.mixins import ProtectedAPIMixin as BaseProtectedAPIMixin
class ProtectedAPIMixin(BaseProtectedAPIMixin):
throttle_scope = 'ipam'
class SubnetView(ProtectedAPIMixin, RetrieveUpdateDestroyAPIView):
serializer_class = SubnetSerializer
queryset = Subnet.objects.all()
Extend openwisp-users
---------------------

Expand Down
19 changes: 19 additions & 0 deletions openwisp_users/api/mixins.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import swapper
from django.core.exceptions import ValidationError
from django.db.models import Q
from rest_framework.authentication import SessionAuthentication
from rest_framework.exceptions import NotFound
from rest_framework.permissions import IsAuthenticated

from .authentication import BearerAuthentication
from .permissions import DjangoModelPermissions, IsOrganizationManager

Organization = swapper.load_model('openwisp_users', 'Organization')


Expand Down Expand Up @@ -206,3 +210,18 @@ class FilterSerializerByOrgOwned(FilterSerializerByOrganization):
"""

_user_attr = 'organizations_owned'


class ProtectedAPIMixin(object):
"""
Contains authentication and permission classes for API views
"""

authentication_classes = (
BearerAuthentication,
SessionAuthentication,
)
permission_classes = (
IsOrganizationManager,
DjangoModelPermissions,
)
18 changes: 8 additions & 10 deletions openwisp_users/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from django.utils.translation import gettext_lazy as _
from drf_yasg.utils import swagger_auto_schema
from rest_framework import pagination
from rest_framework.authentication import SessionAuthentication
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.exceptions import NotFound
from rest_framework.generics import (
Expand All @@ -19,9 +18,9 @@
from rest_framework.settings import api_settings
from swapper import load_model

from openwisp_users.api.authentication import BearerAuthentication
from openwisp_users.api.permissions import DjangoModelPermissions

from .mixins import ProtectedAPIMixin as BaseProtectedAPIMixin
from .serializers import (
ChangePasswordSerializer,
EmailAddressSerializer,
Expand All @@ -42,6 +41,13 @@
OrganizationUser = load_model('openwisp_users', 'OrganizationUser')


class ProtectedAPIMixin(BaseProtectedAPIMixin):
permission_classes = (
IsAuthenticated,
DjangoModelPermissions,
)


class ListViewPagination(pagination.PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
Expand All @@ -62,14 +68,6 @@ def post(self, request, *args, **kwargs):
return super().post(request, *args, **kwargs)


class ProtectedAPIMixin(object):
authentication_classes = [BearerAuthentication, SessionAuthentication]
permission_classes = [
IsAuthenticated,
DjangoModelPermissions,
]


class BaseOrganizationView(ProtectedAPIMixin):
serializer_class = OrganizationSerializer

Expand Down
9 changes: 9 additions & 0 deletions openwisp_users/tests/test_api/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,12 @@ def test_obtain_auth_token(self):
url = reverse('users:user_auth_token')
r = self.client.post(url, params)
self.assertIn('token', r.data)

def test_protected_api_mixin_view(self):
auth_error = 'Authentication credentials were not provided.'
user = self._create_user(username='tester', password='tester')
path = reverse('users:user_detail', args=(user.pk,))
response = self.client.get(path)
self.assertEqual(response.headers['WWW-Authenticate'], 'Bearer')
self.assertEqual(response.data['detail'], auth_error)
self.assertEqual(response.status_code, 401)

0 comments on commit 7036fc0

Please sign in to comment.