Update ACL view to use new AddRemoveView
Add ACL created and edited events. Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
@@ -185,6 +185,8 @@
|
||||
* Increase default title truncation length to 120 characters.
|
||||
* Improve inherited permission computation.
|
||||
* Add test case mixin that produces ephimeral models.
|
||||
* Update ACL permissions view to use the new AddRemoveView class.
|
||||
* Add ACL created and edited events.
|
||||
|
||||
3.1.11 (2019-04-XX)
|
||||
===================
|
||||
|
||||
@@ -217,6 +217,8 @@ Other changes
|
||||
* Increase default title truncation length to 120 characters.
|
||||
* Improve inherited permission computation.
|
||||
* Add test case mixin that produces ephimeral models.
|
||||
* Update ACL permissions view to use the new AddRemoveView class.
|
||||
* Add ACL created and edited events.
|
||||
|
||||
Removals
|
||||
--------
|
||||
|
||||
@@ -4,8 +4,13 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.common.apps import MayanAppConfig
|
||||
from mayan.apps.common.menus import menu_object, menu_secondary
|
||||
from mayan.apps.events.classes import ModelEventType
|
||||
from mayan.apps.events.links import (
|
||||
link_events_for_object, link_object_event_types_user_subcriptions_list
|
||||
)
|
||||
from mayan.apps.navigation.classes import SourceColumn
|
||||
|
||||
from .events import event_acl_created, event_acl_edited
|
||||
from .links import link_acl_create, link_acl_delete, link_acl_permissions
|
||||
|
||||
|
||||
@@ -19,17 +24,29 @@ class ACLsApp(MayanAppConfig):
|
||||
|
||||
def ready(self):
|
||||
super(ACLsApp, self).ready()
|
||||
from actstream import registry
|
||||
|
||||
AccessControlList = self.get_model(model_name='AccessControlList')
|
||||
|
||||
ModelEventType.register(
|
||||
event_types=(event_acl_created, event_acl_edited),
|
||||
model=AccessControlList
|
||||
)
|
||||
|
||||
SourceColumn(
|
||||
attribute='role', is_sortable=True, source=AccessControlList,
|
||||
)
|
||||
|
||||
menu_object.bind_links(
|
||||
links=(link_acl_permissions, link_acl_delete),
|
||||
links=(
|
||||
link_acl_permissions, link_acl_delete,
|
||||
link_events_for_object,
|
||||
link_object_event_types_user_subcriptions_list
|
||||
),
|
||||
sources=(AccessControlList,)
|
||||
)
|
||||
menu_secondary.bind_links(
|
||||
links=(link_acl_create,), sources=('acls:acl_list',)
|
||||
)
|
||||
|
||||
registry.register(AccessControlList)
|
||||
|
||||
16
mayan/apps/acls/events.py
Normal file
16
mayan/apps/acls/events.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.events.classes import EventTypeNamespace
|
||||
|
||||
namespace = EventTypeNamespace(
|
||||
label=_('Access control lists'), name='acls'
|
||||
)
|
||||
|
||||
event_acl_created = namespace.add_event_type(
|
||||
label=_('ACL created'), name='acl_created'
|
||||
)
|
||||
event_acl_edited = namespace.add_event_type(
|
||||
label=_('ACL edited'), name='acl_edited'
|
||||
)
|
||||
@@ -4,13 +4,14 @@ import logging
|
||||
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.db import models, transaction
|
||||
from django.urls import reverse
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.permissions.models import Role, StoredPermission
|
||||
|
||||
from .events import event_acl_created, event_acl_edited
|
||||
from .managers import AccessControlListManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -73,3 +74,32 @@ class AccessControlList(models.Model):
|
||||
return AccessControlList.objects.get_inherited_permissions(
|
||||
obj=self.content_object, role=self.role
|
||||
)
|
||||
|
||||
def permissions_add(self, queryset, _user=None):
|
||||
with transaction.atomic():
|
||||
event_acl_edited.commit(
|
||||
actor=_user, target=self
|
||||
)
|
||||
self.permissions.add(*queryset)
|
||||
|
||||
def permissions_remove(self, queryset, _user=None):
|
||||
with transaction.atomic():
|
||||
event_acl_edited.commit(
|
||||
actor=_user, target=self
|
||||
)
|
||||
self.permissions.remove(*queryset)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
_user = kwargs.pop('_user', None)
|
||||
|
||||
with transaction.atomic():
|
||||
is_new = not self.pk
|
||||
super(AccessControlList, self).save(*args, **kwargs)
|
||||
if is_new:
|
||||
event_acl_created.commit(
|
||||
actor=_user, target=self
|
||||
)
|
||||
else:
|
||||
event_acl_edited.commit(
|
||||
actor=_user, target=self
|
||||
)
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
from mayan.apps.acls.models import AccessControlList
|
||||
from mayan.apps.common.tests.mixins import TestModelTestMixin
|
||||
from mayan.apps.permissions.tests.mixins import (
|
||||
PermissionTestMixin, RoleTestCaseMixin, RoleTestMixin
|
||||
)
|
||||
from mayan.apps.user_management.tests.mixins import UserTestCaseMixin
|
||||
|
||||
from ..classes import ModelPermission
|
||||
from ..models import AccessControlList
|
||||
from ..permissions import permission_acl_edit, permission_acl_view
|
||||
|
||||
|
||||
class ACLTestCaseMixin(RoleTestCaseMixin, UserTestCaseMixin):
|
||||
def setUp(self):
|
||||
@@ -27,7 +32,7 @@ class ACLTestCaseMixin(RoleTestCaseMixin, UserTestCaseMixin):
|
||||
)
|
||||
|
||||
|
||||
class ACLTestMixin(PermissionTestMixin, RoleTestMixin):
|
||||
class ACLTestMixin(PermissionTestMixin, RoleTestMixin, TestModelTestMixin):
|
||||
auto_create_test_role = True
|
||||
|
||||
def _create_test_acl(self):
|
||||
@@ -39,3 +44,32 @@ class ACLTestMixin(PermissionTestMixin, RoleTestMixin):
|
||||
super(ACLTestMixin, self).setUp()
|
||||
if self.auto_create_test_role:
|
||||
self._create_test_role()
|
||||
|
||||
def _inject_test_object_content_type(self):
|
||||
self.test_object_content_type = ContentType.objects.get_for_model(
|
||||
model=self.test_object
|
||||
)
|
||||
|
||||
self.test_content_object_view_kwargs = {
|
||||
'app_label': self.test_object_content_type.app_label,
|
||||
'model_name': self.test_object_content_type.model,
|
||||
'object_id': self.test_object.pk
|
||||
}
|
||||
|
||||
def _setup_test_object(self):
|
||||
self._create_test_model()
|
||||
self._create_test_object()
|
||||
ModelPermission.register(
|
||||
model=self.test_object._meta.model, permissions=(
|
||||
permission_acl_edit, permission_acl_view,
|
||||
)
|
||||
)
|
||||
|
||||
self._create_test_permission()
|
||||
ModelPermission.register(
|
||||
model=self.test_object._meta.model, permissions=(
|
||||
self.test_permission,
|
||||
)
|
||||
)
|
||||
|
||||
self._inject_test_object_content_type()
|
||||
|
||||
@@ -10,6 +10,7 @@ from mayan.apps.documents.tests import (
|
||||
TEST_DOCUMENT_TYPE_2_LABEL
|
||||
)
|
||||
|
||||
from ..classes import ModelPermission
|
||||
from ..models import AccessControlList
|
||||
|
||||
from .mixins import ACLTestMixin
|
||||
@@ -148,3 +149,36 @@ class PermissionTestCase(ACLTestMixin, BaseTestCase):
|
||||
self.assertTrue(self.document_1 in result)
|
||||
self.assertTrue(self.document_2 in result)
|
||||
self.assertTrue(self.document_3 in result)
|
||||
|
||||
|
||||
class InheritedPermissionTestCase(ACLTestMixin, BaseTestCase):
|
||||
def test_retrieve_inherited_role_permission_not_model_applicable(self):
|
||||
self._create_test_model()
|
||||
self.test_object = self.TestModel.objects.create()
|
||||
self._create_test_acl()
|
||||
self._create_test_permission()
|
||||
|
||||
self.test_role.grant(permission=self.test_permission)
|
||||
|
||||
queryset = AccessControlList.objects.get_inherited_permissions(
|
||||
obj=self.test_object, role=self.test_role
|
||||
)
|
||||
self.assertTrue(self.test_permission.stored_permission not in queryset)
|
||||
|
||||
def test_retrieve_inherited_role_permission_model_applicable(self):
|
||||
self._create_test_model()
|
||||
self.test_object = self.TestModel.objects.create()
|
||||
self._create_test_acl()
|
||||
self._create_test_permission()
|
||||
|
||||
ModelPermission.register(
|
||||
model=self.test_object._meta.model, permissions=(
|
||||
self.test_permission,
|
||||
)
|
||||
)
|
||||
self.test_role.grant(permission=self.test_permission)
|
||||
|
||||
queryset = AccessControlList.objects.get_inherited_permissions(
|
||||
obj=self.test_object, role=self.test_role
|
||||
)
|
||||
self.assertTrue(self.test_permission.stored_permission in queryset)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
@@ -12,11 +11,10 @@ from django.utils.encoding import force_text
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.common.generics import (
|
||||
AssignRemoveView, SingleObjectCreateView, SingleObjectDeleteView,
|
||||
AddRemoveView, SingleObjectCreateView, SingleObjectDeleteView,
|
||||
SingleObjectListView
|
||||
)
|
||||
from mayan.apps.permissions import Permission, PermissionNamespace
|
||||
from mayan.apps.permissions.models import Role, StoredPermission
|
||||
from mayan.apps.permissions.models import Role
|
||||
|
||||
from .classes import ModelPermission
|
||||
from .forms import ACLCreateForm
|
||||
@@ -96,6 +94,8 @@ class ACLDeleteView(SingleObjectDeleteView):
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'acl': self.get_object(),
|
||||
'navigation_object_list': ('object', 'acl'),
|
||||
'object': self.get_object().content_object,
|
||||
'title': _('Delete ACL: %s') % self.get_object(),
|
||||
}
|
||||
@@ -161,102 +161,88 @@ class ACLListView(SingleObjectListView):
|
||||
)
|
||||
|
||||
|
||||
class ACLPermissionsView(AssignRemoveView):
|
||||
grouped = True
|
||||
left_list_title = _('Available permissions')
|
||||
right_list_title = _('Granted permissions')
|
||||
class ACLPermissionsView(AddRemoveView):
|
||||
action_add_method = 'permissions_add'
|
||||
action_remove_method = 'permissions_remove'
|
||||
main_object_model = AccessControlList
|
||||
main_object_permission = permission_acl_edit
|
||||
main_object_pk_url_kwarg = 'pk'
|
||||
list_added_title = _('Granted permissions')
|
||||
list_available_title = _('Available permissions')
|
||||
related_field = 'permissions'
|
||||
|
||||
@staticmethod
|
||||
def generate_choices(entries):
|
||||
results = []
|
||||
def generate_choices(self, queryset):
|
||||
namespaces_dictionary = {}
|
||||
|
||||
# Sort permissions by their translatable label
|
||||
entries = sorted(
|
||||
entries, key=lambda permission: permission.volatile_permission.label
|
||||
object_list = sorted(
|
||||
queryset, key=lambda permission: permission.volatile_permission.label
|
||||
)
|
||||
|
||||
# Group permissions by namespace
|
||||
for namespace, permissions in itertools.groupby(entries, lambda entry: entry.namespace):
|
||||
permission_options = [
|
||||
(force_text(permission.pk), permission) for permission in permissions
|
||||
]
|
||||
results.append(
|
||||
(PermissionNamespace.get(name=namespace), permission_options)
|
||||
for permission in object_list:
|
||||
namespaces_dictionary.setdefault(
|
||||
permission.volatile_permission.namespace.label,
|
||||
[]
|
||||
)
|
||||
namespaces_dictionary[permission.volatile_permission.namespace.label].append(
|
||||
(permission.pk, force_text(permission))
|
||||
)
|
||||
|
||||
return results
|
||||
# Sort permissions by their translatable namespace label
|
||||
return sorted(namespaces_dictionary.items())
|
||||
|
||||
def add(self, item):
|
||||
permission = get_object_or_404(klass=StoredPermission, pk=item)
|
||||
self.get_object().permissions.add(permission)
|
||||
|
||||
def 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
|
||||
).exclude(id__in=self.get_granted_list().values_list('pk', flat=True))
|
||||
def get_actions_extra_kwargs(self):
|
||||
return {'_user': self.request.user}
|
||||
|
||||
def get_disabled_choices(self):
|
||||
"""
|
||||
Get permissions from a parent's acls but remove the permissions we
|
||||
already hold for this object
|
||||
Get permissions from a parent's ACLs or directly granted to the role.
|
||||
We return a list since that is what the form widget's can process.
|
||||
"""
|
||||
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)
|
||||
)
|
||||
return self.main_object.get_inherited_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,
|
||||
},
|
||||
'acl': self.main_object,
|
||||
'object': self.main_object.content_object,
|
||||
'navigation_object_list': ('object', 'acl'),
|
||||
'title': _('Role "%(role)s" permission\'s for "%(object)s".') % {
|
||||
'role': self.main_object.role,
|
||||
'object': self.main_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 get_object(self):
|
||||
return get_object_or_404(klass=AccessControlList, pk=self.kwargs['pk'])
|
||||
|
||||
def get_right_list_help_text(self):
|
||||
if self.get_object().get_inherited_permissions():
|
||||
def get_list_added_help_text(self):
|
||||
if self.main_object.get_inherited_permissions():
|
||||
return _(
|
||||
'Disabled permissions are inherited from a parent object.'
|
||||
'Disabled permissions are inherited from a parent object or '
|
||||
'directly granted to the role and can\'t be removed from this '
|
||||
'view. Inherited permissions need to be removed from the '
|
||||
'parent object\'s ACL or from them role via the Setup menu.'
|
||||
)
|
||||
else:
|
||||
return super(ACLPermissionsView, self).get_list_added_help_text()
|
||||
|
||||
return None
|
||||
def get_list_added_queryset(self):
|
||||
"""
|
||||
Merge of permissions we hold for this object and the permissions we
|
||||
hold for this object's parents via another ACL. .distinct() is added
|
||||
in case the permission was added to the ACL and then added to a
|
||||
parent ACL's and thus inherited and would appear twice. If
|
||||
order to remove the double permission from the ACL it would need to be
|
||||
remove from the parent first to enable the choice in the form,
|
||||
remove it from the ACL and then re-add it to the parent ACL.
|
||||
"""
|
||||
queryset_acl = super(ACLPermissionsView, self).get_list_added_queryset()
|
||||
|
||||
def left_list(self):
|
||||
Permission.refresh()
|
||||
return ACLPermissionsView.generate_choices(self.get_available_list())
|
||||
return (
|
||||
queryset_acl | self.main_object.get_inherited_permissions()
|
||||
).distinct()
|
||||
|
||||
def remove(self, item):
|
||||
permission = get_object_or_404(klass=StoredPermission, pk=item)
|
||||
self.get_object().permissions.remove(permission)
|
||||
|
||||
def right_list(self):
|
||||
return ACLPermissionsView.generate_choices(self.get_granted_list())
|
||||
def get_secondary_object_source_queryset(self):
|
||||
return ModelPermission.get_for_instance(
|
||||
instance=self.main_object.content_object
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user