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() - )