Skip to content

Commit 9665dbc

Browse files
committed
[feature] Implemented view permissions by extending DjangoModelPermissions class #249
Closes #249
1 parent 7ade204 commit 9665dbc

3 files changed

Lines changed: 67 additions & 2 deletions

File tree

openwisp_users/api/permissions.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import copy
2+
13
from django.utils.translation import gettext_lazy as _
2-
from rest_framework.permissions import BasePermission
4+
from rest_framework.permissions import BasePermission, DjangoModelPermissions
35
from swapper import load_model
46

57
Organization = load_model('openwisp_users', 'Organization')
@@ -66,3 +68,32 @@ class IsOrganizationOwner(BaseOrganizationPermission):
6668

6769
def validate_membership(self, user, org):
6870
return org and (user.is_superuser or user.is_owner(org))
71+
72+
73+
class CustomDjangoModelPermissions(DjangoModelPermissions):
74+
def __init__(self):
75+
self.perms_map = copy.deepcopy(self.perms_map)
76+
self.perms_map['GET'] = ['%(app_label)s.view_%(model_name)s']
77+
78+
def has_permission(self, request, view):
79+
# Workaround to ensure DjangoModelPermissions are not applied
80+
# to the root view when using DefaultRouter.
81+
if getattr(view, '_ignore_model_permissions', False):
82+
return True
83+
84+
if not request.user or (
85+
not request.user.is_authenticated and self.authenticated_users_only
86+
):
87+
return False
88+
89+
queryset = self._queryset(view)
90+
perms = self.get_required_permissions(request.method, queryset.model)
91+
change_perm = self.get_required_permissions('PUT', queryset.model)
92+
93+
if request.method == 'GET':
94+
if request.user.has_perms(perms) or request.user.has_perms(change_perm):
95+
return True
96+
else:
97+
return False
98+
99+
return request.user.has_perms(perms)

tests/testapp/tests/test_permission_classes.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
from django.contrib.auth import get_user_model
2+
from django.contrib.auth.models import Permission
13
from django.test import TestCase
24
from django.urls import reverse
35

46
from openwisp_users.api.throttling import AuthRateThrottle
57

68
from .mixins import TestMultitenancyMixin
79

10+
User = get_user_model()
11+
812

913
class TestPermissionClasses(TestMultitenancyMixin, TestCase):
1014
def setUp(self):
@@ -117,3 +121,29 @@ def test_organization_field_with_errored_parent(self):
117121
with self.assertRaises(AttributeError) as error:
118122
self.client.get(reverse('test_error_field_view'), **auth)
119123
self.assertIn('Organization not found', str(error.exception))
124+
125+
def test_custom_django_model_permission_with_view_permission(self):
126+
user = User.objects.create_user(
127+
username='operator', password='tester', email='operator@test.com'
128+
)
129+
user_permissions = Permission.objects.filter(codename='view_template')
130+
user.user_permissions.add(*user_permissions)
131+
user.organizations_dict # force caching
132+
self.client.force_login(user)
133+
token = self._obtain_auth_token()
134+
auth = dict(HTTP_AUTHORIZATION=f'Bearer {token}')
135+
response = self.client.get(reverse('test_template_list'), **auth)
136+
self.assertEqual(response.status_code, 200)
137+
138+
def test_custom_django_model_permission_with_change_permission(self):
139+
user = User.objects.create_user(
140+
username='operator', password='tester', email='operator@test.com'
141+
)
142+
user_permissions = Permission.objects.filter(codename='change_template')
143+
user.user_permissions.add(*user_permissions)
144+
user.organizations_dict # force caching
145+
self.client.force_login(user)
146+
token = self._obtain_auth_token()
147+
auth = dict(HTTP_AUTHORIZATION=f'Bearer {token}')
148+
response = self.client.get(reverse('test_template_list'), **auth)
149+
self.assertEqual(response.status_code, 200)

tests/testapp/views.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
)
1515
from openwisp_users.api.permissions import (
1616
BaseOrganizationPermission,
17+
CustomDjangoModelPermissions,
1718
IsOrganizationManager,
1819
IsOrganizationMember,
1920
IsOrganizationOwner,
@@ -169,7 +170,10 @@ def get_queryset(self):
169170
class TemplateListCreateView(ListCreateAPIView):
170171
serializer_class = TemplateSerializer
171172
authentication_classes = (BearerAuthentication,)
172-
permission_classes = (IsOrganizationMember,)
173+
permission_classes = (
174+
IsOrganizationMember,
175+
CustomDjangoModelPermissions,
176+
)
173177
queryset = Template.objects.all()
174178

175179

0 commit comments

Comments
 (0)