ACL app updates

Update the ACL permission view to use the new AddRemoveView.

Add ACL created and ACL edit events.

Add permission adding and removal accesors to the ACL model.

Signed-off-by: Roberto Rosario <Roberto.Rosario@mayan-edms.com>
This commit is contained in:
Roberto Rosario
2019-02-14 02:30:51 -04:00
parent 6a57a5a7de
commit 18e5ee1e4f
5 changed files with 132 additions and 102 deletions

View File

@@ -2,12 +2,16 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from mayan.apps.common import (
MayanAppConfig, menu_object, menu_secondary, menu_sidebar
from mayan.apps.common import MayanAppConfig, menu_object, menu_secondary
from mayan.apps.events import ModelEventType
from mayan.apps.events.links import (
link_events_for_object, link_object_event_types_user_subcriptions_list
)
from mayan.apps.events.permissions import permission_events_view
from mayan.apps.navigation import SourceColumn
from .classes import ModelPermission
from .events import event_acl_created, event_acl_edited
from .links import link_acl_create, link_acl_delete, link_acl_permissions
@@ -21,25 +25,33 @@ 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
)
ModelPermission.register_inheritance(
model=AccessControlList, related='content_object',
)
SourceColumn(
attribute='role', is_identifier=True, is_sortable=True,
source=AccessControlList
)
SourceColumn(
attribute='get_permission_titles', include_label=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_sidebar.bind_links(
menu_secondary.bind_links(
links=(link_acl_create,), sources=('acls:acl_list',)
)
registry.register(AccessControlList)

16
mayan/apps/acls/events.py Normal file
View File

@@ -0,0 +1,16 @@
from __future__ import absolute_import, unicode_literals
from django.utils.translation import ugettext_lazy as _
from mayan.apps.events import EventTypeNamespace
namespace = EventTypeNamespace(
label=_('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'
)

View File

@@ -151,7 +151,7 @@ class AccessControlListManager(models.Manager):
else:
raise PermissionDenied
def get_inherited_permissions(self, role, obj):
def get_inherited_permissions(self, obj, role):
try:
instance = obj.first()
except AttributeError:
@@ -177,11 +177,11 @@ class AccessControlListManager(models.Manager):
parent_object = return_related(
instance=instance, related_field=parent_accessor
)
content_type = ContentType.objects.get_for_model(parent_object)
content_type = ContentType.objects.get_for_model(model=parent_object)
try:
return self.get(
role=role, content_type=content_type,
object_id=parent_object.pk
content_type=content_type, object_id=parent_object.pk,
role=role
).permissions.all()
except self.model.DoesNotExist:
return StoredPermission.objects.none()

View File

@@ -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 force_text, 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__)
@@ -58,11 +59,10 @@ class AccessControlList(models.Model):
def __str__(self):
return _(
'Permissions "%(permissions)s" to role "%(role)s" for "%(object)s"'
'Role "%(role)s" permission\'s for "%(object)s"'
) % {
'permissions': self.get_permission_titles(),
'object': self.content_object,
'role': self.role
'role': self.role,
}
def get_absolute_url(self):
@@ -85,3 +85,32 @@ class AccessControlList(models.Model):
return result or _('None')
get_permission_titles.short_description = _('Permissions')
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
)

View File

@@ -1,9 +1,7 @@
from __future__ import absolute_import, unicode_literals
import itertools
import logging
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
@@ -13,11 +11,10 @@ from mayan.apps.common.mixins import (
ContentTypeViewMixin, ExternalObjectMixin
)
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
@@ -60,7 +57,8 @@ class ACLCreateView(ContentTypeViewMixin, ExternalObjectMixin, SingleObjectCreat
'queryset': Role.objects.exclude(
pk__in=self.get_external_object().acls.values('role')
),
'widget_attributes': {'class': 'select2'}
'widget_attributes': {'class': 'select2'},
'_user': self.request.user
}
def get_instance_extra_data(self):
@@ -140,109 +138,84 @@ class ACLListView(ContentTypeViewMixin, ExternalObjectMixin, SingleObjectListVie
return self.get_external_object().acls.all()
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 = 'acl_id'
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 = {}
entries = sorted(
entries, key=lambda x: (
x.volatile_permission.namespace.label,
x.volatile_permission.label
)
# Sort permissions by their translatable label
object_list = sorted(
queryset, key=lambda permission: permission.volatile_permission.label
)
for namespace, permissions in itertools.groupby(entries, lambda entry: entry.namespace):
permission_options = [
(force_text(permission.pk), permission) for permission in permissions
]
results.append(
(PermissionNamespace.get(name=namespace), permission_options)
# Group permissions by namespace
for permission in object_list:
namespaces_dictionary.setdefault(
permission.volatile_permission.namespace.label,
[]
)
namespaces_dictionary[permission.volatile_permission.namespace.label].append(
(permission.pk, force_text(permission))
)
return results
# Sort permissions by their translatable namespace label
return sorted(namespaces_dictionary.items())
def add(self, item):
permission = get_object_or_404(klass=StoredPermission, pk=item)
self.get_object().permissions.add(permission)
def get_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. 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):
acl = self.get_object()
return {
'acl': acl,
'object': acl.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': acl.role,
'object': acl.content_object,
'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 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
)
return StoredPermission.objects.filter(pk__in=merged_pks)
def get_object(self):
return get_object_or_404(
klass=self.get_queryset(), pk=self.kwargs['acl_id']
)
def get_queryset(self):
return AccessControlList.objects.restrict_queryset(
permission=permission_acl_edit,
queryset=AccessControlList.objects.all(), user=self.request.user
)
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 and '
'can\'t be removed from this view, they need to be removed '
'from the parent object\'s ACL view.'
)
return self.right_list_help_text
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 = super(ACLPermissionsView, self).get_list_added_queryset()
return (
queryset | self.main_object.get_inherited_permissions()
).distinct()
def left_list(self):
Permission.refresh()
return ACLPermissionsView.generate_choices(self.get_available_list())
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
)