diff --git a/mayan/apps/acls/api_views.py b/mayan/apps/acls/api_views.py index d23df29e0c..989d147717 100644 --- a/mayan/apps/acls/api_views.py +++ b/mayan/apps/acls/api_views.py @@ -2,6 +2,7 @@ from __future__ import absolute_import, unicode_literals from django.contrib.contenttypes.models import ContentType from django.shortcuts import get_object_or_404 + from rest_framework import generics from .models import AccessControlList @@ -25,7 +26,7 @@ class APIObjectACLListView(generics.ListCreateAPIView): ) content_object = get_object_or_404( - klass=content_type.model_class(), pk=self.kwargs['object_pk'] + klass=content_type.model_class(), pk=self.kwargs['object_id'] ) if self.request.method == 'GET': @@ -89,7 +90,7 @@ class APIObjectACLView(generics.RetrieveDestroyAPIView): ) content_object = get_object_or_404( - klass=content_type.model_class(), pk=self.kwargs['object_pk'] + klass=content_type.model_class(), pk=self.kwargs['object_id'] ) AccessControlList.objects.check_access( @@ -110,7 +111,7 @@ class APIObjectACLPermissionListView(generics.ListCreateAPIView): """ def get_acl(self): return get_object_or_404( - klass=self.get_content_object().acls, pk=self.kwargs['pk'] + klass=self.get_content_object().acls, pk=self.kwargs['acl_pk'] ) def get_content_object(self): @@ -120,7 +121,7 @@ class APIObjectACLPermissionListView(generics.ListCreateAPIView): ) content_object = get_object_or_404( - klass=content_type.model_class(), pk=self.kwargs['object_pk'] + klass=content_type.model_class(), pk=self.kwargs['object_id'] ) AccessControlList.objects.check_access( @@ -167,7 +168,7 @@ class APIObjectACLPermissionView(generics.RetrieveDestroyAPIView): def get_acl(self): return get_object_or_404( - klass=self.get_content_object().acls, pk=self.kwargs['pk'] + klass=self.get_content_object().acls, pk=self.kwargs['acl_pk'] ) def get_content_object(self): @@ -177,7 +178,7 @@ class APIObjectACLPermissionView(generics.RetrieveDestroyAPIView): ) content_object = get_object_or_404( - klass=content_type.model_class(), pk=self.kwargs['object_pk'] + klass=content_type.model_class(), pk=self.kwargs['object_id'] ) AccessControlList.objects.check_access( diff --git a/mayan/apps/acls/apps.py b/mayan/apps/acls/apps.py index 73ac37d443..250fc45e33 100644 --- a/mayan/apps/acls/apps.py +++ b/mayan/apps/acls/apps.py @@ -19,14 +19,15 @@ class ACLsApp(MayanAppConfig): def ready(self): super(ACLsApp, self).ready() - AccessControlList = self.get_model('AccessControlList') + AccessControlList = self.get_model(model_name='AccessControlList') SourceColumn( - source=AccessControlList, label=_('Role'), attribute='role' + attribute='role', is_identifier=True, is_sortable=True, + source=AccessControlList ) SourceColumn( - source=AccessControlList, label=_('Permissions'), - attribute='get_permission_titles' + attribute='get_permission_titles', include_label=True, + source=AccessControlList ) menu_object.bind_links( diff --git a/mayan/apps/acls/forms.py b/mayan/apps/acls/forms.py new file mode 100644 index 0000000000..d318d96751 --- /dev/null +++ b/mayan/apps/acls/forms.py @@ -0,0 +1,13 @@ +from __future__ import unicode_literals + +from django import forms + +from mayan.apps.common.forms import FilteredSelectionForm + +from .models import AccessControlList + + +class ACLCreateForm(FilteredSelectionForm, forms.ModelForm): + class Meta: + fields = ('role',) + model = AccessControlList diff --git a/mayan/apps/acls/icons.py b/mayan/apps/acls/icons.py index e812a0024d..00cda1f009 100644 --- a/mayan/apps/acls/icons.py +++ b/mayan/apps/acls/icons.py @@ -2,6 +2,7 @@ from __future__ import absolute_import, unicode_literals from mayan.apps.appearance.classes import Icon +icon_acl_delete = Icon(driver_name='fontawesome', symbol='minus') icon_acl_list = Icon(driver_name='fontawesome', symbol='lock') icon_acl_new = Icon( driver_name='fontawesome-dual', primary_symbol='lock', diff --git a/mayan/apps/acls/links.py b/mayan/apps/acls/links.py index 2cd93a42b0..da2f289e10 100644 --- a/mayan/apps/acls/links.py +++ b/mayan/apps/acls/links.py @@ -4,8 +4,9 @@ from django.apps import apps from django.utils.translation import ugettext_lazy as _ from mayan.apps.navigation import Link +from mayan.apps.permissions.icons import icon_permission -from .icons import icon_acl_list, icon_acl_new +from .icons import icon_acl_delete, icon_acl_list, icon_acl_new from .permissions import permission_acl_edit, permission_acl_view @@ -28,9 +29,9 @@ def get_kwargs_factory(variable_name): link_acl_delete = Link( - args='resolved_object.pk', permissions=(permission_acl_edit,), - permissions_related='content_object', tags='dangerous', text=_('Delete'), - view='acls:acl_delete', + args='resolved_object.pk', icon_class=icon_acl_delete, + permissions=(permission_acl_edit,), permissions_related='content_object', + tags='dangerous', text=_('Delete'), view='acls:acl_delete', ) link_acl_list = Link( icon_class=icon_acl_list, kwargs=get_kwargs_factory('resolved_object'), @@ -42,7 +43,7 @@ link_acl_create = Link( view='acls:acl_create' ) link_acl_permissions = Link( - args='resolved_object.pk', permissions=(permission_acl_edit,), - permissions_related='content_object', text=_('Permissions'), - view='acls:acl_permissions', + args='resolved_object.pk', icon_class=icon_permission, + permissions=(permission_acl_edit,), permissions_related='content_object', + text=_('Permissions'), view='acls:acl_permissions', ) diff --git a/mayan/apps/acls/managers.py b/mayan/apps/acls/managers.py index 40a44f653b..7b2aae92bf 100644 --- a/mayan/apps/acls/managers.py +++ b/mayan/apps/acls/managers.py @@ -55,7 +55,9 @@ class AccessControlListManager(models.Manager): except AttributeError: # AttributeError means non model objects: ie Statistics # These can't have ACLs so we raise PermissionDenied - raise PermissionDenied(_('Insufficient access for: %s') % obj) + raise PermissionDenied( + _('Insufficient access for: %(object)s') % {'object': obj} + ) except KeyError: pass else: @@ -89,7 +91,7 @@ class AccessControlListManager(models.Manager): user_roles.append(role) - if not self.filter(content_type=ContentType.objects.get_for_model(obj), object_id=obj.pk, permissions__in=stored_permissions, role__in=user_roles).exists(): + if not self.filter(content_type=ContentType.objects.get_for_model(model=obj), object_id=obj.pk, permissions__in=stored_permissions, role__in=user_roles).exists(): logger.debug( 'Permissions "%s" on "%s" denied for user "%s"', permissions, obj, user @@ -104,8 +106,8 @@ class AccessControlListManager(models.Manager): def filter_by_access(self, permission, user, queryset): if user.is_superuser or user.is_staff: logger.debug( - 'Unfiltered queryset returned to user "%s" as superuser or staff', - user + 'Unfiltered queryset returned to user "%s" as superuser ' + 'or staff', user ) return queryset @@ -138,10 +140,11 @@ class AccessControlListManager(models.Manager): except TypeError: # Is not a function, try it as a field parent_content_type = ContentType.objects.get_for_model( - parent_object + model=parent_object ) parent_queryset = self.filter( - content_type=parent_content_type, role__in=user_roles, + content_type=parent_content_type, + role__in=user_roles, permissions=permission.stored_permission ) parent_acl_query = Q( @@ -159,7 +162,10 @@ class AccessControlListManager(models.Manager): result = [] for entry in queryset: try: - self.check_access(permissions=permission, user=user, obj=entry) + self.check_access( + obj=entry, permissions=permission, + user=user + ) except PermissionDenied: pass else: @@ -170,7 +176,9 @@ class AccessControlListManager(models.Manager): parent_acl_query = Q() # Directly granted access - content_type = ContentType.objects.get_for_model(queryset.model) + content_type = ContentType.objects.get_for_model( + model=queryset.model + ) acl_query = Q(pk__in=self.filter( content_type=content_type, role__in=user_roles, permissions=permission.stored_permission @@ -194,7 +202,9 @@ class AccessControlListManager(models.Manager): return StoredPermission.objects.none() try: - parent_accessor = ModelPermission.get_inheritance(type(instance)) + parent_accessor = ModelPermission.get_inheritance( + model=type(instance) + ) except KeyError: return StoredPermission.objects.none() else: @@ -230,6 +240,8 @@ class AccessControlListManager(models.Manager): acl.permissions.add(permission.stored_permission) + return acl + def revoke(self, permission, role, obj): content_type = ContentType.objects.get_for_model(model=obj) acl, created = self.get_or_create( diff --git a/mayan/apps/acls/models.py b/mayan/apps/acls/models.py index 490f0991c4..e93f00f486 100644 --- a/mayan/apps/acls/models.py +++ b/mayan/apps/acls/models.py @@ -5,6 +5,7 @@ import logging from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models +from django.urls import reverse from django.utils.encoding import force_text, python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ @@ -64,6 +65,11 @@ class AccessControlList(models.Model): 'role': self.role } + def get_absolute_url(self): + return reverse( + viewname='acls:acl_permissions', kwargs={'acl_pk': self.pk} + ) + def get_inherited_permissions(self): return AccessControlList.objects.get_inherited_permissions( role=self.role, obj=self.content_object @@ -78,3 +84,4 @@ class AccessControlList(models.Model): ) return result or _('None') + get_permission_titles.short_description = _('Permissions') diff --git a/mayan/apps/acls/permissions.py b/mayan/apps/acls/permissions.py index 0d4bda0a49..cd4bd07446 100644 --- a/mayan/apps/acls/permissions.py +++ b/mayan/apps/acls/permissions.py @@ -7,8 +7,8 @@ from mayan.apps.permissions import PermissionNamespace namespace = PermissionNamespace(label=_('Access control lists'), name='acls') permission_acl_edit = namespace.add_permission( - name='acl_edit', label=_('Edit ACLs') + label=_('Edit ACLs'), name='acl_edit' ) permission_acl_view = namespace.add_permission( - name='acl_view', label=_('View ACLs') + label=_('View ACLs'), name='acl_view' ) diff --git a/mayan/apps/acls/serializers.py b/mayan/apps/acls/serializers.py index 87e41036d5..5a8a02157b 100644 --- a/mayan/apps/acls/serializers.py +++ b/mayan/apps/acls/serializers.py @@ -38,18 +38,22 @@ class AccessControlListSerializer(serializers.ModelSerializer): def get_permissions_url(self, instance): return reverse( - 'rest_api:accesscontrollist-permission-list', args=( - instance.content_type.app_label, instance.content_type.model, - instance.object_id, instance.pk - ), request=self.context['request'], format=self.context['format'] + viewname='rest_api:accesscontrollist-permission-list', kwargs={ + 'app_label': instance.content_type.app_label, + 'model': instance.content_type.model, + 'object_id': instance.object_id, + 'acl_pk': instance.pk + }, request=self.context['request'], format=self.context['format'] ) def get_url(self, instance): return reverse( - 'rest_api:accesscontrollist-detail', args=( - instance.content_type.app_label, instance.content_type.model, - instance.object_id, instance.pk - ), request=self.context['request'], format=self.context['format'] + 'rest_api:accesscontrollist-detail', kwargs={ + 'app_label': instance.content_type.app_label, + 'model': instance.content_type.model, + 'object_id': instance.object_id, + 'acl_pk': instance.pk + }, request=self.context['request'], format=self.context['format'] ) @@ -65,21 +69,23 @@ class AccessControlListPermissionSerializer(PermissionSerializer): def get_acl_permission_url(self, instance): return reverse( - 'rest_api:accesscontrollist-permission-detail', args=( - self.context['acl'].content_type.app_label, - self.context['acl'].content_type.model, - self.context['acl'].object_id, self.context['acl'].pk, - instance.stored_permission.pk - ), request=self.context['request'], format=self.context['format'] + 'rest_api:accesscontrollist-permission-detail', kwargs={ + 'app_label': self.context['acl'].content_type.app_label, + 'model': self.context['acl'].content_type.model, + 'object_id': self.context['acl'].object_id, + 'acl_pk': self.context['acl'].pk, + 'permission_pk': instance.stored_permission.pk + }, request=self.context['request'], format=self.context['format'] ) def get_acl_url(self, instance): return reverse( - 'rest_api:accesscontrollist-detail', args=( - self.context['acl'].content_type.app_label, - self.context['acl'].content_type.model, - self.context['acl'].object_id, self.context['acl'].pk - ), request=self.context['request'], format=self.context['format'] + 'rest_api:accesscontrollist-detail', kwargs={ + 'app_label': self.context['acl'].content_type.app_label, + 'model': self.context['acl'].content_type.model, + 'object_id': self.context['acl'].object_id, + 'acl_pk': self.context['acl'].pk + }, request=self.context['request'], format=self.context['format'] ) @@ -153,18 +159,22 @@ class WritableAccessControlListSerializer(serializers.ModelSerializer): def get_permissions_url(self, instance): return reverse( - 'rest_api:accesscontrollist-permission-list', args=( - instance.content_type.app_label, instance.content_type.model, - instance.object_id, instance.pk - ), request=self.context['request'], format=self.context['format'] + 'rest_api:accesscontrollist-permission-list', kwargs={ + 'app_label': instance.content_type.app_label, + 'model': instance.content_type.model, + 'object_id': instance.object_id, + 'acl_pk': instance.pk + }, request=self.context['request'], format=self.context['format'] ) def get_url(self, instance): return reverse( - 'rest_api:accesscontrollist-detail', args=( - instance.content_type.app_label, instance.content_type.model, - instance.object_id, instance.pk - ), request=self.context['request'], format=self.context['format'] + 'rest_api:accesscontrollist-detail', kwargs={ + 'app_label': instance.content_type.app_label, + 'model': instance.content_type.model, + 'object_id': instance.object_id, + 'acl_pk': instance.pk + }, request=self.context['request'], format=self.context['format'] ) def validate(self, attrs): diff --git a/mayan/apps/acls/tests/test_api.py b/mayan/apps/acls/tests/test_api.py index bab454823b..55433b2fb3 100644 --- a/mayan/apps/acls/tests/test_api.py +++ b/mayan/apps/acls/tests/test_api.py @@ -35,13 +35,12 @@ class ACLAPITestCase(DocumentTestMixin, BaseAPITestCase): response = self.get( viewname='rest_api:accesscontrollist-list', - args=( - self.document_content_type.app_label, - self.document_content_type.model, - self.document.pk - ) + kwargs={ + 'app_label': self.document_content_type.app_label, + 'model': self.document_content_type.model, + 'object_id': self.document.pk + } ) - self.assertEqual( response.data['results'][0]['content_type']['app_label'], self.document_content_type.app_label @@ -55,11 +54,12 @@ class ACLAPITestCase(DocumentTestMixin, BaseAPITestCase): response = self.delete( viewname='rest_api:accesscontrollist-detail', - args=( - self.document_content_type.app_label, - self.document_content_type.model, - self.document.pk, self.acl.pk - ) + kwargs={ + 'app_label': self.document_content_type.app_label, + 'model': self.document_content_type.model, + 'object_id': self.document.pk, + 'acl_pk': self.acl.pk + } ) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -70,11 +70,12 @@ class ACLAPITestCase(DocumentTestMixin, BaseAPITestCase): response = self.get( viewname='rest_api:accesscontrollist-detail', - args=( - self.document_content_type.app_label, - self.document_content_type.model, - self.document.pk, self.acl.pk - ) + kwargs={ + 'app_label': self.document_content_type.app_label, + 'model': self.document_content_type.model, + 'object_id': self.document.pk, + 'acl_pk': self.acl.pk + } ) self.assertEqual( response.data['content_type']['app_label'], @@ -90,12 +91,12 @@ class ACLAPITestCase(DocumentTestMixin, BaseAPITestCase): response = self.delete( viewname='rest_api:accesscontrollist-permission-detail', - args=( - self.document_content_type.app_label, - self.document_content_type.model, - self.document.pk, self.acl.pk, - permission.pk - ) + kwargs={ + 'app_label': self.document_content_type.app_label, + 'model': self.document_content_type.model, + 'object_id': self.document.pk, + 'acl_pk': self.acl.pk, 'permission_pk': permission.pk + } ) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertEqual(self.acl.permissions.count(), 0) @@ -106,16 +107,16 @@ class ACLAPITestCase(DocumentTestMixin, BaseAPITestCase): response = self.get( viewname='rest_api:accesscontrollist-permission-detail', - args=( - self.document_content_type.app_label, - self.document_content_type.model, - self.document.pk, self.acl.pk, - permission.pk - ) + kwargs={ + 'app_label': self.document_content_type.app_label, + 'model': self.document_content_type.model, + 'object_id': self.document.pk, 'acl_pk': self.acl.pk, + 'permission_pk': permission.pk + } ) self.assertEqual( - response.data['pk'], permission_document_view.pk + response.data['permission_pk'], permission_document_view.pk ) def test_object_acl_permission_list_view(self): @@ -123,15 +124,16 @@ class ACLAPITestCase(DocumentTestMixin, BaseAPITestCase): response = self.get( viewname='rest_api:accesscontrollist-permission-list', - args=( - self.document_content_type.app_label, - self.document_content_type.model, - self.document.pk, self.acl.pk - ) + kwargs={ + 'app_label': self.document_content_type.app_label, + 'model': self.document_content_type.model, + 'object_id': self.document.pk, + 'acl_pk': self.acl.pk + } ) self.assertEqual( - response.data['results'][0]['pk'], + response.data['results'][0]['permission_pk'], permission_document_view.pk ) @@ -140,11 +142,11 @@ class ACLAPITestCase(DocumentTestMixin, BaseAPITestCase): response = self.post( viewname='rest_api:accesscontrollist-permission-list', - args=( - self.document_content_type.app_label, - self.document_content_type.model, - self.document.pk, self.acl.pk - ), data={'permission_pk': permission_acl_view.pk} + kwargs={ + 'app_label': self.document_content_type.app_label, + 'model': self.document_content_type.model, + 'object_id': self.document.pk, 'acl_pk': self.acl.pk + }, data={'permission_pk': permission_acl_view.pk} ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) @@ -158,11 +160,11 @@ class ACLAPITestCase(DocumentTestMixin, BaseAPITestCase): def test_object_acl_post_no_permissions_added_view(self): response = self.post( viewname='rest_api:accesscontrollist-list', - args=( - self.document_content_type.app_label, - self.document_content_type.model, - self.document.pk - ), data={'role_pk': self.role.pk} + kwargs={ + 'app_label': self.document_content_type.app_label, + 'model': self.document_content_type.model, + 'object_id': self.document.pk + }, data={'role_pk': self.role.pk} ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) @@ -179,11 +181,11 @@ class ACLAPITestCase(DocumentTestMixin, BaseAPITestCase): def test_object_acl_post_with_permissions_added_view(self): response = self.post( viewname='rest_api:accesscontrollist-list', - args=( - self.document_content_type.app_label, - self.document_content_type.model, - self.document.pk - ), data={ + kwargs={ + 'app_label': self.document_content_type.app_label, + 'model': self.document_content_type.model, + 'object_id': self.document.pk + }, data={ 'role_pk': self.role.pk, 'permissions_pk_list': permission_acl_view.pk diff --git a/mayan/apps/acls/tests/test_links.py b/mayan/apps/acls/tests/test_links.py index 821194ce03..231ad6e78d 100644 --- a/mayan/apps/acls/tests/test_links.py +++ b/mayan/apps/acls/tests/test_links.py @@ -35,7 +35,7 @@ class ACLsLinksTestCase(GenericDocumentViewTestCase): } self.assertEqual( - resolved_link.url, reverse('acls:acl_create', kwargs=kwargs) + resolved_link.url, reverse(viewname='acls:acl_create', kwargs=kwargs) ) def test_document_acl_delete_link(self): @@ -53,7 +53,7 @@ class ACLsLinksTestCase(GenericDocumentViewTestCase): self.assertNotEqual(resolved_link, None) self.assertEqual( - resolved_link.url, reverse('acls:acl_delete', args=(acl.pk,)) + resolved_link.url, reverse(viewname='acls:acl_delete', kwargs={'acl_pk': acl.pk}) ) def test_document_acl_edit_link(self): @@ -71,7 +71,7 @@ class ACLsLinksTestCase(GenericDocumentViewTestCase): self.assertNotEqual(resolved_link, None) self.assertEqual( - resolved_link.url, reverse('acls:acl_permissions', args=(acl.pk,)) + resolved_link.url, reverse(viewname='acls:acl_permissions', kwargs={'acl_pk': acl.pk}) ) def test_document_acl_list_link(self): @@ -96,5 +96,5 @@ class ACLsLinksTestCase(GenericDocumentViewTestCase): } self.assertEqual( - resolved_link.url, reverse('acls:acl_list', kwargs=kwargs) + resolved_link.url, reverse(viewname='acls:acl_list', kwargs=kwargs) ) diff --git a/mayan/apps/acls/tests/test_views.py b/mayan/apps/acls/tests/test_views.py index 38f98f0d5d..05a839f298 100644 --- a/mayan/apps/acls/tests/test_views.py +++ b/mayan/apps/acls/tests/test_views.py @@ -1,191 +1,178 @@ from __future__ import absolute_import, unicode_literals from django.contrib.contenttypes.models import ContentType +from django.utils.encoding import force_text from mayan.apps.documents.tests import GenericDocumentViewTestCase +from mayan.apps.permissions.tests.mixins import RoleTestMixin from ..models import AccessControlList from ..permissions import permission_acl_edit, permission_acl_view -class AccessControlListViewTestCase(GenericDocumentViewTestCase): +class AccessControlListViewTestCase(RoleTestMixin, GenericDocumentViewTestCase): def setUp(self): super(AccessControlListViewTestCase, self).setUp() + self.login_user() + self._create_test_role() - content_type = ContentType.objects.get_for_model(self.document) + self.test_object = self.document - self.view_arguments = { + content_type = ContentType.objects.get_for_model(self.test_object) + + self.view_content_object_arguments = { 'app_label': content_type.app_label, 'model': content_type.model, - 'object_id': self.document.pk + 'object_id': self.test_object.pk } - def test_acl_create_view_no_permission(self): - self.login_user() - - response = self.get( - viewname='acls:acl_create', kwargs=self.view_arguments, data={ - 'role': self.role.pk + def _request_get_acl_create_view(self): + return self.get( + viewname='acls:acl_create', + kwargs=self.view_content_object_arguments, data={ + 'role': self.test_role.pk } ) - self.assertEquals(response.status_code, 403) + def test_acl_create_view_get_no_permission(self): + response = self._request_get_acl_create_view() + + self.assertEqual(response.status_code, 404) self.assertEqual(AccessControlList.objects.count(), 0) - def test_acl_create_view_with_permission(self): - self.login_user() + def test_acl_create_view_get_with_document_access(self): + self.grant_access(obj=self.test_object, permission=permission_acl_edit) - self.role.permissions.add( - permission_acl_edit.stored_permission - ) - - response = self.get( - viewname='acls:acl_create', kwargs=self.view_arguments, data={ - 'role': self.role.pk - }, follow=True - ) + response = self._request_get_acl_create_view() self.assertContains( - response, text=self.document.label, status_code=200 + response=response, text=force_text(self.test_object), + status_code=200 + ) + + def _request_post_acl_create_view(self): + return self.post( + viewname='acls:acl_create', + kwargs=self.view_content_object_arguments, data={ + 'role': self.test_role.pk + } ) def test_acl_create_view_post_no_permission(self): - self.login_user() + response = self._request_post_acl_create_view() - response = self.post( - viewname='acls:acl_create', kwargs=self.view_arguments, data={ - 'role': self.role.pk - } - ) - - self.assertEquals(response.status_code, 403) + self.assertEqual(response.status_code, 404) self.assertEqual(AccessControlList.objects.count(), 0) - def test_acl_create_view_with_post_permission(self): - self.login_user() + def test_acl_create_view_post_with_document_access(self): + self.grant_access(obj=self.test_object, permission=permission_acl_edit) + response = self._request_post_acl_create_view() - self.role.permissions.add( - permission_acl_edit.stored_permission - ) - - response = self.post( - viewname='acls:acl_create', kwargs=self.view_arguments, data={ - 'role': self.role.pk - }, follow=True - ) - - self.assertContains(response, text='created', status_code=200) - self.assertEqual(AccessControlList.objects.count(), 1) + self.assertEqual(response.status_code, 302) + # 2 ACLs: 1 created by the test and the other by the self.grant_access + self.assertEqual(AccessControlList.objects.count(), 2) def test_acl_create_duplicate_view_with_permission(self): """ Test creating a duplicate ACL entry: same object & role Result: Should redirect to existing ACL for object + role combination """ - acl = AccessControlList.objects.create( - content_object=self.document, role=self.role + self._create_test_acl() + + self.grant_access(obj=self.test_object, permission=permission_acl_edit) + + response = self._request_post_acl_create_view() + self.assertNotContains( + response=response, text=force_text(self.test_acl.role), + status_code=200 ) - self.login_user() - - self.role.permissions.add( - permission_acl_edit.stored_permission + self.assertEqual(AccessControlList.objects.count(), 2) + self.assertEqual( + AccessControlList.objects.first().pk, self.test_acl.pk ) - response = self.post( - viewname='acls:acl_create', kwargs=self.view_arguments, data={ - 'role': self.role.pk - }, follow=True + def _create_test_acl(self): + self.test_acl = AccessControlList.objects.create( + content_object=self.test_object, role=self.test_role ) - self.assertContains( - response, text='vailable permissions', status_code=200 - ) - self.assertEqual(AccessControlList.objects.count(), 1) - self.assertEqual(AccessControlList.objects.first().pk, acl.pk) - - def test_orphan_acl_create_view_with_permission(self): - """ - Test creating an ACL entry for an object with no model permissions. - Result: Should display a blank permissions list (not optgroup) - """ - self.login_user() - - self.role.permissions.add( - permission_acl_edit.stored_permission + def _request_acl_delete_view(self): + return self.post( + viewname='acls:acl_delete', kwargs={'acl_pk': self.test_acl.pk} ) - recent_entry = self.document.add_as_recent_document_for_user(self.user) + def test_acl_delete_view_no_permission(self): + self._create_test_acl() - content_type = ContentType.objects.get_for_model(recent_entry) - - view_arguments = { - 'app_label': content_type.app_label, - 'model': content_type.model, - 'object_id': recent_entry.pk - } - - response = self.post( - viewname='acls:acl_create', kwargs=view_arguments, data={ - 'role': self.role.pk - }, follow=True + response = self._request_acl_delete_view() + self.assertNotContains( + response=response, text=force_text(self.test_object), + status_code=404 + ) + # 1 ACL: the test one + self.assertQuerysetEqual( + qs=AccessControlList.objects.all(), values=(repr(self.test_acl),) ) - self.assertNotContains(response, text='optgroup', status_code=200) - self.assertEqual(AccessControlList.objects.count(), 1) + def test_acl_delete_view_with_access(self): + self._create_test_acl() + + acl = self.grant_access( + obj=self.test_object, permission=permission_acl_edit + ) + response = self._request_acl_delete_view() + self.assertEqual(response.status_code, 302) + # 1 ACL: the one created by the self.grant_access + self.assertQuerysetEqual( + qs=AccessControlList.objects.all(), values=(repr(acl),) + ) + + def _request_acl_list_view(self): + return self.get( + viewname='acls:acl_list', kwargs=self.view_content_object_arguments + ) def test_acl_list_view_no_permission(self): - self.login_user() + response = self._request_acl_list_view() - document = self.document.add_as_recent_document_for_user( - self.user - ).document - - acl = AccessControlList.objects.create( - content_object=document, role=self.role - ) - acl.permissions.add(permission_acl_edit.stored_permission) - - content_type = ContentType.objects.get_for_model(document) - - view_arguments = { - 'app_label': content_type.app_label, - 'model': content_type.model, - 'object_id': document.pk - } - - response = self.get( - viewname='acls:acl_list', kwargs=view_arguments + self.assertNotContains( + response=response, text=force_text(self.test_object), + status_code=404 ) - self.assertNotContains(response, text=document.label, status_code=403) - self.assertNotContains(response, text='otal: 1', status_code=403) + def test_acl_list_view_with_access(self): + self.grant_access(obj=self.test_object, permission=permission_acl_view) - def test_acl_list_view_with_permission(self): - self.login_user() + response = self._request_acl_list_view() - self.role.permissions.add( - permission_acl_view.stored_permission + self.assertContains( + response=response, text=force_text(self.test_object), + status_code=200 ) - document = self.document.add_as_recent_document_for_user( - self.user - ).document - - acl = AccessControlList.objects.create( - content_object=document, role=self.role + def _request_get_acl_permissions_view(self): + return self.get( + viewname='acls:acl_permissions', + kwargs={'acl_pk': self.test_acl.pk} ) - acl.permissions.add(permission_acl_view.stored_permission) - content_type = ContentType.objects.get_for_model(document) + def test_acl_permissions_view_get_no_permission(self): + self._create_test_acl() - view_arguments = { - 'app_label': content_type.app_label, - 'model': content_type.model, - 'object_id': document.pk - } - - response = self.get( - viewname='acls:acl_list', kwargs=view_arguments + response = self._request_get_acl_permissions_view() + self.assertNotContains( + response=response, text=force_text(self.test_object), + status_code=404 + ) + + def test_acl_permissions_view_get_with_access(self): + self._create_test_acl() + + self.grant_access(obj=self.test_object, permission=permission_acl_edit) + + response = self._request_get_acl_permissions_view() + self.assertContains( + response=response, text=force_text(self.test_object), + status_code=200 ) - self.assertContains(response, text=document.label, status_code=200) diff --git a/mayan/apps/acls/urls.py b/mayan/apps/acls/urls.py index ea0adb9d8b..dc73f8a5b7 100644 --- a/mayan/apps/acls/urls.py +++ b/mayan/apps/acls/urls.py @@ -12,35 +12,40 @@ from .views import ( urlpatterns = [ url( - r'^(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/create/$', - ACLCreateView.as_view(), name='acl_create' + regex=r'^objects/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/create/$', + name='acl_create', view=ACLCreateView.as_view() ), url( - r'^(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/list/$', - ACLListView.as_view(), name='acl_list' + regex=r'^objects/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/list/$', + name='acl_list', view=ACLListView.as_view() ), - url(r'^(?P\d+)/delete/$', ACLDeleteView.as_view(), name='acl_delete'), url( - r'^(?P\d+)/permissions/$', ACLPermissionsView.as_view(), - name='acl_permissions' + regex=r'^acls/(?P\d+)/delete/$', name='acl_delete', + view=ACLDeleteView.as_view() + ), + url( + regex=r'^acls/(?P\d+)/permissions/$', name='acl_permissions', + view=ACLPermissionsView.as_view() ), ] api_urls = [ url( - r'^objects/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/acls/$', - APIObjectACLListView.as_view(), name='accesscontrollist-list' + regex=r'^objects/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/acls/$', + name='accesscontrollist-list', view=APIObjectACLListView.as_view() ), url( - r'^objects/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/acls/(?P\d+)/$', - APIObjectACLView.as_view(), name='accesscontrollist-detail' + regex=r'^objects/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/acls/(?P\d+)/$', + name='accesscontrollist-detail', view=APIObjectACLView.as_view() ), url( - r'^objects/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/acls/(?P\d+)/permissions/$', - APIObjectACLPermissionListView.as_view(), name='accesscontrollist-permission-list' + regex=r'^objects/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/acls/(?P\d+)/permissions/$', + name='accesscontrollist-permission-list', + view=APIObjectACLPermissionListView.as_view() ), url( - r'^objects/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/acls/(?P\d+)/permissions/(?P\d+)/$', - APIObjectACLPermissionView.as_view(), name='accesscontrollist-permission-detail' + regex=r'^objects/(?P[-\w]+)/(?P[-\w]+)/(?P\d+)/acls/(?P\d+)/permissions/(?P\d+)/$', + name='accesscontrollist-permission-detail', + view=APIObjectACLPermissionView.as_view() ), ] diff --git a/mayan/apps/acls/views.py b/mayan/apps/acls/views.py index 704ec58d51..afbc4616c8 100644 --- a/mayan/apps/acls/views.py +++ b/mayan/apps/acls/views.py @@ -3,22 +3,23 @@ from __future__ import absolute_import, unicode_literals import itertools import logging -from django.contrib.contenttypes.models import ContentType -from django.http import Http404, HttpResponseRedirect +from django.core.exceptions import PermissionDenied from django.shortcuts import get_object_or_404 from django.template import RequestContext from django.urls import reverse from django.utils.encoding import force_text from django.utils.translation import ugettext_lazy as _ +from mayan.apps.common.mixins import ContentTypeViewMixin, ExternalObjectViewMixin from mayan.apps.common.views import ( AssignRemoveView, SingleObjectCreateView, SingleObjectDeleteView, SingleObjectListView ) from mayan.apps.permissions import Permission, PermissionNamespace -from mayan.apps.permissions.models import StoredPermission +from mayan.apps.permissions.models import Role, StoredPermission from .classes import ModelPermission +from .forms import ACLCreateForm from .icons import icon_acl_list from .links import link_acl_create from .models import AccessControlList @@ -27,76 +28,58 @@ from .permissions import permission_acl_edit, permission_acl_view logger = logging.getLogger(__name__) -class ACLCreateView(SingleObjectCreateView): - fields = ('role',) - model = AccessControlList +class ACLCreateView(ContentTypeViewMixin, ExternalObjectViewMixin, SingleObjectCreateView): + external_object_permission = permission_acl_edit + external_object_pk_url_kwarg = 'object_id' + form_class = ACLCreateForm - def dispatch(self, request, *args, **kwargs): - self.object_content_type = get_object_or_404( - klass=ContentType, app_label=self.kwargs['app_label'], - model=self.kwargs['model'] - ) + def get_error_message_duplicate(self): + return _( + 'An ACL for "%(object)s" using role "%(role)s" already exists. ' + 'Edit that ACL entry instead.' + ) % {'object': self.get_external_object(), 'role': self.object.role} - try: - self.content_object = self.object_content_type.get_object_for_this_type( - pk=self.kwargs['object_id'] - ) - except self.object_content_type.model_class().DoesNotExist: - raise Http404 - - AccessControlList.objects.check_access( - permissions=permission_acl_edit, user=request.user, - obj=self.content_object - ) - - return super(ACLCreateView, self).dispatch(request, *args, **kwargs) - - def get_instance_extra_data(self): - return { - 'content_object': self.content_object - } - - def form_valid(self, form): - try: - acl = AccessControlList.objects.get( - content_type=self.object_content_type, - object_id=self.content_object.pk, - role=form.cleaned_data['role'] - ) - except AccessControlList.DoesNotExist: - return super(ACLCreateView, self).form_valid(form) - else: - return HttpResponseRedirect( - reverse('acls:acl_permissions', args=(acl.pk,)) - ) + def get_external_object_queryset(self): + # Here we get a queryset the object model for which an ACL will be + # created. + return self.get_content_type().model_class().objects.all() def get_extra_context(self): return { - 'object': self.content_object, + 'object': self.get_external_object(), 'title': _( 'New access control lists for: %s' - ) % self.content_object + ) % self.get_external_object() } + def get_form_extra_kwargs(self): + return { + 'field_name': 'role', + 'label': _('Role'), + 'queryset': Role.objects.exclude( + pk__in=self.get_external_object().acls.values('role') + ), + 'widget_attributes': {'class': 'select2'} + } + + def get_instance_extra_data(self): + return { + 'content_object': self.get_external_object() + } + + def get_queryset(self): + self.get_external_object().acls.all() + def get_success_url(self): - if self.object.pk: - return reverse('acls:acl_permissions', args=(self.object.pk,)) - else: - return super(ACLCreateView, self).get_success_url() + return self.object.get_absolute_url() class ACLDeleteView(SingleObjectDeleteView): + object_permission = permission_acl_edit + object_permission_related = 'content_object' + object_permission_raise_404 = True model = AccessControlList - - def dispatch(self, request, *args, **kwargs): - acl = get_object_or_404(klass=AccessControlList, pk=self.kwargs['pk']) - - AccessControlList.objects.check_access( - permissions=permission_acl_edit, user=request.user, - obj=acl.content_object - ) - - return super(ACLDeleteView, self).dispatch(request, *args, **kwargs) + pk_url_kwarg = 'acl_pk' def get_extra_context(self): return { @@ -107,33 +90,22 @@ class ACLDeleteView(SingleObjectDeleteView): def get_post_action_redirect(self): instance = self.get_object() return reverse( - 'acls:acl_list', args=( - instance.content_type.app_label, - instance.content_type.model, instance.object_id - ) + 'acls:acl_list', kwargs={ + 'app_label': instance.content_type.app_label, + 'model': instance.content_type.model, + 'object_id': instance.object_id + } ) -class ACLListView(SingleObjectListView): - def dispatch(self, request, *args, **kwargs): - self.object_content_type = get_object_or_404( - klass=ContentType, app_label=self.kwargs['app_label'], - model=self.kwargs['model'] - ) +class ACLListView(ContentTypeViewMixin, ExternalObjectViewMixin, SingleObjectListView): + external_object_permission = permission_acl_view + external_object_pk_url_kwarg = 'object_id' - try: - self.content_object = self.object_content_type.get_object_for_this_type( - pk=self.kwargs['object_id'] - ) - except self.object_content_type.model_class().DoesNotExist: - raise Http404 - - AccessControlList.objects.check_access( - permissions=permission_acl_view, user=request.user, - obj=self.content_object - ) - - return super(ACLListView, self).dispatch(request, *args, **kwargs) + def get_external_object_queryset(self): + # Here we get a queryset the object model for which an ACL will be + # created. + return self.get_content_type().model_class().objects.all() def get_extra_context(self): return { @@ -141,7 +113,9 @@ class ACLListView(SingleObjectListView): 'no_results_icon': icon_acl_list, 'no_results_main_link': link_acl_create.resolve( context=RequestContext( - self.request, {'resolved_object': self.content_object} + self.request, { + 'resolved_object': self.get_external_object() + } ) ), 'no_results_title': _( @@ -149,17 +123,18 @@ class ACLListView(SingleObjectListView): ), 'no_results_text': _( 'ACL stands for Access Control List and is a precise method ' - ' to control user access to objects in the system.' + ' to control user access to objects in the system. ACLs ' + 'allow granting a permission to a role but only for a ' + 'specific object or set of objects.' + ), + 'object': self.get_external_object(), + 'title': _( + 'Access control lists for: %s' % self.get_external_object() ), - 'object': self.content_object, - 'title': _('Access control lists for: %s' % self.content_object), } def get_object_list(self): - return AccessControlList.objects.filter( - content_type=self.object_content_type, - object_id=self.content_object.pk - ) + return self.get_external_object().acls.all() class ACLPermissionsView(AssignRemoveView): @@ -192,18 +167,6 @@ class ACLPermissionsView(AssignRemoveView): permission = get_object_or_404(klass=StoredPermission, pk=item) self.get_object().permissions.add(permission) - def dispatch(self, request, *args, **kwargs): - acl = get_object_or_404(klass=AccessControlList, pk=self.kwargs['pk']) - - AccessControlList.objects.check_access( - permissions=permission_acl_edit, user=request.user, - obj=acl.content_object - ) - - return super( - ACLPermissionsView, self - ).dispatch(request, *args, **kwargs) - def get_available_list(self): return ModelPermission.get_for_instance( instance=self.get_object().content_object @@ -235,22 +198,45 @@ class ACLPermissionsView(AssignRemoveView): def get_granted_list(self): """ - Merge or permissions we hold for this object and the permissions we - hold for this object's parent via another ACL + Merge of permissions we hold for this object and the permissions we + hold for this object's parent via another ACL. """ - merged_pks = self.get_object().permissions.values_list('pk', flat=True) | self.get_object().get_inherited_permissions().values_list('pk', flat=True) + merged_pks = self.get_object().permissions.values_list( + 'pk', flat=True + ) | self.get_object().get_inherited_permissions().values_list( + 'pk', flat=True + ) return StoredPermission.objects.filter(pk__in=merged_pks) def get_object(self): - return get_object_or_404(klass=AccessControlList, pk=self.kwargs['pk']) + acl = get_object_or_404( + klass=AccessControlList, pk=self.kwargs['acl_pk'] + ) + + # Get the ACL, from this get the object of the ACL, from the object + # get all ACLs it holds as a filtered queryset by access. + + try: + AccessControlList.objects.check_access( + permissions=(permission_acl_edit,), obj=acl.content_object, + user=self.request.user + ) + except PermissionDenied: + queryset = AccessControlList.objects.none() + else: + queryset = acl.content_object.acls.all() + + return get_object_or_404(klass=queryset, pk=self.kwargs['acl_pk']) def get_right_list_help_text(self): if self.get_object().get_inherited_permissions(): return _( - 'Disabled permissions are inherited from a parent object.' + 'Disabled permissions are inherited from a parent object and ' + 'can\'t be removed from this view, they need to be removed ' + 'from the parent object\'s ACL view.' ) - return None + return self.right_list_help_text def left_list(self): Permission.refresh() diff --git a/mayan/apps/acls/workflow_actions.py b/mayan/apps/acls/workflow_actions.py index 1843f73745..0d333914dd 100644 --- a/mayan/apps/acls/workflow_actions.py +++ b/mayan/apps/acls/workflow_actions.py @@ -98,7 +98,9 @@ class GrantAccessAction(WorkflowAction): def get_form_schema(self, *args, **kwargs): self.fields['content_type']['kwargs']['queryset'] = ModelPermission.get_classes(as_content_type=True) - self.fields['permissions']['kwargs']['choices'] = Permission.all(as_choices=True) + self.fields['permissions']['kwargs']['choices'] = Permission.all( + as_choices=True + ) return super(GrantAccessAction, self).get_form_schema(*args, **kwargs) def get_execute_data(self):