From f3f7b4bb7d25bf99177b9a8b64f0f76ac120a62b Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Tue, 12 Feb 2019 03:36:16 -0400 Subject: [PATCH] Refactor the permissions app Use the new AddRemove View for the Role's group and permissions views as well as the Group's role views. Convert the API to use viewsets. Add more tests. Add role created and edited events. Add event subscription support to roles. Signed-off-by: Roberto Rosario --- mayan/apps/permissions/api_views.py | 215 ++++++--- mayan/apps/permissions/apps.py | 20 +- mayan/apps/permissions/classes.py | 10 +- mayan/apps/permissions/events.py | 16 + mayan/apps/permissions/methods.py | 43 ++ mayan/apps/permissions/models.py | 67 ++- mayan/apps/permissions/serializers.py | 183 ++++---- mayan/apps/permissions/tests/literals.py | 12 +- mayan/apps/permissions/tests/mixins.py | 19 +- mayan/apps/permissions/tests/test_api.py | 505 +++++++++++--------- mayan/apps/permissions/tests/test_views.py | 507 +++++++++++++++++++-- mayan/apps/permissions/urls.py | 26 +- mayan/apps/permissions/views.py | 167 +++---- 13 files changed, 1269 insertions(+), 521 deletions(-) create mode 100644 mayan/apps/permissions/events.py create mode 100644 mayan/apps/permissions/methods.py diff --git a/mayan/apps/permissions/api_views.py b/mayan/apps/permissions/api_views.py index fb23c78a00..3e0f079551 100644 --- a/mayan/apps/permissions/api_views.py +++ b/mayan/apps/permissions/api_views.py @@ -1,77 +1,180 @@ from __future__ import unicode_literals -from rest_framework import generics +from rest_framework import status, viewsets +from rest_framework.response import Response +from rest_framework.decorators import action -from mayan.apps.rest_api.filters import MayanObjectPermissionsFilter -from mayan.apps.rest_api.permissions import MayanPermission +from mayan.apps.rest_api.viewsets import MayanAPIModelViewSet +from mayan.apps.user_management.permissions import permission_group_view +from mayan.apps.user_management.serializers import GroupSerializer -from .classes import Permission +from .classes import PermissionNamespace from .models import Role from .permissions import ( permission_role_create, permission_role_delete, permission_role_edit, permission_role_view ) from .serializers import ( - PermissionSerializer, RoleSerializer, WritableRoleSerializer + PermissionNamespaceSerializer, PermissionSerializer, RoleGroupAddRemoveSerializer, + RolePermissionAddRemoveSerializer, RoleSerializer ) -class APIPermissionList(generics.ListAPIView): - """ - get: Returns a list of all the available permissions. - """ +class PermissionNamespaceViewSet(viewsets.ReadOnlyModelViewSet): + lookup_field = 'name' + lookup_url_kwarg = 'permission_namespace_name' + serializer_class = PermissionNamespaceSerializer + + def get_object(self): + lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field + filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} + return PermissionNamespace.get(**filter_kwargs) + + @action( + detail=True, serializer_class=PermissionSerializer, + url_name='permission-list', url_path='permissions' + ) + def permission_list(self, request, *args, **kwargs): + queryset = self.get_object().permissions + page = self.paginate_queryset(queryset) + + serializer = self.get_serializer( + queryset, many=True, context={'request': request} + ) + + if page is not None: + return self.get_paginated_response(serializer.data) + + return Response(serializer.data) + + def get_queryset(self): + return PermissionNamespace.all() + + +class PermissionViewSet(viewsets.ReadOnlyModelViewSet): + lookup_field = 'pk' + lookup_url_kwarg = 'permission_name' + lookup_value_regex = r'[\w\.]+' serializer_class = PermissionSerializer - queryset = Permission.all() + + def get_object(self): + namespace = PermissionNamespace.get(name=self.kwargs['permission_namespace_name']) + permissions = namespace.get_permissions() + return permissions.get(self.kwargs['permission_name']) -class APIRoleListView(generics.ListCreateAPIView): - """ - get: Returns a list of all the roles. - post: Create a new role. - """ - filter_backends = (MayanObjectPermissionsFilter,) - mayan_object_permissions = {'GET': (permission_role_view,)} - mayan_view_permissions = {'POST': (permission_role_create,)} - permission_classes = (MayanPermission,) - queryset = Role.objects.all() - - def get_serializer(self, *args, **kwargs): - if not self.request: - return None - - return super(APIRoleListView, self).get_serializer(*args, **kwargs) - - def get_serializer_class(self): - if self.request.method == 'GET': - return RoleSerializer - elif self.request.method == 'POST': - return WritableRoleSerializer - - -class APIRoleView(generics.RetrieveUpdateDestroyAPIView): - """ - delete: Delete the selected role. - get: Return the details of the selected role. - patch: Edit the selected role. - put: Edit the selected role. - """ - mayan_object_permissions = { - 'GET': (permission_role_view,), - 'PUT': (permission_role_edit,), - 'PATCH': (permission_role_edit,), - 'DELETE': (permission_role_delete,) +class RoleAPIViewSet(MayanAPIModelViewSet): + lookup_url_kwarg = 'role_id' + object_permission_map = { + 'destroy': permission_role_delete, + 'group_add': permission_role_edit, + 'group_list': permission_role_view, + 'group_remove': permission_role_edit, + 'list': permission_role_view, + 'partial_update': permission_role_edit, + 'retrieve': permission_role_view, + 'update': permission_role_edit, } - permission_classes = (MayanPermission,) queryset = Role.objects.all() + serializer_class = RoleSerializer + view_permission_map = { + 'create': permission_role_create + } - def get_serializer(self, *args, **kwargs): - if not self.request: - return None + @action( + detail=True, lookup_url_kwarg='role_id', methods=('post',), + serializer_class=RoleGroupAddRemoveSerializer, + url_name='group-add', url_path='groups/add' + ) + def group_add(self, request, *args, **kwargs): + instance = self.get_object() + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.groups_add(instance=instance) + headers = self.get_success_headers(data=serializer.data) + return Response( + serializer.data, status=status.HTTP_200_OK, headers=headers + ) - return super(APIRoleView, self).get_serializer(*args, **kwargs) + @action( + detail=True, lookup_url_kwarg='role_id', + serializer_class=GroupSerializer, url_name='group-list', + url_path='groups' + ) + def group_list(self, request, *args, **kwargs): + queryset = self.get_object().get_groups( + permission=permission_group_view, user=self.request.user + ) + page = self.paginate_queryset(queryset) - def get_serializer_class(self): - if self.request.method == 'GET': - return RoleSerializer - elif self.request.method in ('PATCH', 'PUT'): - return WritableRoleSerializer + serializer = self.get_serializer( + queryset, many=True, context={'request': request} + ) + + if page is not None: + return self.get_paginated_response(serializer.data) + + return Response(serializer.data) + + @action( + detail=True, lookup_url_kwarg='role_id', + methods=('post',), serializer_class=RoleGroupAddRemoveSerializer, + url_name='group-remove', url_path='groups/remove' + ) + def group_remove(self, request, *args, **kwargs): + instance = self.get_object() + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.groups_remove(instance=instance) + headers = self.get_success_headers(data=serializer.data) + return Response( + serializer.data, status=status.HTTP_200_OK, headers=headers + ) + + @action( + detail=True, lookup_url_kwarg='role_id', methods=('post',), + serializer_class=RolePermissionAddRemoveSerializer, + url_name='permission-add', url_path='permissions/add' + ) + def permission_add(self, request, *args, **kwargs): + instance = self.get_object() + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.permissions_add(instance=instance) + headers = self.get_success_headers(data=serializer.data) + return Response( + serializer.data, status=status.HTTP_200_OK, headers=headers + ) + + @action( + detail=True, lookup_url_kwarg='role_id', + serializer_class=PermissionSerializer, url_name='permission-list', + url_path='permissions' + ) + def permission_list(self, request, *args, **kwargs): + queryset = self.get_object().permissions.all() + page = self.paginate_queryset(queryset) + + serializer = self.get_serializer( + queryset, many=True, context={'request': request} + ) + + if page is not None: + return self.get_paginated_response(serializer.data) + + return Response(serializer.data) + + @action( + detail=True, lookup_url_kwarg='role_id', + methods=('post',), serializer_class=RolePermissionAddRemoveSerializer, + url_name='permission-remove', url_path='permissions/remove' + ) + def permission_remove(self, request, *args, **kwargs): + instance = self.get_object() + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.permissions_remove(instance=instance) + headers = self.get_success_headers(data=serializer.data) + return Response( + serializer.data, status=status.HTTP_200_OK, headers=headers + ) diff --git a/mayan/apps/permissions/apps.py b/mayan/apps/permissions/apps.py index 34ca946cbd..63983adcbb 100644 --- a/mayan/apps/permissions/apps.py +++ b/mayan/apps/permissions/apps.py @@ -13,14 +13,20 @@ from mayan.apps.common import ( menu_secondary, menu_setup ) from mayan.apps.common.signals import perform_upgrade +from mayan.apps.events import ModelEventType +from mayan.apps.events.links import ( + link_events_for_object, link_object_event_types_user_subcriptions_list +) from mayan.apps.navigation import SourceColumn +from .events import event_role_created, event_role_edited from .handlers import handler_purge_permissions from .links import ( link_group_roles, link_permission_grant, link_permission_revoke, link_role_create, link_role_delete, link_role_edit, link_role_groups, link_role_list, link_role_permissions ) +from .methods import method_group_roles_add, method_group_roles_remove from .permissions import ( permission_role_delete, permission_role_edit, permission_role_view ) @@ -37,10 +43,18 @@ class PermissionsApp(MayanAppConfig): def ready(self): super(PermissionsApp, self).ready() + from actstream import registry Role = self.get_model('Role') Group = apps.get_model(app_label='auth', model_name='Group') + Group.add_to_class(name='roles_add', value=method_group_roles_add) + Group.add_to_class(name='roles_remove', value=method_group_roles_remove) + + ModelEventType.register( + event_types=(event_role_created, event_role_edited), model=Role + ) + ModelPermission.register( model=Role, permissions=( permission_acl_edit, permission_acl_view, @@ -53,7 +67,9 @@ class PermissionsApp(MayanAppConfig): menu_list_facet.bind_links( links=( - link_acl_list, link_role_groups, link_role_permissions, + link_acl_list, link_events_for_object, + link_object_event_types_user_subcriptions_list, + link_role_groups, link_role_permissions, ), sources=(Role,) ) menu_list_facet.bind_links( @@ -78,3 +94,5 @@ class PermissionsApp(MayanAppConfig): dispatch_uid='permissions_handler_purge_permissions', receiver=handler_purge_permissions ) + + registry.register(Role) diff --git a/mayan/apps/permissions/classes.py b/mayan/apps/permissions/classes.py index eb75ca8202..08e5a8dbdf 100644 --- a/mayan/apps/permissions/classes.py +++ b/mayan/apps/permissions/classes.py @@ -2,15 +2,12 @@ from __future__ import unicode_literals import itertools import logging -import warnings from django.apps import apps from django.core.exceptions import PermissionDenied from django.utils.encoding import force_text, python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ -from mayan.apps.common.warnings import InterfaceWarning - from .exceptions import InvalidNamespace logger = logging.getLogger(__name__) @@ -49,6 +46,13 @@ class PermissionNamespace(object): self.permissions.append(permission) return permission + def get_permissions(self): + result = {} + for permission in self.permissions: + result[permission.pk] = permission + + return result + @python_2_unicode_compatible class Permission(object): diff --git a/mayan/apps/permissions/events.py b/mayan/apps/permissions/events.py new file mode 100644 index 0000000000..9b6d20ba96 --- /dev/null +++ b/mayan/apps/permissions/events.py @@ -0,0 +1,16 @@ +from __future__ import absolute_import, unicode_literals + +from django.utils.translation import ugettext_lazy as _ + +from mayan.apps.events import EventTypeNamespace + +namespace = EventTypeNamespace( + label=_('Permissions'), name='permissions' +) + +event_role_created = namespace.add_event_type( + label=_('Role created'), name='role_created' +) +event_role_edited = namespace.add_event_type( + label=_('Role edited'), name='role_edited' +) diff --git a/mayan/apps/permissions/methods.py b/mayan/apps/permissions/methods.py new file mode 100644 index 0000000000..79311d38f1 --- /dev/null +++ b/mayan/apps/permissions/methods.py @@ -0,0 +1,43 @@ +from __future__ import unicode_literals + +from django.apps import apps +from django.db import transaction + +from mayan.apps.user_management.events import event_group_edited + +from .events import event_role_edited + + +def method_group_get_roles(self, permission, _user): + AccessControlList = apps.get_model( + app_label='acls', model_name='AccessControlList' + ) + + return AccessControlList.objects.restrict_queryset( + permission=permission, queryset=self.roles.all(), + user=_user + ) + + +def method_group_roles_add(self, queryset, _user): + with transaction.atomic(): + event_group_edited.commit( + actor=_user, target=self + ) + for role in queryset: + self.roles.add(role) + event_role_edited.commit( + actor=_user, action_object=self, target=role + ) + + +def method_group_roles_remove(self, queryset, _user): + with transaction.atomic(): + event_group_edited.commit( + actor=_user, target=self + ) + for role in queryset: + self.roles.remove(role) + event_role_edited.commit( + actor=_user, action_object=self, target=role + ) diff --git a/mayan/apps/permissions/models.py b/mayan/apps/permissions/models.py index 503df464aa..70c167feb9 100644 --- a/mayan/apps/permissions/models.py +++ b/mayan/apps/permissions/models.py @@ -2,13 +2,17 @@ from __future__ import unicode_literals import logging +from django.apps import apps from django.contrib.auth.models import Group -from django.db import models +from django.db import models, transaction from django.urls import reverse from django.utils.encoding import force_text, python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ +from mayan.apps.user_management.events import event_group_edited + from .classes import Permission +from .events import event_role_created, event_role_edited from .managers import RoleManager, StoredPermissionManager logger = logging.getLogger(__name__) @@ -118,9 +122,70 @@ class Role(models.Model): def grant(self, permission): self.permissions.add(permission.stored_permission) + def get_groups(self, permission, user): + AccessControlList = apps.get_model( + app_label='acls', model_name='AccessControlList' + ) + + return AccessControlList.objects.restrict_queryset( + permission=permission, queryset=self.groups.all(), + user=user + ) + + def groups_add(self, queryset, _user=None): + with transaction.atomic(): + event_role_edited.commit( + actor=_user, target=self + ) + for obj in queryset: + self.groups.add(obj) + event_group_edited.commit( + actor=_user, action_object=self, target=obj + ) + + def groups_remove(self, queryset, _user=None): + with transaction.atomic(): + event_role_edited.commit( + actor=_user, target=self + ) + for obj in queryset: + self.groups.remove(obj) + event_group_edited.commit( + actor=_user, action_object=self, target=obj + ) + def natural_key(self): return (self.label,) natural_key.dependencies = ['auth.Group', 'permissions.StoredPermission'] + def permissions_add(self, queryset, _user=None): + with transaction.atomic(): + event_role_edited.commit( + actor=_user, target=self + ) + self.permissions.add(*queryset) + + def permissions_remove(self, queryset, _user=None): + with transaction.atomic(): + event_role_edited.commit( + actor=_user, target=self + ) + self.permissions.remove(*queryset) + def revoke(self, permission): self.permissions.remove(permission.stored_permission) + + def save(self, *args, **kwargs): + _user = kwargs.pop('_user', None) + + with transaction.atomic(): + is_new = not self.pk + super(Role, self).save(*args, **kwargs) + if is_new: + event_role_created.commit( + actor=_user, target=self + ) + else: + event_role_edited.commit( + actor=_user, target=self + ) diff --git a/mayan/apps/permissions/serializers.py b/mayan/apps/permissions/serializers.py index 4753bb3867..f4127e7ded 100644 --- a/mayan/apps/permissions/serializers.py +++ b/mayan/apps/permissions/serializers.py @@ -4,18 +4,45 @@ from django.contrib.auth.models import Group from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers -from rest_framework.exceptions import ValidationError -from mayan.apps.user_management.serializers import GroupSerializer +from mayan.apps.rest_api.mixins import ExternalObjectListSerializerMixin +from mayan.apps.rest_api.relations import MultiKwargHyperlinkedIdentityField +from mayan.apps.user_management.permissions import permission_group_edit from .classes import Permission from .models import Role, StoredPermission +class PermissionNamespaceSerializer(serializers.Serializer): + name = serializers.CharField(read_only=True) + label = serializers.CharField(read_only=True) + permissions_url = serializers.HyperlinkedIdentityField( + lookup_field='name', + lookup_url_kwarg='permission_namespace_name', + view_name='rest_api:permission_namespace-permission-list' + ) + url = serializers.HyperlinkedIdentityField( + lookup_field='name', + lookup_url_kwarg='permission_namespace_name', + view_name='rest_api:permission_namespace-detail' + ) + + class PermissionSerializer(serializers.Serializer): namespace = serializers.CharField(read_only=True) pk = serializers.CharField(read_only=True) label = serializers.CharField(read_only=True) + url = MultiKwargHyperlinkedIdentityField( + view_kwargs=( + { + 'lookup_field': 'namespace.name', 'lookup_url_kwarg': 'permission_namespace_name', + }, + { + 'lookup_field': 'pk', 'lookup_url_kwarg': 'permission_name', + } + ), + view_name='rest_api:permission-detail' + ) def to_representation(self, instance): if isinstance(instance, StoredPermission): @@ -28,85 +55,85 @@ class PermissionSerializer(serializers.Serializer): ) +class RoleGroupAddRemoveSerializer(ExternalObjectListSerializerMixin, serializers.Serializer): + group_id_list = serializers.CharField( + help_text=_( + 'Comma separated list of group primary keys that will be added or ' + 'removed.' + ), required=False, write_only=True + ) + + class Meta: + external_object_list_model = Group + external_object_list_permission = permission_group_edit + external_object_list_pk_list_field = 'group_id_list' + + def groups_add(self, instance): + instance.groups_add( + queryset=self.get_external_object_list(), + _user=self.context['request'].user + ) + + def groups_remove(self, instance): + instance.groups_remove( + queryset=self.get_external_object_list(), + _user=self.context['request'].user + ) + + +class RolePermissionAddRemoveSerializer(ExternalObjectListSerializerMixin, serializers.Serializer): + permission_id_list = serializers.CharField( + help_text=_( + 'Comma separated list of permission primary keys that will be added or ' + 'removed.' + ), required=False, write_only=True + ) + + class Meta: + external_object_list_model = Permission + external_object_list_pk_list_field = 'permission_id_list' + + def permissions_add(self, instance): + instance.permissions.add( + *self.get_external_object_list() + ) + + def permissions_remove(self, instance): + instance.permissions.remove( + *self.get_external_object_list() + ) + + class RoleSerializer(serializers.HyperlinkedModelSerializer): - groups = GroupSerializer(many=True, read_only=True) - permissions = PermissionSerializer(many=True, read_only=True) + group_add_url = serializers.HyperlinkedIdentityField( + lookup_url_kwarg='role_id', view_name='rest_api:role-group-add' + ) + group_list_url = serializers.HyperlinkedIdentityField( + lookup_url_kwarg='role_id', view_name='rest_api:role-group-list' + ) + group_remove_url = serializers.HyperlinkedIdentityField( + lookup_url_kwarg='role_id', view_name='rest_api:role-group-remove' + ) + permission_add_url = serializers.HyperlinkedIdentityField( + lookup_url_kwarg='role_id', view_name='rest_api:role-permission-add' + ) + permission_list_url = serializers.HyperlinkedIdentityField( + lookup_url_kwarg='role_id', view_name='rest_api:role-permission-list' + ) + permission_remove_url = serializers.HyperlinkedIdentityField( + lookup_url_kwarg='role_id', view_name='rest_api:role-permission-remove' + ) class Meta: extra_kwargs = { - 'url': {'view_name': 'rest_api:role-detail'}, + 'url': { + 'lookup_url_kwarg': 'role_id', + 'view_name': 'rest_api:role-detail' + } } - fields = ('groups', 'id', 'label', 'permissions', 'url') + fields = ( + 'id', 'label', 'url', 'group_add_url', 'group_list_url', + 'group_remove_url', 'permission_add_url', 'permission_list_url', + 'permission_remove_url' + ) model = Role - - -class WritableRoleSerializer(serializers.HyperlinkedModelSerializer): - groups_pk_list = serializers.CharField( - help_text=_( - 'Comma separated list of groups primary keys to add to, or replace' - ' in this role.' - ), required=False - ) - - permissions_pk_list = serializers.CharField( - help_text=_( - 'Comma separated list of permission primary keys to grant to this ' - 'role.' - ), required=False - ) - - class Meta: - fields = ('groups_pk_list', 'id', 'label', 'permissions_pk_list') - model = Role - - def create(self, validated_data): - self.groups_pk_list = validated_data.pop('groups_pk_list', '') - self.permissions_pk_list = validated_data.pop( - 'permissions_pk_list', '' - ) - - instance = super(WritableRoleSerializer, self).create(validated_data) - - if self.groups_pk_list: - self._add_groups(instance=instance) - - if self.permissions_pk_list: - self._add_permissions(instance=instance) - - return instance - - def _add_groups(self, instance): - instance.groups.add( - *Group.objects.filter(pk__in=self.groups_pk_list.split(',')) - ) - - def _add_permissions(self, instance): - for pk in self.permissions_pk_list.split(','): - try: - stored_permission = Permission.get(pk=pk) - instance.permissions.add(stored_permission) - instance.save() - except KeyError: - raise ValidationError(_('No such permission: %s') % pk) - - def update(self, instance, validated_data): - result = validated_data.copy() - - self.groups_pk_list = validated_data.pop('groups_pk_list', '') - self.permissions_pk_list = validated_data.pop( - 'permissions_pk_list', '' - ) - - result = super(WritableRoleSerializer, self).update( - instance, validated_data - ) - - if self.groups_pk_list: - instance.groups.clear() - self._add_groups(instance=instance) - - if self.permissions_pk_list: - instance.permissions.clear() - self._add_permissions(instance=instance) - - return result diff --git a/mayan/apps/permissions/tests/literals.py b/mayan/apps/permissions/tests/literals.py index 5c392a61ea..f81c7f4a3d 100644 --- a/mayan/apps/permissions/tests/literals.py +++ b/mayan/apps/permissions/tests/literals.py @@ -1,11 +1,11 @@ from __future__ import unicode_literals -TEST_CASE_ROLE_LABEL = 'test case role' +TEST_CASE_ROLE_LABEL = 'test case role label' TEST_INVALID_PERMISSION_NAMESPACE_NAME = 'invalid namespace' TEST_INVALID_PERMISSION_NAME = 'invalid name' -TEST_PERMISSION_NAMESPACE_LABEL = 'test namespace label' -TEST_PERMISSION_NAMESPACE_NAME = 'test namespace' -TEST_PERMISSION_LABEL = 'test name label' -TEST_PERMISSION_NAME = 'test name' -TEST_ROLE_LABEL = 'test role 2' +TEST_PERMISSION_NAMESPACE_LABEL = 'test permission namespace label' +TEST_PERMISSION_NAMESPACE_NAME = 'test_permission_namespace_name' +TEST_PERMISSION_LABEL = 'test permission name label' +TEST_PERMISSION_NAME = '{}.{}'.format(TEST_PERMISSION_NAMESPACE_NAME, 'test_permission_name') +TEST_ROLE_LABEL = 'test role label' TEST_ROLE_LABEL_EDITED = 'test role label edited' diff --git a/mayan/apps/permissions/tests/mixins.py b/mayan/apps/permissions/tests/mixins.py index 212fb554c4..8dd5442da4 100644 --- a/mayan/apps/permissions/tests/mixins.py +++ b/mayan/apps/permissions/tests/mixins.py @@ -1,8 +1,25 @@ from __future__ import unicode_literals +from ..classes import PermissionNamespace from ..models import Role -from .literals import TEST_CASE_ROLE_LABEL, TEST_ROLE_LABEL +from .literals import ( + TEST_CASE_ROLE_LABEL, TEST_PERMISSION_LABEL, TEST_PERMISSION_NAME, + TEST_PERMISSION_NAMESPACE_LABEL, TEST_PERMISSION_NAMESPACE_NAME, + TEST_ROLE_LABEL +) + + +class PermissionTestMixin(object): + def _create_test_permission(self): + self.test_permission_namespace = PermissionNamespace( + label=TEST_PERMISSION_NAMESPACE_LABEL, + name=TEST_PERMISSION_NAMESPACE_NAME + ) + self.test_permission = self.test_permission_namespace.add_permission( + label=TEST_PERMISSION_LABEL, + name=TEST_PERMISSION_NAME + ) class RoleTestCaseMixin(object): diff --git a/mayan/apps/permissions/tests/test_api.py b/mayan/apps/permissions/tests/test_api.py index 2ed28ceffb..54e7c0f292 100644 --- a/mayan/apps/permissions/tests/test_api.py +++ b/mayan/apps/permissions/tests/test_api.py @@ -1,13 +1,14 @@ from __future__ import unicode_literals -from django.contrib.auth.models import Group - from rest_framework import status from mayan.apps.rest_api.tests import BaseAPITestCase -from mayan.apps.user_management.tests.literals import TEST_GROUP_NAME +from mayan.apps.user_management.permissions import ( + permission_group_edit, permission_group_view +) +from mayan.apps.user_management.tests.mixins import GroupTestMixin -from ..classes import Permission +from ..classes import PermissionNamespace from ..models import Role from ..permissions import ( permission_role_create, permission_role_delete, permission_role_edit, @@ -15,239 +16,313 @@ from ..permissions import ( ) from .literals import TEST_ROLE_LABEL, TEST_ROLE_LABEL_EDITED -from .mixins import RoleTestMixin +from .mixins import PermissionTestMixin, RoleTestMixin -class PermissionAPITestCase(RoleTestMixin, BaseAPITestCase): - def test_permissions_list_view(self): - response = self.get(viewname='rest_api:permission-list') +class PermissionNamespaceAPITestCase(PermissionTestMixin, RoleTestMixin, BaseAPITestCase): + def _request_permission_namespace_list_api_view(self): + return self.get(viewname='rest_api:permission_namespace-list') + + def test_permission_namespace_list_api_view(self): + PermissionNamespace._registry = {} + self._create_test_permission() + + response = self._request_permission_namespace_list_api_view() self.assertEqual(response.status_code, status.HTTP_200_OK) - - # Role view - - def test_roles_list_view_no_access(self): - response = self.get(viewname='rest_api:role-list') - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data['count'], 0) - - def test_roles_list_view_with_access(self): - self.grant_access( - permission=permission_role_view, obj=self.test_role + self.assertEqual( + self.test_permission_namespace.name, response.json()['results'][0]['name'] ) - response = self.get(viewname='rest_api:role-list') + + def _request_permission_namespace_permission_list_api_view(self): + return self.get( + kwargs={ + 'permission_namespace_name': self.test_permission_namespace.name + }, viewname='rest_api:permission_namespace-permission-list' + ) + + def test_permission_namespace_permission_list_api_view(self): + self._create_test_permission() + + response = self._request_permission_namespace_permission_list_api_view() self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data['count'], 1) - self.assertEqual(response.data['results'][0]['label'], self.test_role.label) + self.assertEqual( + response.json()['results'][0]['pk'], self.test_permission.pk + ) - # Role create - - def _role_create_request(self, extra_data=None): - data = { - 'label': TEST_ROLE_LABEL - } - - if extra_data: - data.update(extra_data) +class RoleAPITestCase(RoleTestMixin, BaseAPITestCase): + def _request_role_create_api_view(self): return self.post( - viewname='rest_api:role-list', data=data - ) - - def test_role_create_view_no_permission(self): - response = self._role_create_request() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(Role.objects.count(), 1) - - def test_role_create_view_with_permission(self): - self.grant_permission(permission=permission_role_create) - response = self._role_create_request() - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - - role = Role.objects.get(label=TEST_ROLE_LABEL) - self.assertEqual(response.data, {'label': role.label, 'id': role.pk}) - self.assertEqual(Role.objects.count(), 2) - self.assertEqual(role.label, TEST_ROLE_LABEL) - - #def _create_group(self): - # self.test_group = Group.objects.create(name=TEST_GROUP_NAME) - - def _request_role_create_with_extra_data(self): - self._create_group() - - return self._role_create_request( - extra_data={ - 'groups_pk_list': '{}'.format(self.test_group.pk), - 'permissions_pk_list': '{}'.format(permission_role_view.pk) + viewname='rest_api:role-list', data={ + 'label': TEST_ROLE_LABEL } ) - def test_role_create_complex_view_no_permission(self): - response = self._request_role_create_with_extra_data() + def test_role_create_api_view_no_permission(self): + role_count = Role.objects.count() + response = self._request_role_create_api_view() self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(Role.objects.count(), 1) - self.assertEqual( - list(Role.objects.values_list('label', flat=True)), - [TEST_ROLE_LABEL] - ) - def test_role_create_complex_view_with_permission(self): + self.assertEqual(role_count, Role.objects.count()) + + def test_role_create_api_view_with_permission(self): + role_count = Role.objects.count() + self.grant_permission(permission=permission_role_create) - response = self._request_role_create_with_extra_data() - + response = self._request_role_create_api_view() self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(Role.objects.count(), 2) - role = Role.objects.get(label=TEST_ROLE_2_LABEL) - self.assertEqual(role.label, TEST_ROLE_2_LABEL) - self.assertQuerysetEqual( - role.groups.all(), (repr(self.test_group),) - ) - self.assertQuerysetEqual( - role.permissions.all(), - (repr(permission_role_view.stored_permission),) - ) - # Role edit + self.assertEqual(role_count + 1, Role.objects.count()) - def _request_role_edit(self, extra_data=None, request_type='patch'): - data = { - 'label': TEST_ROLE_LABEL_EDITED - } - - if extra_data: - data.update(extra_data) - - return getattr(self, request_type)( - viewname='rest_api:role-detail', kwargs={'role_id': self.test_role.pk}, - data=data - ) - - def test_role_edit_via_patch_no_access(self): - self._create_test_role() - response = self._request_role_edit(request_type='patch') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.test_role.refresh_from_db() - self.assertEqual(self.test_role.label, TEST_ROLE_LABEL) - - def test_role_edit_via_patch_with_access(self): - self._create_test_role() - self.grant_access(permission=permission_role_edit, obj=self.test_role) - response = self._request_role_edit(request_type='patch') - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.test_role.refresh_from_db() - self.assertEqual(self.test_role.label, TEST_ROLE_LABEL_EDITED) - - def _request_role_edit_via_patch_with_extra_data(self): - self._create_test_role() - self._create_group() - return self._request_role_edit( - extra_data={ - 'groups_pk_list': '{}'.format(self.test_group.pk), - 'permissions_pk_list': '{}'.format(permission_role_view.pk) - }, - request_type='patch' - ) - - def test_role_edit_complex_via_patch_no_access(self): - self._create_test_role() - - response = self._request_role_edit_via_patch_with_extra_data() - - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.test_role.refresh_from_db() - self.assertEqual(self.test_role.label, TEST_ROLE_LABEL) - - self.assertQuerysetEqual( - self.test_role.groups.all(), (repr(self.group),) - ) - self.assertQuerysetEqual(self.test_role.permissions.all(), ()) - - def test_role_edit_complex_via_patch_with_access(self): - self._create_test_role() - self.grant_access(permission=permission_role_edit, obj=self.test_role) - response = self._request_role_edit_via_patch_with_extra_data() - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.test_role.refresh_from_db() - self.assertEqual(self.test_role.label, TEST_ROLE_LABEL_EDITED) - self.assertQuerysetEqual( - self.test_role.groups.all(), (repr(self.test_group),) - ) - self.assertQuerysetEqual( - self.test_role.permissions.all(), - (repr(permission_role_view.stored_permission),) - ) - - def test_role_edit_via_put_no_access(self): - self._create_test_role() - response = self._request_role_edit(request_type='put') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.test_role.refresh_from_db() - self.assertEqual(self.test_role.label, TEST_ROLE_LABEL) - - def test_role_edit_via_put_with_access(self): - self._create_test_role() - self.grant_access(permission=permission_role_edit, obj=self.test_role) - response = self._request_role_edit(request_type='put') - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.test_role.refresh_from_db() - self.assertEqual(self.test_role.label, TEST_ROLE_LABEL_EDITED) - - def _request_role_edit_via_put_with_extra_data(self): - self._create_test_role() - self._create_group() - - return self._request_role_edit( - extra_data={ - 'groups_pk_list': '{}'.format(self.test_group.pk), - 'permissions_pk_list': '{}'.format(permission_role_view.pk) - }, request_type='put' - ) - - def test_role_edit_complex_via_put_no_access(self): - self._create_test_role() - response = self._request_role_edit_via_put_with_extra_data() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.test_role.refresh_from_db() - self.assertEqual(self.test_role.label, TEST_ROLE_LABEL) - self.assertQuerysetEqual( - self.test_role.groups.all(), (repr(self.group),) - ) - self.assertQuerysetEqual( - self.test_role.permissions.all(), - () - ) - - def test_role_edit_complex_via_put_with_access(self): - self._create_test_role() - self.grant_access(permission=permission_role_edit, obj=self.test_role) - response = self._request_role_edit_via_put_with_extra_data() - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.test_role.refresh_from_db() - self.assertEqual(self.test_role.label, TEST_ROLE_LABEL_EDITED) - self.assertQuerysetEqual( - self.test_role.groups.all(), (repr(self.test_group),) - ) - self.assertQuerysetEqual( - self.test_role.permissions.all(), - (repr(permission_role_view.stored_permission),) - ) - - # Role delete - - def _request_role_delete_view(self): + def _request_role_delete_api_view(self): return self.delete( viewname='rest_api:role-detail', kwargs={'role_id': self.test_role.pk} ) - def test_role_delete_view_no_access(self): - response = self._request_role_delete_view() - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(Role.objects.count(), 1) + def test_role_delete_api_view_no_permission(self): + self._create_test_role() + role_count = Role.objects.count() - def test_role_delete_view_with_access(self): - self.grant_access(permission=permission_role_delete, obj=self.test_role) - response = self._request_role_delete_view() + response = self._request_role_delete_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.assertEqual(role_count, Role.objects.count()) + + def test_role_delete_api_view_with_access(self): + self._create_test_role() + role_count = Role.objects.count() + + self.grant_access(obj=self.test_role, permission=permission_role_delete) + response = self._request_role_delete_api_view() self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - self.assertEqual(Role.objects.count(), 0) + + self.assertEqual(role_count - 1, Role.objects.count()) + + def _request_role_edit(self, request_type='patch'): + return getattr(self, request_type)( + viewname='rest_api:role-detail', kwargs={'role_id': self.test_role.pk}, + data={ + 'label': TEST_ROLE_LABEL_EDITED + } + ) + + def test_role_edit_patch_api_view_no_permission(self): + self._create_test_role() + role_label = self.test_role.label + + response = self._request_role_edit(request_type='patch') + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_role.refresh_from_db() + self.assertEqual(self.test_role.label, role_label) + + def test_role_edit_patch_api_view_with_access(self): + self._create_test_role() + role_label = self.test_role.label + + self.grant_access(obj=self.test_role, permission=permission_role_edit) + response = self._request_role_edit(request_type='patch') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.test_role.refresh_from_db() + self.assertNotEqual(self.test_role.label, role_label) + + def test_role_edit_put_api_view_no_permission(self): + self._create_test_role() + role_label = self.test_role.label + + response = self._request_role_edit(request_type='put') + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_role.refresh_from_db() + self.assertEqual(self.test_role.label, role_label) + + def test_role_edit_put_api_view_with_access(self): + self._create_test_role() + role_label = self.test_role.label + + self.grant_access(obj=self.test_role, permission=permission_role_edit) + response = self._request_role_edit(request_type='put') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.test_role.refresh_from_db() + self.assertNotEqual(self.test_role.label, role_label) + + def _request_role_list_api_view(self): + return self.get(viewname='rest_api:role-list') + + def test_role_list_api_view_no_permission(self): + self._create_test_role() + + response = self._request_role_list_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertFalse(self.test_role.label in response.content) + + def test_role_list_api_view_with_access(self): + self._create_test_role() + + self.grant_access(obj=self.test_role, permission=permission_role_view) + response = self._request_role_list_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.assertTrue(self.test_role.label in response.content) + + +class RoleGroupAPITestCase(GroupTestMixin, RoleTestMixin, BaseAPITestCase): + def _request_role_group_list_api_view(self): + return self.get( + viewname='rest_api:role-group-list', + kwargs={'role_id': self.test_role.pk} + ) + + def _request_role_group_add_api_view(self): + return self.post( + viewname='rest_api:role-group-add', + kwargs={'role_id': self.test_role.pk}, + data={'group_id_list': '{}'.format(self.test_group.pk)} + ) + + def _request_role_group_remove_api_view(self): + return self.post( + viewname='rest_api:role-group-remove', + kwargs={'role_id': self.test_role.pk}, + data={'group_id_list': '{}'.format(self.test_group.pk)} + ) + + def _setup_role_group_list(self): + self._create_test_group() + self._create_test_role() + self.test_role.groups.add(self.test_group) + + def test_role_group_list_api_view_no_permission(self): + self._setup_role_group_list() + + response = self._request_role_group_list_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_role_group_list_api_view_with_role_access(self): + self._setup_role_group_list() + + self.grant_access(obj=self.test_role, permission=permission_role_view) + response = self._request_role_group_list_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['count'], 0) + + def test_role_group_list_api_view_with_group_access(self): + self._setup_role_group_list() + + self.grant_access( + obj=self.test_group, permission=permission_group_view + ) + response = self._request_role_group_list_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_role_group_list_api_view_with_full_access(self): + self._setup_role_group_list() + + self.grant_access(obj=self.test_role, permission=permission_role_view) + self.grant_access( + obj=self.test_group, permission=permission_group_view + ) + response = self._request_role_group_list_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['count'], 1) + + def _setup_role_group_add(self): + self._create_test_group() + self._create_test_role() + + def test_role_group_add_api_view_no_permission(self): + self._setup_role_group_add() + + response = self._request_role_group_add_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_role.refresh_from_db() + self.assertTrue(self.test_group not in self.test_role.groups.all()) + + def test_role_group_add_api_view_with_role_access(self): + self._setup_role_group_add() + + self.grant_access(obj=self.test_role, permission=permission_role_edit) + response = self._request_role_group_add_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.test_role.refresh_from_db() + self.assertTrue(self.test_group not in self.test_role.groups.all()) + + def test_role_group_add_api_view_with_group_access(self): + self._setup_role_group_add() + + self.grant_access( + obj=self.test_group, permission=permission_group_edit + ) + response = self._request_role_group_add_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_role.refresh_from_db() + self.assertTrue(self.test_group not in self.test_role.groups.all()) + + def test_role_group_add_api_view_with_full_access(self): + self._setup_role_group_add() + + self.grant_access(obj=self.test_role, permission=permission_role_edit) + self.grant_access( + obj=self.test_group, permission=permission_group_edit + ) + response = self._request_role_group_add_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.test_role.refresh_from_db() + self.assertTrue(self.test_group in self.test_role.groups.all()) + + def _setup_role_group_remove(self): + self._create_test_group() + self._create_test_role() + self.test_role.groups.add(self.test_group) + + def test_role_group_remove_api_view_no_permission(self): + self._setup_role_group_remove() + + response = self._request_role_group_remove_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_role.refresh_from_db() + self.assertTrue(self.test_group in self.test_role.groups.all()) + + def test_role_group_remove_api_view_with_role_access(self): + self._setup_role_group_remove() + + self.grant_access(obj=self.test_role, permission=permission_role_edit) + response = self._request_role_group_remove_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.test_role.refresh_from_db() + self.assertTrue(self.test_group in self.test_role.groups.all()) + + def test_role_group_remove_api_view_with_group_access(self): + self._setup_role_group_remove() + + self.grant_access( + obj=self.test_group, permission=permission_group_edit + ) + response = self._request_role_group_remove_api_view() + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + self.test_role.refresh_from_db() + self.assertTrue(self.test_group in self.test_role.groups.all()) + + def test_role_group_remove_api_view_with_full_access(self): + self._setup_role_group_remove() + + self.grant_access(obj=self.test_role, permission=permission_role_edit) + self.grant_access( + obj=self.test_group, permission=permission_group_edit + ) + response = self._request_role_group_remove_api_view() + self.assertEqual(response.status_code, status.HTTP_200_OK) + + self.test_role.refresh_from_db() + self.assertTrue(self.test_group not in self.test_role.groups.all()) diff --git a/mayan/apps/permissions/tests/test_views.py b/mayan/apps/permissions/tests/test_views.py index a4a6252999..a1565da404 100644 --- a/mayan/apps/permissions/tests/test_views.py +++ b/mayan/apps/permissions/tests/test_views.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from mayan.apps.common.tests import GenericViewTestCase from mayan.apps.user_management.permissions import permission_group_edit -from mayan.apps.user_management.tests import GroupTestMixin +from mayan.apps.user_management.tests.mixins import GroupTestMixin from ..models import Role from ..permissions import ( @@ -11,14 +11,10 @@ from ..permissions import ( ) from .literals import TEST_ROLE_LABEL, TEST_ROLE_LABEL_EDITED -from .mixins import RoleTestMixin +from .mixins import PermissionTestMixin, RoleTestMixin -class PermissionsViewsTestCase(GroupTestMixin, RoleTestMixin, GenericViewTestCase): - def setUp(self): - super(PermissionsViewsTestCase, self).setUp() - self.login_user() - +class RoleViewsTestCase(RoleTestMixin, GenericViewTestCase): def _request_create_role_view(self): return self.post( viewname='permissions:role_create', data={ @@ -27,21 +23,21 @@ class PermissionsViewsTestCase(GroupTestMixin, RoleTestMixin, GenericViewTestCas ) def test_role_creation_view_no_permission(self): + role_count = Role.objects.count() + response = self._request_create_role_view() self.assertEqual(response.status_code, 403) - self.assertEqual(Role.objects.count(), 1) - self.assertFalse( - TEST_ROLE_LABEL in Role.objects.values_list('label', flat=True) - ) + + self.assertTrue(role_count == Role.objects.count()) def test_role_creation_view_with_permission(self): + role_count = Role.objects.count() + self.grant_permission(permission=permission_role_create) response = self._request_create_role_view() self.assertEqual(response.status_code, 302) - self.assertEqual(Role.objects.count(), 2) - self.assertTrue( - TEST_ROLE_LABEL in Role.objects.values_list('label', flat=True) - ) + + self.assertTrue(role_count + 1 == Role.objects.count()) def _request_role_delete_view(self): return self.post( @@ -51,22 +47,22 @@ class PermissionsViewsTestCase(GroupTestMixin, RoleTestMixin, GenericViewTestCas def test_role_delete_view_no_permission(self): self._create_test_role() + role_count = Role.objects.count() + response = self._request_role_delete_view() self.assertEqual(response.status_code, 404) - self.assertEqual(Role.objects.count(), 2) - self.assertTrue( - TEST_ROLE_LABEL in Role.objects.values_list('label', flat=True) - ) + + self.assertTrue(role_count == Role.objects.count()) def test_role_delete_view_with_access(self): self._create_test_role() - self.grant_access(permission=permission_role_delete, obj=self.test_role) + role_count = Role.objects.count() + + self.grant_access(obj=self.test_role, permission=permission_role_delete) response = self._request_role_delete_view() self.assertEqual(response.status_code, 302) - self.assertEqual(Role.objects.count(), 1) - self.assertFalse( - TEST_ROLE_LABEL in Role.objects.values_list('label', flat=True) - ) + + self.assertTrue(role_count - 1 == Role.objects.count()) def _request_role_edit_view(self): return self.post( @@ -78,44 +74,48 @@ class PermissionsViewsTestCase(GroupTestMixin, RoleTestMixin, GenericViewTestCas def test_role_edit_view_no_permission(self): self._create_test_role() - response = self._request_role_edit_view() + role_label = self.test_role.label + response = self._request_role_edit_view() self.assertEqual(response.status_code, 404) self.test_role.refresh_from_db() - self.assertEqual(Role.objects.count(), 2) - self.assertEqual(self.test_role.label, TEST_ROLE_LABEL) + self.assertTrue(role_label == self.test_role.label) def test_role_edit_view_with_access(self): self._create_test_role() - self.grant_access(permission=permission_role_edit, obj=self.test_role) + role_label = self.test_role.label + + self.grant_access(obj=self.test_role, permission=permission_role_edit) response = self._request_role_edit_view() - self.assertEqual(response.status_code, 302) - self.test_role.refresh_from_db() - self.assertEqual(Role.objects.count(), 2) - self.assertEqual(self.test_role.label, TEST_ROLE_LABEL_EDITED) + self.test_role.refresh_from_db() + self.assertTrue(role_label != self.test_role.label) def _request_role_list_view(self): return self.get(viewname='permissions:role_list') def test_role_list_view_no_permission(self): self._create_test_role() + response = self._request_role_list_view() self.assertEqual(response.status_code, 200) self.assertNotContains( - response=response, text=TEST_ROLE_LABEL, status_code=200 + response=response, text=self.test_role.label, status_code=200 ) def test_role_list_view_with_access(self): self._create_test_role() + self.grant_access(permission=permission_role_view, obj=self.test_role) response = self._request_role_list_view() self.assertContains( - response=response, text=TEST_ROLE_LABEL, status_code=200 + response=response, text=self.test_role.label, status_code=200 ) + +class RolePermissionViewsTestCase(PermissionTestMixin, RoleTestMixin, GenericViewTestCase): def _request_role_permissions_view(self): return self.get( viewname='permissions:role_permissions', @@ -124,17 +124,93 @@ class PermissionsViewsTestCase(GroupTestMixin, RoleTestMixin, GenericViewTestCas def test_role_permissions_view_no_permission(self): self._create_test_role() + response = self._request_role_permissions_view() - self.assertEqual(response.status_code, 403) + self.assertEqual(response.status_code, 404) def test_role_permissions_view_with_access(self): self._create_test_role() + self.grant_access( - permission=permission_permission_view, obj=self.test_role + obj=self.test_role, permission=permission_role_edit ) response = self._request_role_permissions_view() self.assertEqual(response.status_code, 200) + def _request_role_permissions_add_view(self): + return self.post( + viewname='permissions:role_permissions', + kwargs={'role_id': self.test_role.pk}, + data={'available-selection': self.test_permission.stored_permission.pk} + ) + + def test_role_permission_add_view_no_permission(self): + self._create_test_role() + self._create_test_permission() + + response = self._request_role_permissions_add_view() + self.assertEqual(response.status_code, 404) + + self.test_role.refresh_from_db() + self.assertTrue( + self.test_permission.stored_permission not in self.test_role.permissions.all() + ) + + def test_role_permission_add_view_with_access(self): + self._create_test_role() + self._create_test_permission() + + self.grant_access( + obj=self.test_role, permission=permission_role_edit + ) + + response = self._request_role_permissions_add_view() + self.assertEqual(response.status_code, 302) + + self.test_role.refresh_from_db() + self.assertTrue( + self.test_permission.stored_permission in self.test_role.permissions.all() + ) + + def _request_role_permissions_remove_view(self): + return self.post( + viewname='permissions:role_permissions', + kwargs={'role_id': self.test_role.pk}, + data={'added-selection': self.test_permission.stored_permission.pk} + ) + + def test_role_permission_remove_view_no_permission(self): + self._create_test_role() + self._create_test_permission() + self.test_role.grant(permission=self.test_permission) + + response = self._request_role_permissions_remove_view() + self.assertEqual(response.status_code, 404) + + self.test_role.refresh_from_db() + self.assertTrue( + self.test_permission.stored_permission in self.test_role.permissions.all() + ) + + def test_role_permission_remove_view_with_access(self): + self._create_test_role() + self._create_test_permission() + self.test_role.grant(permission=self.test_permission) + + self.grant_access( + obj=self.test_role, permission=permission_role_edit + ) + + response = self._request_role_permissions_remove_view() + self.assertEqual(response.status_code, 302) + + self.test_role.refresh_from_db() + self.assertTrue( + self.test_permission.stored_permission not in self.test_role.permissions.all() + ) + + +class RoleGroupViewsTestCase(GroupTestMixin, RoleTestMixin, GenericViewTestCase): def _request_role_groups_view(self): return self.get( viewname='permissions:role_groups', @@ -143,15 +219,196 @@ class PermissionsViewsTestCase(GroupTestMixin, RoleTestMixin, GenericViewTestCas def test_role_groups_view_no_permission(self): self._create_test_role() - response = self._request_role_groups_view() - self.assertEqual(response.status_code, 403) + self._create_test_group() - def test_role_groups_view_with_access(self): + response = self._request_role_groups_view() + self.assertNotContains( + response=response, text=self.test_role.label, status_code=404 + ) + self.assertNotContains( + response=response, text=self.test_group.name, status_code=404 + ) + + def test_role_groups_view_with_role_access(self): self._create_test_role() - self.grant_access(permission=permission_role_edit, obj=self.test_role) + self._create_test_group() + + self.grant_access(obj=self.test_role, permission=permission_role_edit) response = self._request_role_groups_view() self.assertEqual(response.status_code, 200) + self.assertContains( + response=response, text=self.test_role.label, status_code=200 + ) + self.assertNotContains( + response=response, text=self.test_group.name, status_code=200 + ) + def _request_role_groups_add_view(self): + return self.post( + viewname='permissions:role_groups', + kwargs={'role_id': self.test_role.pk}, + data={'available-selection': self.test_group.pk} + ) + + def test_role_group_add_view_no_permission(self): + self._create_test_role() + self._create_test_group() + + response = self._request_role_groups_add_view() + self.assertEqual(response.status_code, 404) + + self.test_role.refresh_from_db() + self.assertTrue( + self.test_group not in self.test_role.groups.all() + ) + + def test_role_group_add_view_with_role_access(self): + self._create_test_role() + self._create_test_group() + + self.grant_access( + obj=self.test_role, permission=permission_role_edit + ) + + response = self._request_role_groups_add_view() + self.assertContains( + response=response, text=self.test_role, status_code=200 + ) + self.assertNotContains( + response=response, text=self.test_group, status_code=200 + ) + + self.test_role.refresh_from_db() + self.assertTrue( + self.test_group not in self.test_role.groups.all() + ) + + def test_role_group_add_view_with_group_access(self): + self._create_test_role() + self._create_test_group() + + self.grant_access( + obj=self.test_group, permission=permission_group_edit + ) + + response = self._request_role_groups_add_view() + self.assertNotContains( + response=response, text=self.test_role, status_code=404 + ) + self.assertNotContains( + response=response, text=self.test_group, status_code=404 + ) + + self.test_role.refresh_from_db() + self.assertTrue( + self.test_group not in self.test_role.groups.all() + ) + + def test_role_group_add_view_with_full_access(self): + self._create_test_role() + self._create_test_group() + + self.grant_access( + obj=self.test_group, permission=permission_group_edit + ) + self.grant_access( + obj=self.test_role, permission=permission_role_edit + ) + + response = self._request_role_groups_add_view() + self.assertEqual(response.status_code, 302) + + self.test_role.refresh_from_db() + self.assertTrue( + self.test_group in self.test_role.groups.all() + ) + + def _request_role_groups_remove_view(self): + return self.post( + viewname='permissions:role_groups', + kwargs={'role_id': self.test_role.pk}, + data={'added-selection': self.test_group.pk} + ) + + def test_role_group_remove_view_no_permission(self): + self._create_test_role() + self._create_test_group() + self.test_role.groups.add(self.test_group) + + response = self._request_role_groups_remove_view() + self.assertEqual(response.status_code, 404) + + self.test_role.refresh_from_db() + self.assertTrue( + self.test_group in self.test_role.groups.all() + ) + + def test_role_group_remove_view_with_role_access(self): + self._create_test_role() + self._create_test_group() + self.test_role.groups.add(self.test_group) + + self.grant_access( + obj=self.test_role, permission=permission_role_edit + ) + + response = self._request_role_groups_remove_view() + self.assertContains( + response=response, text=self.test_role, status_code=200 + ) + self.assertNotContains( + response=response, text=self.test_group, status_code=200 + ) + + self.test_role.refresh_from_db() + self.assertTrue( + self.test_group in self.test_role.groups.all() + ) + + def test_role_group_remove_view_with_group_access(self): + self._create_test_role() + self._create_test_group() + self.test_role.groups.add(self.test_group) + + self.grant_access( + obj=self.test_group, permission=permission_group_edit + ) + + response = self._request_role_groups_remove_view() + self.assertNotContains( + response=response, text=self.test_role, status_code=404 + ) + self.assertNotContains( + response=response, text=self.test_group, status_code=404 + ) + + self.test_role.refresh_from_db() + self.assertTrue( + self.test_group in self.test_role.groups.all() + ) + + def test_role_group_remove_view_with_full_access(self): + self._create_test_role() + self._create_test_group() + self.test_role.groups.add(self.test_group) + + self.grant_access( + obj=self.test_group, permission=permission_group_edit + ) + self.grant_access( + obj=self.test_role, permission=permission_role_edit + ) + + response = self._request_role_groups_remove_view() + self.assertEqual(response.status_code, 302) + + self.test_role.refresh_from_db() + self.assertTrue( + self.test_group not in self.test_role.groups.all() + ) + + +class GroupRoleViewsTestCase(GroupTestMixin, RoleTestMixin, GenericViewTestCase): def _request_group_roles_view(self): return self.get( viewname='permissions:group_roles', @@ -160,11 +417,177 @@ class PermissionsViewsTestCase(GroupTestMixin, RoleTestMixin, GenericViewTestCas def test_group_roles_view_no_permission(self): self._create_test_group() + response = self._request_group_roles_view() - self.assertEqual(response.status_code, 403) + self.assertEqual(response.status_code, 404) def test_group_roles_view_with_access(self): self._create_test_group() - self.grant_access(permission=permission_group_edit, obj=self.test_group) + + self.grant_access(obj=self.test_group, permission=permission_group_edit) response = self._request_group_roles_view() self.assertEqual(response.status_code, 200) + + def _request_group_roles_add_view(self): + return self.post( + viewname='permissions:group_roles', + kwargs={'group_id': self.test_group.pk}, + data={'available-selection': self.test_role.pk} + ) + + def test_group_role_add_view_no_permission(self): + self._create_test_group() + self._create_test_role() + + response = self._request_group_roles_add_view() + self.assertEqual(response.status_code, 404) + + self.test_group.refresh_from_db() + self.assertTrue( + self.test_role not in self.test_group.roles.all() + ) + + def test_group_role_add_view_with_group_access(self): + self._create_test_group() + self._create_test_role() + + self.grant_access( + obj=self.test_group, permission=permission_group_edit + ) + + response = self._request_group_roles_add_view() + self.assertContains( + response=response, text=self.test_group, status_code=200 + ) + self.assertNotContains( + response=response, text=self.test_role, status_code=200 + ) + + self.test_group.refresh_from_db() + self.assertTrue( + self.test_role not in self.test_group.roles.all() + ) + + def test_group_role_add_view_with_role_access(self): + self._create_test_group() + self._create_test_role() + + self.grant_access( + obj=self.test_role, permission=permission_role_edit + ) + + response = self._request_group_roles_add_view() + self.assertNotContains( + response=response, text=self.test_group, status_code=404 + ) + self.assertNotContains( + response=response, text=self.test_role, status_code=404 + ) + + self.test_group.refresh_from_db() + self.assertTrue( + self.test_role not in self.test_group.roles.all() + ) + + def test_group_role_add_view_with_full_access(self): + self._create_test_group() + self._create_test_role() + + self.grant_access( + obj=self.test_role, permission=permission_role_edit + ) + self.grant_access( + obj=self.test_group, permission=permission_group_edit + ) + + response = self._request_group_roles_add_view() + self.assertEqual(response.status_code, 302) + + self.test_group.refresh_from_db() + self.assertTrue( + self.test_role in self.test_group.roles.all() + ) + + def _request_group_roles_remove_view(self): + return self.post( + viewname='permissions:group_roles', + kwargs={'group_id': self.test_group.pk}, + data={'added-selection': self.test_role.pk} + ) + + def test_group_role_remove_view_no_permission(self): + self._create_test_group() + self._create_test_role() + self.test_group.roles.add(self.test_role) + + response = self._request_group_roles_remove_view() + self.assertEqual(response.status_code, 404) + + self.test_group.refresh_from_db() + self.assertTrue( + self.test_role in self.test_group.roles.all() + ) + + def test_group_role_remove_view_with_group_access(self): + self._create_test_group() + self._create_test_role() + self.test_group.roles.add(self.test_role) + + self.grant_access( + obj=self.test_group, permission=permission_group_edit + ) + + response = self._request_group_roles_remove_view() + self.assertContains( + response=response, text=self.test_group, status_code=200 + ) + self.assertNotContains( + response=response, text=self.test_role, status_code=200 + ) + + self.test_group.refresh_from_db() + self.assertTrue( + self.test_role in self.test_group.roles.all() + ) + + def test_group_role_remove_view_with_role_access(self): + self._create_test_group() + self._create_test_role() + self.test_group.roles.add(self.test_role) + + self.grant_access( + obj=self.test_role, permission=permission_role_edit + ) + + response = self._request_group_roles_remove_view() + self.assertNotContains( + response=response, text=self.test_group, status_code=404 + ) + self.assertNotContains( + response=response, text=self.test_role, status_code=404 + ) + + self.test_group.refresh_from_db() + self.assertTrue( + self.test_role in self.test_group.roles.all() + ) + + def test_group_role_remove_view_with_full_access(self): + self._create_test_group() + self._create_test_role() + self.test_group.roles.add(self.test_role) + + self.grant_access( + obj=self.test_role, permission=permission_role_edit + ) + self.grant_access( + obj=self.test_group, permission=permission_group_edit + ) + + response = self._request_group_roles_remove_view() + self.assertEqual(response.status_code, 302) + + self.test_group.refresh_from_db() + self.assertTrue( + self.test_role not in self.test_group.roles.all() + ) diff --git a/mayan/apps/permissions/urls.py b/mayan/apps/permissions/urls.py index 89e1d4b7f6..4e1dd44d86 100644 --- a/mayan/apps/permissions/urls.py +++ b/mayan/apps/permissions/urls.py @@ -2,7 +2,9 @@ from __future__ import unicode_literals from django.conf.urls import url -from .api_views import APIPermissionList, APIRoleListView, APIRoleView +from .api_views import ( + PermissionNamespaceViewSet, PermissionViewSet, RoleAPIViewSet +) from .views import ( GroupRolesView, RoleCreateView, RoleDeleteView, RoleEditView, RoleGroupsView, RoleListView, RolePermissionsView @@ -36,14 +38,14 @@ urlpatterns = [ url(regex=r'^roles/list/$', name='role_list', view=RoleListView.as_view()), ] -api_urls = [ - url( - regex=r'^permissions/$', name='permission-list', - view=APIPermissionList.as_view(), - ), - url(regex=r'^roles/$', name='role-list', view=APIRoleListView.as_view()), - url( - regex=r'^roles/(?P[0-9]+)/$', name='role-detail', - view=APIRoleView.as_view() - ), -] +api_router_entries = ( + { + 'prefix': r'permission_namespaces', 'viewset': PermissionNamespaceViewSet, + 'basename': 'permission_namespace' + }, + { + 'prefix': r'permission_namespaces/(?P[^/.]+)/permissions', + 'viewset': PermissionViewSet, 'basename': 'permission' + }, + {'prefix': r'roles', 'viewset': RoleAPIViewSet, 'basename': 'role'}, +) diff --git a/mayan/apps/permissions/views.py b/mayan/apps/permissions/views.py index 06c63c04dc..69e302a77f 100644 --- a/mayan/apps/permissions/views.py +++ b/mayan/apps/permissions/views.py @@ -1,22 +1,17 @@ from __future__ import unicode_literals -import itertools - from django.contrib.auth.models import Group -from django.shortcuts import get_object_or_404 from django.template import RequestContext from django.urls import reverse_lazy from django.utils.encoding import force_text from django.utils.translation import ugettext_lazy as _ -from mayan.apps.acls.models import AccessControlList from mayan.apps.common.generics import ( - AssignRemoveView, SingleObjectCreateView, SingleObjectDeleteView, + AddRemoveView, SingleObjectCreateView, SingleObjectDeleteView, SingleObjectEditView, SingleObjectListView ) from mayan.apps.user_management.permissions import permission_group_edit -from .classes import Permission, PermissionNamespace from .icons import icon_role_list from .links import link_role_create from .models import Role, StoredPermission @@ -26,45 +21,33 @@ from .permissions import ( ) -class GroupRolesView(AssignRemoveView): - grouped = False - left_list_title = _('Available roles') - right_list_title = _('Group roles') - object_permission = permission_group_edit +class GroupRolesView(AddRemoveView): + action_add_method = 'roles_add' + action_remove_method = 'roles_remove' + main_object_model = Group + main_object_permission = permission_group_edit + main_object_pk_url_kwarg = 'group_id' + secondary_object_model = Role + secondary_object_permission = permission_role_edit + list_available_title = _('Available roles') + list_added_title = _('Group roles') + related_field = 'roles' - def add(self, item): - role = get_object_or_404(klass=Role, pk=item) - self.get_object().roles.add(role) + def get_actions_extra_kwargs(self): + return {'_user': self.request.user} def get_extra_context(self): return { - 'object': self.get_object(), - 'title': _('Roles of group: %s') % self.get_object() + 'object': self.main_object, + 'title': _('Roles of group: %s') % self.main_object, } - def get_object(self): - return get_object_or_404(klass=Group, pk=self.kwargs['group_id']) - - def left_list(self): - return [ - (force_text(role.pk), role.label) for role in set(Role.objects.all()) - set(self.get_object().roles.all()) - ] - - def right_list(self): - return [ - (force_text(role.pk), role.label) for role in self.get_object().roles.all() - ] - - def remove(self, item): - role = get_object_or_404(klass=Role, pk=item) - self.get_object().roles.remove(role) - class RoleCreateView(SingleObjectCreateView): fields = ('label',) model = Role - view_permission = permission_role_create post_action_redirect = reverse_lazy(viewname='permissions:role_list') + view_permission = permission_role_create class RoleDeleteView(SingleObjectDeleteView): @@ -81,43 +64,31 @@ class RoleEditView(SingleObjectEditView): pk_url_kwarg = 'role_id' -class RoleGroupsView(AssignRemoveView): - grouped = False - left_list_title = _('Available groups') - right_list_title = _('Role groups') - object_permission = permission_role_edit +class RoleGroupsView(AddRemoveView): + action_add_method = 'groups_add' + action_remove_method = 'groups_remove' + main_object_model = Role + main_object_permission = permission_role_edit + main_object_pk_url_kwarg = 'role_id' + secondary_object_model = Group + secondary_object_permission = permission_group_edit + list_available_title = _('Available groups') + list_added_title = _('Role groups') + related_field = 'groups' - def add(self, item): - group = get_object_or_404(klass=Group, pk=item) - self.get_object().groups.add(group) + def get_actions_extra_kwargs(self): + return {'_user': self.request.user} def get_extra_context(self): return { - 'object': self.get_object(), - 'title': _('Groups of role: %s') % self.get_object(), + 'object': self.main_object, + 'title': _('Groups of role: %s') % self.main_object, 'subtitle': _( 'Add groups to be part of a role. They will ' 'inherit the role\'s permissions and access controls.' ), } - def get_object(self): - return get_object_or_404(klass=Role, pk=self.kwargs['role_id']) - - def left_list(self): - return [ - (force_text(group.pk), group.name) for group in set(Group.objects.all()) - set(self.get_object().groups.all()) - ] - - def remove(self, item): - group = get_object_or_404(klass=Group, pk=item) - self.get_object().groups.remove(group) - - def right_list(self): - return [ - (force_text(group.pk), group.name) for group in self.get_object().groups.all() - ] - class RoleListView(SingleObjectListView): model = Role @@ -143,64 +114,48 @@ class RoleListView(SingleObjectListView): } -class RolePermissionsView(AssignRemoveView): +class RolePermissionsView(AddRemoveView): + action_add_method = 'permissions_add' + action_remove_method = 'permissions_remove' grouped = True - left_list_title = _('Available permissions') - object_permission = permission_role_edit - right_list_title = _('Granted permissions') + main_object_model = Role + main_object_permission = permission_role_edit + main_object_pk_url_kwarg = 'role_id' + list_available_title = _('Available permissions') + list_added_title = _('Granted permissions') + related_field = 'permissions' + secondary_object_model = StoredPermission - @staticmethod - def generate_choices(entries): - results = [] + def generate_choices(self, queryset): + namespaces_dictionary = {} - entries = sorted( - entries, key=lambda x: ( - x.volatile_permission.namespace.label, - x.volatile_permission.label - ) + # Sort permissions by their translatable label + object_list = sorted( + queryset, key=lambda permission: permission.volatile_permission.label ) - for namespace, permissions in itertools.groupby(entries, lambda entry: entry.namespace): - permission_options = [ - (force_text(permission.pk), permission) for permission in permissions - ] - results.append( - (PermissionNamespace.get(name=namespace), permission_options) + # Group permissions by namespace + for permission in object_list: + namespaces_dictionary.setdefault( + permission.volatile_permission.namespace.label, + [] + ) + namespaces_dictionary[permission.volatile_permission.namespace.label].append( + (permission.pk, force_text(permission)) ) - return results + # Sort permissions by their translatable namespace label + return sorted(namespaces_dictionary.items()) - def add(self, item): - permission = get_object_or_404(klass=StoredPermission, pk=item) - self.get_object().permissions.add(permission) + def get_actions_extra_kwargs(self): + return {'_user': self.request.user} def get_extra_context(self): return { - 'object': self.get_object(), + 'object': self.main_object, 'subtitle': _( 'Permissions granted here will apply to the entire system ' 'and all objects.' ), - 'title': _('Permissions for role: %s') % self.get_object(), + 'title': _('Permissions for role: %s') % self.main_object, } - - def get_object(self): - return get_object_or_404(klass=Role, pk=self.kwargs['role_id']) - - def left_list(self): - Permission.refresh() - - return RolePermissionsView.generate_choices( - entries=StoredPermission.objects.exclude( - id__in=self.get_object().permissions.values_list('pk', flat=True) - ) - ) - - def remove(self, item): - permission = get_object_or_404(klass=StoredPermission, pk=item) - self.get_object().permissions.remove(permission) - - def right_list(self): - return RolePermissionsView.generate_choices( - entries=self.get_object().permissions.all() - )