Update ACL app to compy with MERC 5 and 6

Update the entire with keyword arguments. Update the views
to comply with MERC 6 by returning error 404 on access
failure. API are untouched. Add icon to the ACL delete
button. Add additional view tests. Use the new filtered
choice form to display a select2 enabled role selection
widget. Update the ACL creation view to not redirect to an
existing ACL in case of duplication but to instead stop
and display an error with a suggestion to the user
to instead edit the existing ACL.

Signed-off-by: Roberto Rosario <Roberto.Rosario@mayan-edms.com>
This commit is contained in:
Roberto Rosario
2019-01-02 02:48:19 -04:00
parent 86b0463a38
commit dfd548bf62
15 changed files with 389 additions and 361 deletions

View File

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

View File

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

13
mayan/apps/acls/forms.py Normal file
View File

@@ -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

View File

@@ -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',

View File

@@ -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',
)

View File

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

View File

@@ -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')

View File

@@ -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'
)

View File

@@ -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):

View File

@@ -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

View File

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

View File

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

View File

@@ -12,35 +12,40 @@ from .views import (
urlpatterns = [
url(
r'^(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/create/$',
ACLCreateView.as_view(), name='acl_create'
regex=r'^objects/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/create/$',
name='acl_create', view=ACLCreateView.as_view()
),
url(
r'^(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/list/$',
ACLListView.as_view(), name='acl_list'
regex=r'^objects/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/list/$',
name='acl_list', view=ACLListView.as_view()
),
url(r'^(?P<pk>\d+)/delete/$', ACLDeleteView.as_view(), name='acl_delete'),
url(
r'^(?P<pk>\d+)/permissions/$', ACLPermissionsView.as_view(),
name='acl_permissions'
regex=r'^acls/(?P<acl_pk>\d+)/delete/$', name='acl_delete',
view=ACLDeleteView.as_view()
),
url(
regex=r'^acls/(?P<acl_pk>\d+)/permissions/$', name='acl_permissions',
view=ACLPermissionsView.as_view()
),
]
api_urls = [
url(
r'^objects/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_pk>\d+)/acls/$',
APIObjectACLListView.as_view(), name='accesscontrollist-list'
regex=r'^objects/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/acls/$',
name='accesscontrollist-list', view=APIObjectACLListView.as_view()
),
url(
r'^objects/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_pk>\d+)/acls/(?P<pk>\d+)/$',
APIObjectACLView.as_view(), name='accesscontrollist-detail'
regex=r'^objects/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/acls/(?P<acl_pk>\d+)/$',
name='accesscontrollist-detail', view=APIObjectACLView.as_view()
),
url(
r'^objects/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_pk>\d+)/acls/(?P<pk>\d+)/permissions/$',
APIObjectACLPermissionListView.as_view(), name='accesscontrollist-permission-list'
regex=r'^objects/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/acls/(?P<acl_pk>\d+)/permissions/$',
name='accesscontrollist-permission-list',
view=APIObjectACLPermissionListView.as_view()
),
url(
r'^objects/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_pk>\d+)/acls/(?P<pk>\d+)/permissions/(?P<permission_pk>\d+)/$',
APIObjectACLPermissionView.as_view(), name='accesscontrollist-permission-detail'
regex=r'^objects/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/acls/(?P<acl_pk>\d+)/permissions/(?P<permission_pk>\d+)/$',
name='accesscontrollist-permission-detail',
view=APIObjectACLPermissionView.as_view()
),
]

View File

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

View File

@@ -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):