Add permission inheritance by parent object. Add ACLs app model tests.
This commit is contained in:
@@ -3,7 +3,7 @@ python:
|
|||||||
- 2.7
|
- 2.7
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- TEST_APPS="authentication django_gpg document_indexing document_signatures documents dynamic_search folders lock_manager ocr permissions sources tags"
|
- TEST_APPS="acls authentication django_gpg document_indexing document_signatures documents dynamic_search folders lock_manager ocr permissions sources tags"
|
||||||
matrix:
|
matrix:
|
||||||
- DB=mysql
|
- DB=mysql
|
||||||
- DB=postgres
|
- DB=postgres
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class ModelPermission(object):
|
class ModelPermission(object):
|
||||||
_registry = {}
|
_registry = {}
|
||||||
|
_proxies = {}
|
||||||
|
_inheritances = {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def register(cls, model, permissions):
|
def register(cls, model, permissions):
|
||||||
@@ -18,6 +20,25 @@ class ModelPermission(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_for_instance(cls, instance):
|
def get_for_instance(cls, instance):
|
||||||
permissions = cls._registry.get(type(instance), ())
|
try:
|
||||||
|
permissions = cls._registry[type(instance)]
|
||||||
|
except KeyError:
|
||||||
|
try:
|
||||||
|
permissions = cls._registry[cls._proxies[type(instance)]]
|
||||||
|
except KeyError:
|
||||||
|
permissions = ()
|
||||||
|
|
||||||
pks = [permission.stored_permission.pk for permission in permissions]
|
pks = [permission.stored_permission.pk for permission in permissions]
|
||||||
return StoredPermission.objects.filter(pk__in=pks)
|
return StoredPermission.objects.filter(pk__in=pks)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register_proxy(cls, source, model):
|
||||||
|
cls._proxies[model] = source
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register_inheritance(cls, model, related):
|
||||||
|
cls._inheritances[model] = related
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_inheritance(cls, model):
|
||||||
|
return cls._inheritances[model]
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import Q
|
||||||
from django.utils.translation import ugettext
|
from django.utils.translation import ugettext
|
||||||
|
|
||||||
|
from permissions.models import StoredPermission
|
||||||
|
|
||||||
|
from .classes import ModelPermission
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -16,20 +21,44 @@ class AccessControlListManager(models.Manager):
|
|||||||
and an object
|
and an object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def get_inherited_permissions(self, role, obj):
|
||||||
|
try:
|
||||||
|
instance = obj.first()
|
||||||
|
except AttributeError:
|
||||||
|
instance = obj
|
||||||
|
else:
|
||||||
|
if not instance:
|
||||||
|
return StoredPermission.objects.none()
|
||||||
|
|
||||||
|
try:
|
||||||
|
parent_accessor = ModelPermission.get_inheritance(type(instance))
|
||||||
|
except KeyError:
|
||||||
|
return StoredPermission.objects.none()
|
||||||
|
else:
|
||||||
|
parent_object = getattr(instance, parent_accessor)
|
||||||
|
content_type = ContentType.objects.get_for_model(parent_object)
|
||||||
|
try:
|
||||||
|
return self.get(role=role, content_type=content_type, object_id=parent_object.pk).permissions.all()
|
||||||
|
except self.model.DoesNotExist:
|
||||||
|
return StoredPermission.objects.none()
|
||||||
|
|
||||||
def check_access(self, permissions, user, obj):
|
def check_access(self, permissions, user, obj):
|
||||||
if user.is_superuser or user.is_staff:
|
if user.is_superuser or user.is_staff:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
user_roles = []
|
|
||||||
for group in user.groups.all():
|
|
||||||
for role in group.roles.all():
|
|
||||||
user_roles.append(role)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stored_permissions = [permission.stored_permission for permission in permissions]
|
stored_permissions = [permission.stored_permission for permission in permissions]
|
||||||
except TypeError:
|
except TypeError:
|
||||||
stored_permissions = [permissions.stored_permission]
|
stored_permissions = [permissions.stored_permission]
|
||||||
|
|
||||||
|
user_roles = []
|
||||||
|
for group in user.groups.all():
|
||||||
|
for role in group.roles.all():
|
||||||
|
if set(stored_permissions).intersection(set(self.get_inherited_permissions(role=role, obj=obj))):
|
||||||
|
return True
|
||||||
|
|
||||||
|
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):
|
if not self.filter(content_type=ContentType.objects.get_for_model(obj), object_id=obj.pk, permissions__in=stored_permissions, role__in=user_roles):
|
||||||
raise PermissionDenied(ugettext('Insufficient access.'))
|
raise PermissionDenied(ugettext('Insufficient access.'))
|
||||||
|
|
||||||
@@ -42,11 +71,21 @@ class AccessControlListManager(models.Manager):
|
|||||||
for role in group.roles.all():
|
for role in group.roles.all():
|
||||||
user_roles.append(role)
|
user_roles.append(role)
|
||||||
|
|
||||||
|
parent_accessor = ModelPermission.get_inheritance(queryset.model)
|
||||||
|
instance = queryset.first()
|
||||||
|
if instance:
|
||||||
|
parent_object = getattr(instance, parent_accessor)
|
||||||
|
parent_content_type = ContentType.objects.get_for_model(parent_object)
|
||||||
|
parent_queryset = self.filter(content_type=parent_content_type, role__in=user_roles, permissions=permission.stored_permission)
|
||||||
|
parent_acl_query = Q(**{'{}__pk__in'.format(parent_accessor): parent_queryset.values_list('pk', flat=True)})
|
||||||
|
else:
|
||||||
|
parent_acl_query = Q()
|
||||||
|
|
||||||
|
# Directly granted access
|
||||||
content_type = ContentType.objects.get_for_model(queryset.model)
|
content_type = ContentType.objects.get_for_model(queryset.model)
|
||||||
|
acl_query = Q(pk__in=self.filter(content_type=content_type, role__in=user_roles, permissions=permission.stored_permission).values_list('object_id', flat=True))
|
||||||
|
|
||||||
acls = self.filter(content_type=content_type, role__in=user_roles, permissions=permission.stored_permission).values_list('object_id', flat=True)
|
new_queryset = queryset.filter(parent_acl_query | acl_query)
|
||||||
|
|
||||||
new_queryset = queryset.filter(pk__in=acls)
|
|
||||||
|
|
||||||
if new_queryset.count() == 0 and exception_on_empty:
|
if new_queryset.count() == 0 and exception_on_empty:
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
|
|
||||||
from permissions.models import Role, StoredPermission
|
from permissions.models import Role, StoredPermission
|
||||||
|
|
||||||
|
from .classes import ModelPermission
|
||||||
from .managers import AccessControlListManager
|
from .managers import AccessControlListManager
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -43,3 +44,6 @@ class AccessControlList(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{} <=> {}'.format(self.content_object, self.role)
|
return '{} <=> {}'.format(self.content_object, self.role)
|
||||||
|
|
||||||
|
def get_inherited_permissions(self):
|
||||||
|
return AccessControlList.objects.get_inherited_permissions(role=self.role, obj=self.content_object)
|
||||||
|
|||||||
146
mayan/apps/acls/test_models.py
Normal file
146
mayan/apps/acls/test_models.py
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.auth.models import Group
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.core.files import File
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.test.client import Client
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from documents.models import Document, DocumentType
|
||||||
|
from documents.permissions import permission_document_view
|
||||||
|
from documents.test_models import (
|
||||||
|
TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME, TEST_ADMIN_EMAIL,
|
||||||
|
TEST_SMALL_DOCUMENT_FILENAME, TEST_NON_ASCII_DOCUMENT_FILENAME,
|
||||||
|
TEST_NON_ASCII_COMPRESSED_DOCUMENT_FILENAME, TEST_DOCUMENT_PATH,
|
||||||
|
TEST_SIGNED_DOCUMENT_PATH, TEST_SMALL_DOCUMENT_PATH,
|
||||||
|
TEST_NON_ASCII_DOCUMENT_PATH, TEST_NON_ASCII_COMPRESSED_DOCUMENT_PATH,
|
||||||
|
TEST_DOCUMENT_DESCRIPTION, TEST_DOCUMENT_TYPE
|
||||||
|
)
|
||||||
|
from permissions.classes import Permission
|
||||||
|
from permissions.models import Role
|
||||||
|
|
||||||
|
from .models import AccessControlList
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.document_type_1 = DocumentType.objects.create(label=TEST_DOCUMENT_TYPE)
|
||||||
|
|
||||||
|
ocr_settings = self.document_type_1.ocr_settings
|
||||||
|
ocr_settings.auto_ocr = False
|
||||||
|
ocr_settings.save()
|
||||||
|
|
||||||
|
self.document_type_2 = DocumentType.objects.create(label=TEST_DOCUMENT_TYPE + '2')
|
||||||
|
|
||||||
|
ocr_settings = self.document_type_2.ocr_settings
|
||||||
|
ocr_settings.auto_ocr = False
|
||||||
|
ocr_settings.save()
|
||||||
|
|
||||||
|
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
||||||
|
self.document_1 = self.document_type_1.new_document(file_object=File(file_object), label='document 1')
|
||||||
|
|
||||||
|
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
||||||
|
self.document_2 = self.document_type_1.new_document(file_object=File(file_object), label='document 2')
|
||||||
|
|
||||||
|
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
||||||
|
self.document_3 = self.document_type_2.new_document(file_object=File(file_object), label='document 3')
|
||||||
|
|
||||||
|
self.user = get_user_model().objects.create(username='test user')
|
||||||
|
self.group = Group.objects.create(name='test group')
|
||||||
|
self.role = Role.objects.create(label='test role')
|
||||||
|
Permission.invalidate_cache()
|
||||||
|
|
||||||
|
def test_check_access_without_permissions(self):
|
||||||
|
with self.assertRaises(PermissionDenied):
|
||||||
|
AccessControlList.objects.check_access(permissions=(permission_document_view,), user=self.user, obj=self.document_1)
|
||||||
|
|
||||||
|
def test_filtering_without_permissions(self):
|
||||||
|
self.assertEqual(
|
||||||
|
list(AccessControlList.objects.filter_by_access(permission=permission_document_view, user=self.user, queryset=Document.objects.all())),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_check_access_with_acl(self):
|
||||||
|
self.group.user_set.add(self.user)
|
||||||
|
self.role.groups.add(self.group)
|
||||||
|
|
||||||
|
acl = AccessControlList.objects.create(content_object=self.document_1, role=self.role)
|
||||||
|
acl.permissions.add(permission_document_view.stored_permission)
|
||||||
|
|
||||||
|
try:
|
||||||
|
AccessControlList.objects.check_access(permissions=(permission_document_view,), user=self.user, obj=self.document_1)
|
||||||
|
except PermissionDenied:
|
||||||
|
self.fail('PermissionDenied exception was not expected.')
|
||||||
|
|
||||||
|
def test_filtering_with_permissions(self):
|
||||||
|
self.group.user_set.add(self.user)
|
||||||
|
self.role.permissions.add(permission_document_view.stored_permission)
|
||||||
|
self.role.groups.add(self.group)
|
||||||
|
|
||||||
|
acl = AccessControlList.objects.create(content_object=self.document_1, role=self.role)
|
||||||
|
acl.permissions.add(permission_document_view.stored_permission)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
list(AccessControlList.objects.filter_by_access(permission=permission_document_view, user=self.user, queryset=Document.objects.all())),
|
||||||
|
[self.document_1]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_check_access_with_inherited_acl(self):
|
||||||
|
self.group.user_set.add(self.user)
|
||||||
|
self.role.groups.add(self.group)
|
||||||
|
|
||||||
|
acl = AccessControlList.objects.create(content_object=self.document_type_1, role=self.role)
|
||||||
|
acl.permissions.add(permission_document_view.stored_permission)
|
||||||
|
|
||||||
|
try:
|
||||||
|
AccessControlList.objects.check_access(permissions=(permission_document_view,), user=self.user, obj=self.document_1)
|
||||||
|
except PermissionDenied:
|
||||||
|
self.fail('PermissionDenied exception was not expected.')
|
||||||
|
|
||||||
|
def test_check_access_with_inherited_acl_and_local_acl(self):
|
||||||
|
self.group.user_set.add(self.user)
|
||||||
|
self.role.groups.add(self.group)
|
||||||
|
|
||||||
|
acl = AccessControlList.objects.create(content_object=self.document_type_1, role=self.role)
|
||||||
|
acl.permissions.add(permission_document_view.stored_permission)
|
||||||
|
|
||||||
|
acl = AccessControlList.objects.create(content_object=self.document_3, role=self.role)
|
||||||
|
acl.permissions.add(permission_document_view.stored_permission)
|
||||||
|
|
||||||
|
try:
|
||||||
|
AccessControlList.objects.check_access(permissions=(permission_document_view,), user=self.user, obj=self.document_3)
|
||||||
|
except PermissionDenied:
|
||||||
|
self.fail('PermissionDenied exception was not expected.')
|
||||||
|
|
||||||
|
def test_filtering_with_inherited_permissions(self):
|
||||||
|
self.group.user_set.add(self.user)
|
||||||
|
self.role.permissions.add(permission_document_view.stored_permission)
|
||||||
|
self.role.groups.add(self.group)
|
||||||
|
|
||||||
|
acl = AccessControlList.objects.create(content_object=self.document_type_1, role=self.role)
|
||||||
|
acl.permissions.add(permission_document_view.stored_permission)
|
||||||
|
|
||||||
|
result = AccessControlList.objects.filter_by_access(permission=permission_document_view, user=self.user, queryset=Document.objects.all())
|
||||||
|
self.assertTrue(self.document_1 in result)
|
||||||
|
self.assertTrue(self.document_2 in result)
|
||||||
|
self.assertTrue(self.document_3 not in result)
|
||||||
|
|
||||||
|
|
||||||
|
def test_filtering_with_inherited_permissions_and_local_acl(self):
|
||||||
|
self.group.user_set.add(self.user)
|
||||||
|
self.role.permissions.add(permission_document_view.stored_permission)
|
||||||
|
self.role.groups.add(self.group)
|
||||||
|
|
||||||
|
acl = AccessControlList.objects.create(content_object=self.document_type_1, role=self.role)
|
||||||
|
acl.permissions.add(permission_document_view.stored_permission)
|
||||||
|
|
||||||
|
acl = AccessControlList.objects.create(content_object=self.document_3, role=self.role)
|
||||||
|
acl.permissions.add(permission_document_view.stored_permission)
|
||||||
|
|
||||||
|
result = AccessControlList.objects.filter_by_access(permission=permission_document_view, user=self.user, queryset=Document.objects.all())
|
||||||
|
self.assertTrue(self.document_1 in result)
|
||||||
|
self.assertTrue(self.document_2 in result)
|
||||||
|
self.assertTrue(self.document_3 in result)
|
||||||
@@ -5,6 +5,7 @@ import logging
|
|||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
@@ -24,11 +25,11 @@ from .permissions import permission_acl_edit, permission_acl_view
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _permission_titles(permission_list):
|
|
||||||
return ', '.join([unicode(permission) for permission in permission_list])
|
|
||||||
|
|
||||||
|
|
||||||
class ACLListView(SingleObjectListView):
|
class ACLListView(SingleObjectListView):
|
||||||
|
@staticmethod
|
||||||
|
def permission_titles(permission_list):
|
||||||
|
return ', '.join([unicode(permission) for permission in permission_list])
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
self.content_type = get_object_or_404(ContentType, app_label=self.kwargs['app_label'], model=self.kwargs['model'])
|
self.content_type = get_object_or_404(ContentType, app_label=self.kwargs['app_label'], model=self.kwargs['model'])
|
||||||
|
|
||||||
@@ -47,32 +48,27 @@ class ACLListView(SingleObjectListView):
|
|||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return AccessControlList.objects.filter(content_type=self.content_type, object_id=self.content_object.pk)
|
return AccessControlList.objects.filter(content_type=self.content_type, object_id=self.content_object.pk)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_extra_context(self):
|
||||||
context = super(ACLListView, self).get_context_data(**kwargs)
|
return {
|
||||||
context.update(
|
'hide_object': True,
|
||||||
{
|
'object': self.content_object,
|
||||||
'hide_object': True,
|
'title': _('Access control lists for: %s' % self.content_object),
|
||||||
'object': self.content_object,
|
'extra_columns': [
|
||||||
'title': _('Access control lists for: %s' % self.content_object),
|
{
|
||||||
'extra_columns': [
|
'name': _('Role'),
|
||||||
{
|
'attribute': 'role'
|
||||||
'name': _('Role'),
|
},
|
||||||
'attribute': 'role'
|
{
|
||||||
},
|
'name': _('Permissions'),
|
||||||
{
|
'attribute': encapsulate(lambda entry: ACLListView.permission_titles(entry.permissions.all()))
|
||||||
'name': _('Permissions'),
|
},
|
||||||
'attribute': encapsulate(lambda x: _permission_titles(x.permissions.all()))
|
],
|
||||||
},
|
}
|
||||||
],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class ACLCreateView(SingleObjectCreateView):
|
class ACLCreateView(SingleObjectCreateView):
|
||||||
model = AccessControlList
|
|
||||||
fields = ('role',)
|
fields = ('role',)
|
||||||
|
model = AccessControlList
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
content_type = get_object_or_404(ContentType, app_label=self.kwargs['app_label'], model=self.kwargs['model'])
|
content_type = get_object_or_404(ContentType, app_label=self.kwargs['app_label'], model=self.kwargs['model'])
|
||||||
@@ -90,12 +86,15 @@ class ACLCreateView(SingleObjectCreateView):
|
|||||||
return super(ACLCreateView, self).dispatch(request, *args, **kwargs)
|
return super(ACLCreateView, self).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
instance = form.save(commit=False)
|
self.instance = form.save(commit=False)
|
||||||
instance.content_object = self.content_object
|
self.instance.content_object = self.content_object
|
||||||
instance.save()
|
self.instance.save()
|
||||||
|
|
||||||
return super(ACLCreateView, self).form_valid(form)
|
return super(ACLCreateView, self).form_valid(form)
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse('acls:acl_permissions', args=[self.instance.pk])
|
||||||
|
|
||||||
def get_extra_context(self):
|
def get_extra_context(self):
|
||||||
return {
|
return {
|
||||||
'object': self.content_object,
|
'object': self.content_object,
|
||||||
@@ -105,7 +104,16 @@ class ACLCreateView(SingleObjectCreateView):
|
|||||||
|
|
||||||
class ACLDeleteView(SingleObjectDeleteView):
|
class ACLDeleteView(SingleObjectDeleteView):
|
||||||
model = AccessControlList
|
model = AccessControlList
|
||||||
object_permission = permission_acl_edit
|
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
acl = get_object_or_404(AccessControlList, pk=self.kwargs['pk'])
|
||||||
|
|
||||||
|
try:
|
||||||
|
Permission.check_permissions(request.user, permissions=(permission_acl_edit,))
|
||||||
|
except PermissionDenied:
|
||||||
|
AccessControlList.objects.check_access(permission_acl_edit, request.user, acl.content_object)
|
||||||
|
|
||||||
|
return super(ACLDeleteView, self).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(ACLDeleteView, self).get_context_data(**kwargs)
|
context = super(ACLDeleteView, self).get_context_data(**kwargs)
|
||||||
@@ -120,45 +128,74 @@ class ACLDeleteView(SingleObjectDeleteView):
|
|||||||
|
|
||||||
class ACLPermissionsView(AssignRemoveView):
|
class ACLPermissionsView(AssignRemoveView):
|
||||||
grouped = True
|
grouped = True
|
||||||
object_permission = permission_acl_edit
|
|
||||||
left_list_title = _('Available permissions')
|
left_list_title = _('Available permissions')
|
||||||
right_list_title = _('Granted permissions')
|
right_list_title = _('Granted permissions')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_choices(entries):
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for namespace, permissions in itertools.groupby(entries, lambda entry: entry.namespace):
|
||||||
|
permission_options = [(unicode(permission.pk), permission) for permission in permissions]
|
||||||
|
results.append((PermissionNamespace.get(namespace), permission_options))
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
def add(self, item):
|
def add(self, item):
|
||||||
permission = get_object_or_404(StoredPermission, pk=item)
|
permission = get_object_or_404(StoredPermission, pk=item)
|
||||||
self.get_object().permissions.add(permission)
|
self.get_object().permissions.add(permission)
|
||||||
|
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
acl = get_object_or_404(AccessControlList, pk=self.kwargs['pk'])
|
||||||
|
|
||||||
|
try:
|
||||||
|
Permission.check_permissions(request.user, permissions=(permission_acl_edit,))
|
||||||
|
except PermissionDenied:
|
||||||
|
AccessControlList.objects.check_access(permission_acl_edit, request.user, acl.content_object)
|
||||||
|
|
||||||
|
return super(ACLPermissionsView, self).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_help_text(self):
|
||||||
|
if self.get_object().get_inherited_permissions():
|
||||||
|
return _('Disabled permissions are inherited from a parent object.')
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
return get_object_or_404(AccessControlList, pk=self.kwargs['pk'])
|
return get_object_or_404(AccessControlList, pk=self.kwargs['pk'])
|
||||||
|
|
||||||
def left_list(self):
|
def get_available_list(self):
|
||||||
results = []
|
return ModelPermission.get_for_instance(instance=self.get_object().content_object).exclude(id__in=self.get_granted_list().values_list('pk', flat=True))
|
||||||
for namespace, permissions in itertools.groupby(ModelPermission.get_for_instance(instance=self.get_object().content_object).exclude(id__in=self.get_object().permissions.values_list('pk', flat=True)), lambda entry: entry.namespace):
|
|
||||||
permission_options = [(unicode(permission.pk), permission) for permission in permissions]
|
|
||||||
results.append((PermissionNamespace.get(namespace), permission_options))
|
|
||||||
|
|
||||||
return results
|
def get_disabled_choices(self):
|
||||||
|
"""
|
||||||
|
Get permissions from a parent's acls but remove the permissions we
|
||||||
|
already hold for this object
|
||||||
|
"""
|
||||||
|
return map(str, set(self.get_object().get_inherited_permissions().values_list('pk', flat=True)).difference(self.get_object().permissions.values_list('pk', flat=True)))
|
||||||
|
|
||||||
|
def get_extra_context(self):
|
||||||
|
return {
|
||||||
|
'object': self.get_object().content_object,
|
||||||
|
'title': _('Role "%(role)s" permission\'s for "%(object)s"') % {
|
||||||
|
'role': self.get_object().role,
|
||||||
|
'object': self.get_object().content_object,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
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 left_list(self):
|
||||||
|
return ACLPermissionsView.generate_choices(self.get_available_list())
|
||||||
|
|
||||||
def right_list(self):
|
def right_list(self):
|
||||||
results = []
|
return ACLPermissionsView.generate_choices(self.get_granted_list())
|
||||||
for namespace, permissions in itertools.groupby(self.get_object().permissions.all(), lambda entry: entry.namespace):
|
|
||||||
permission_options = [(unicode(permission.pk), permission) for permission in permissions]
|
|
||||||
results.append((PermissionNamespace.get(namespace), permission_options))
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(ACLPermissionsView, self).get_context_data(**kwargs)
|
|
||||||
context.update(
|
|
||||||
{
|
|
||||||
'object': self.get_object().content_object,
|
|
||||||
'title': _('Role "%(role)s" permission\'s for "%(object)s"') % {
|
|
||||||
'role': self.get_object().role,
|
|
||||||
'object': self.get_object().content_object,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return context
|
|
||||||
|
|
||||||
def remove(self, item):
|
def remove(self, item):
|
||||||
permission = get_object_or_404(StoredPermission, pk=item)
|
permission = get_object_or_404(StoredPermission, pk=item)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
|
|
||||||
from .models import UserLocaleProfile
|
from .models import UserLocaleProfile
|
||||||
from .utils import return_attrib
|
from .utils import return_attrib
|
||||||
from .widgets import DetailSelectMultiple, PlainWidget
|
from .widgets import DetailSelectMultiple, DisableableSelectWidget, PlainWidget
|
||||||
|
|
||||||
|
|
||||||
class DetailForm(forms.ModelForm):
|
class DetailForm(forms.ModelForm):
|
||||||
@@ -57,24 +57,6 @@ class DetailForm(forms.ModelForm):
|
|||||||
self.fields[field_name].widget.attrs.update({'readonly': 'readonly'})
|
self.fields[field_name].widget.attrs.update({'readonly': 'readonly'})
|
||||||
|
|
||||||
|
|
||||||
class GenericAssignRemoveForm(forms.Form):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
left_list_qryset = kwargs.pop('left_list_qryset', None)
|
|
||||||
right_list_qryset = kwargs.pop('right_list_qryset', None)
|
|
||||||
left_filter = kwargs.pop('left_filter', None)
|
|
||||||
super(GenericAssignRemoveForm, self).__init__(*args, **kwargs)
|
|
||||||
if left_filter:
|
|
||||||
self.fields['left_list'].queryset = left_list_qryset.filter(
|
|
||||||
*left_filter)
|
|
||||||
else:
|
|
||||||
self.fields['left_list'].queryset = left_list_qryset
|
|
||||||
|
|
||||||
self.fields['right_list'].queryset = right_list_qryset
|
|
||||||
|
|
||||||
left_list = forms.ModelMultipleChoiceField(required=False, queryset=None)
|
|
||||||
right_list = forms.ModelMultipleChoiceField(required=False, queryset=None)
|
|
||||||
|
|
||||||
|
|
||||||
class ChoiceForm(forms.Form):
|
class ChoiceForm(forms.Form):
|
||||||
"""
|
"""
|
||||||
Form to be used in side by side templates used to add or remove
|
Form to be used in side by side templates used to add or remove
|
||||||
@@ -83,12 +65,16 @@ class ChoiceForm(forms.Form):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
choices = kwargs.pop('choices', [])
|
choices = kwargs.pop('choices', [])
|
||||||
label = kwargs.pop('label', _('Selection'))
|
label = kwargs.pop('label', _('Selection'))
|
||||||
|
help_text = kwargs.pop('help_text', None)
|
||||||
|
disabled_choices = kwargs.pop('disabled_choices', ())
|
||||||
super(ChoiceForm, self).__init__(*args, **kwargs)
|
super(ChoiceForm, self).__init__(*args, **kwargs)
|
||||||
self.fields['selection'].choices = choices
|
self.fields['selection'].choices = choices
|
||||||
self.fields['selection'].label = label
|
self.fields['selection'].label = label
|
||||||
|
self.fields['selection'].help_text = help_text
|
||||||
|
self.fields['selection'].widget.disabled_choices = disabled_choices
|
||||||
self.fields['selection'].widget.attrs.update({'size': 14, 'class': 'choice_form'})
|
self.fields['selection'].widget.attrs.update({'size': 14, 'class': 'choice_form'})
|
||||||
|
|
||||||
selection = forms.MultipleChoiceField()
|
selection = forms.MultipleChoiceField(widget=DisableableSelectWidget())
|
||||||
|
|
||||||
|
|
||||||
class UserForm_view(DetailForm):
|
class UserForm_view(DetailForm):
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ from .mixins import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AssignRemoveView(ViewPermissionCheckMixin, ObjectPermissionCheckMixin, TemplateView):
|
class AssignRemoveView(ExtraContextMixin, ViewPermissionCheckMixin, ObjectPermissionCheckMixin, TemplateView):
|
||||||
decode_content_type = False
|
decode_content_type = False
|
||||||
extra_context = None
|
extra_context = None
|
||||||
grouped = False
|
grouped = False
|
||||||
@@ -75,9 +75,15 @@ class AssignRemoveView(ViewPermissionCheckMixin, ObjectPermissionCheckMixin, Tem
|
|||||||
# Subclass must override
|
# Subclass must override
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def get_disabled_choices(self):
|
||||||
|
return ()
|
||||||
|
|
||||||
|
def get_help_text(self):
|
||||||
|
return self.help_text
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
self.unselected_list = ChoiceForm(prefix=self.LEFT_LIST_NAME, choices=self.left_list())
|
self.unselected_list = ChoiceForm(prefix=self.LEFT_LIST_NAME, choices=self.left_list())
|
||||||
self.selected_list = ChoiceForm(prefix=self.RIGHT_LIST_NAME, choices=self.right_list())
|
self.selected_list = ChoiceForm(prefix=self.RIGHT_LIST_NAME, choices=self.right_list(), disabled_choices=self.get_disabled_choices(), help_text=self.get_help_text())
|
||||||
return self.render_to_response(self.get_context_data())
|
return self.render_to_response(self.get_context_data())
|
||||||
|
|
||||||
def process_form(self, prefix, items_function, action_function):
|
def process_form(self, prefix, items_function, action_function):
|
||||||
|
|||||||
@@ -98,6 +98,14 @@ class DocumentsApp(MayanAppConfig):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ModelPermission.register_proxy(
|
||||||
|
source=Document, model=DocumentType,
|
||||||
|
)
|
||||||
|
|
||||||
|
ModelPermission.register_inheritance(
|
||||||
|
model=Document, related='document_type',
|
||||||
|
)
|
||||||
|
|
||||||
SourceColumn(source=Document, label=_('Thumbnail'), attribute=encapsulate(lambda document: document_thumbnail(document, gallery_name='documents:document_list', title=getattr(document, 'label', None), size=setting_thumbnail_size.value)))
|
SourceColumn(source=Document, label=_('Thumbnail'), attribute=encapsulate(lambda document: document_thumbnail(document, gallery_name='documents:document_list', title=getattr(document, 'label', None), size=setting_thumbnail_size.value)))
|
||||||
SourceColumn(source=Document, label=_('Type'), attribute='document_type')
|
SourceColumn(source=Document, label=_('Type'), attribute='document_type')
|
||||||
SourceColumn(source=DeletedDocument, label=_('Type'), attribute='document_type')
|
SourceColumn(source=DeletedDocument, label=_('Type'), attribute='document_type')
|
||||||
@@ -124,7 +132,7 @@ class DocumentsApp(MayanAppConfig):
|
|||||||
menu_tools.bind_links(links=[link_clear_image_cache])
|
menu_tools.bind_links(links=[link_clear_image_cache])
|
||||||
|
|
||||||
# Document type links
|
# Document type links
|
||||||
menu_object.bind_links(links=[link_document_type_edit, link_document_type_filename_list, link_document_type_delete], sources=[DocumentType])
|
menu_object.bind_links(links=[link_document_type_edit, link_document_type_filename_list, link_acl_list, link_document_type_delete], sources=[DocumentType])
|
||||||
menu_object.bind_links(links=[link_document_type_filename_edit, link_document_type_filename_delete], sources=[DocumentTypeFilename])
|
menu_object.bind_links(links=[link_document_type_filename_edit, link_document_type_filename_delete], sources=[DocumentTypeFilename])
|
||||||
menu_secondary.bind_links(links=[link_document_type_list, link_document_type_create], sources=[DocumentType, 'documents:document_type_create', 'documents:document_type_list'])
|
menu_secondary.bind_links(links=[link_document_type_list, link_document_type_create], sources=[DocumentType, 'documents:document_type_create', 'documents:document_type_list'])
|
||||||
menu_sidebar.bind_links(links=[link_document_type_filename_create], sources=[DocumentTypeFilename, 'documents:document_type_filename_list', 'documents:document_type_filename_create'])
|
menu_sidebar.bind_links(links=[link_document_type_filename_create], sources=[DocumentTypeFilename, 'documents:document_type_filename_list', 'documents:document_type_filename_create'])
|
||||||
|
|||||||
Reference in New Issue
Block a user