Add document ACLs workflow actions
Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
@@ -296,6 +296,8 @@
|
|||||||
* Add Latvian translation.
|
* Add Latvian translation.
|
||||||
* Support search model selection.
|
* Support search model selection.
|
||||||
* Support passing a queryset factory to the search model.
|
* Support passing a queryset factory to the search model.
|
||||||
|
* Add workflow actions to grant or remove permissions to
|
||||||
|
a document.
|
||||||
|
|
||||||
3.1.11 (2019-04-XX)
|
3.1.11 (2019-04-XX)
|
||||||
===================
|
===================
|
||||||
|
|||||||
@@ -725,6 +725,8 @@ Other changes
|
|||||||
- Add Latvian translation.
|
- Add Latvian translation.
|
||||||
- Support search model selection.
|
- Support search model selection.
|
||||||
- Support passing a queryset factory to the search model.
|
- Support passing a queryset factory to the search model.
|
||||||
|
- Add workflow actions to grant or remove permissions to
|
||||||
|
a document.
|
||||||
|
|
||||||
|
|
||||||
Removals
|
Removals
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
from __future__ import unicode_literals, absolute_import
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
from django.utils.encoding import force_text
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -53,8 +55,21 @@ class ModelPermission(object):
|
|||||||
return cls._registry.keys()
|
return cls._registry.keys()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_for_class(cls, klass):
|
def get_for_class(cls, klass, as_choices=False):
|
||||||
return cls._registry.get(klass, ())
|
if as_choices:
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for namespace, permissions in itertools.groupby(cls.get_for_class(klass=klass, as_choices=False), lambda entry: entry.namespace):
|
||||||
|
permission_options = [
|
||||||
|
(force_text(permission.pk), permission) for permission in permissions
|
||||||
|
]
|
||||||
|
results.append(
|
||||||
|
(namespace, permission_options)
|
||||||
|
)
|
||||||
|
|
||||||
|
return results
|
||||||
|
else:
|
||||||
|
return cls._registry.get(klass, ())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_for_instance(cls, instance):
|
def get_for_instance(cls, instance):
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from mayan.apps.document_states.tests.test_actions import ActionTestCase
|
from mayan.apps.document_states.tests.test_actions import ActionTestCase
|
||||||
from mayan.apps.documents.permissions import permission_document_view
|
from mayan.apps.documents.permissions import permission_document_view
|
||||||
|
|
||||||
from ..workflow_actions import GrantAccessAction, RevokeAccessAction
|
from ..workflow_actions import (
|
||||||
|
GrantAccessAction, GrantDocumentAccessAction, RevokeAccessAction,
|
||||||
|
RevokeDocumentAccessAction
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ACLActionTestCase(ActionTestCase):
|
class ACLActionTestCase(ActionTestCase):
|
||||||
@@ -29,6 +32,22 @@ class ACLActionTestCase(ActionTestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(self.test_document.acls.first().role, self._test_case_role)
|
self.assertEqual(self.test_document.acls.first().role, self._test_case_role)
|
||||||
|
|
||||||
|
def test_grant_document_access_action(self):
|
||||||
|
action = GrantDocumentAccessAction(
|
||||||
|
form_data={
|
||||||
|
'roles': [self._test_case_role.pk],
|
||||||
|
'permissions': [permission_document_view.pk],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
action.execute(context={'document': self.test_document})
|
||||||
|
|
||||||
|
self.assertEqual(self.test_document.acls.count(), 1)
|
||||||
|
self.assertEqual(
|
||||||
|
list(self.test_document.acls.first().permissions.all()),
|
||||||
|
[permission_document_view.stored_permission]
|
||||||
|
)
|
||||||
|
self.assertEqual(self.test_document.acls.first().role, self._test_case_role)
|
||||||
|
|
||||||
def test_revoke_access_action(self):
|
def test_revoke_access_action(self):
|
||||||
self.grant_access(
|
self.grant_access(
|
||||||
obj=self.test_document, permission=permission_document_view
|
obj=self.test_document, permission=permission_document_view
|
||||||
@@ -47,3 +66,18 @@ class ACLActionTestCase(ActionTestCase):
|
|||||||
action.execute(context={'entry_log': self.entry_log})
|
action.execute(context={'entry_log': self.entry_log})
|
||||||
|
|
||||||
self.assertEqual(self.test_document.acls.count(), 0)
|
self.assertEqual(self.test_document.acls.count(), 0)
|
||||||
|
|
||||||
|
def test_revoke_document_access_action(self):
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document, permission=permission_document_view
|
||||||
|
)
|
||||||
|
|
||||||
|
action = RevokeDocumentAccessAction(
|
||||||
|
form_data={
|
||||||
|
'roles': [self._test_case_role.pk],
|
||||||
|
'permissions': [permission_document_view.pk],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
action.execute(context={'document': self.test_document})
|
||||||
|
|
||||||
|
self.assertEqual(self.test_document.acls.count(), 0)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from mayan.apps.acls.models import AccessControlList
|
from mayan.apps.acls.models import AccessControlList
|
||||||
|
from mayan.apps.documents.models import Document
|
||||||
from mayan.apps.document_states.classes import WorkflowAction
|
from mayan.apps.document_states.classes import WorkflowAction
|
||||||
from mayan.apps.permissions.classes import Permission
|
from mayan.apps.permissions.classes import Permission
|
||||||
from mayan.apps.permissions.models import Role
|
from mayan.apps.permissions.models import Role
|
||||||
@@ -149,3 +150,74 @@ class RevokeAccessAction(GrantAccessAction):
|
|||||||
AccessControlList.objects.revoke(
|
AccessControlList.objects.revoke(
|
||||||
obj=self.obj, permission=permission, role=role
|
obj=self.obj, permission=permission, role=role
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GrantDocumentAccessAction(WorkflowAction):
|
||||||
|
fields = {
|
||||||
|
'roles': {
|
||||||
|
'label': _('Roles'),
|
||||||
|
'class': 'django.forms.ModelMultipleChoiceField', 'kwargs': {
|
||||||
|
'help_text': _('Roles whose access will be modified.'),
|
||||||
|
'queryset': Role.objects.all(), 'required': True
|
||||||
|
}
|
||||||
|
}, 'permissions': {
|
||||||
|
'label': _('Permissions'),
|
||||||
|
'class': 'django.forms.MultipleChoiceField', 'kwargs': {
|
||||||
|
'help_text': _(
|
||||||
|
'Permissions to grant/revoke to/from the role for the '
|
||||||
|
'object selected above.'
|
||||||
|
), 'choices': (),
|
||||||
|
'required': True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
field_order = ('roles', 'permissions')
|
||||||
|
label = _('Grant document access')
|
||||||
|
widgets = {
|
||||||
|
'roles': {
|
||||||
|
'class': 'django.forms.widgets.SelectMultiple', 'kwargs': {
|
||||||
|
'attrs': {'class': 'select2'},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'permissions': {
|
||||||
|
'class': 'django.forms.widgets.SelectMultiple', 'kwargs': {
|
||||||
|
'attrs': {'class': 'select2'},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_form_schema(self, *args, **kwargs):
|
||||||
|
self.fields['permissions']['kwargs']['choices'] = ModelPermission.get_for_class(
|
||||||
|
klass=Document, as_choices=True
|
||||||
|
)
|
||||||
|
return super(GrantDocumentAccessAction, self).get_form_schema(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_execute_data(self):
|
||||||
|
self.roles = Role.objects.filter(pk__in=self.form_data['roles'])
|
||||||
|
self.permissions = [
|
||||||
|
Permission.get(
|
||||||
|
pk=permission, proxy_only=True
|
||||||
|
) for permission in self.form_data['permissions']
|
||||||
|
]
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
self.get_execute_data()
|
||||||
|
|
||||||
|
for role in self.roles:
|
||||||
|
for permission in self.permissions:
|
||||||
|
AccessControlList.objects.grant(
|
||||||
|
obj=context['document'], permission=permission, role=role
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RevokeDocumentAccessAction(GrantDocumentAccessAction):
|
||||||
|
label = _('Revoke document access')
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
self.get_execute_data()
|
||||||
|
|
||||||
|
for role in self.roles:
|
||||||
|
for permission in self.permissions:
|
||||||
|
AccessControlList.objects.revoke(
|
||||||
|
obj=context['document'], permission=permission, role=role
|
||||||
|
)
|
||||||
|
|||||||
@@ -37,18 +37,26 @@ def handler_trigger_transition(sender, **kwargs):
|
|||||||
app_label='document_states', model_name='WorkflowTransition'
|
app_label='document_states', model_name='WorkflowTransition'
|
||||||
)
|
)
|
||||||
|
|
||||||
trigger_transitions = WorkflowTransition.objects.filter(trigger_events__event_type__name=kwargs['instance'].verb)
|
trigger_transitions = WorkflowTransition.objects.filter(
|
||||||
|
trigger_events__event_type__name=kwargs['instance'].verb
|
||||||
|
)
|
||||||
|
|
||||||
if isinstance(action.target, Document):
|
if isinstance(action.target, Document):
|
||||||
workflow_instances = WorkflowInstance.objects.filter(workflow__transitions__in=trigger_transitions, document=action.target).distinct()
|
workflow_instances = WorkflowInstance.objects.filter(
|
||||||
|
workflow__transitions__in=trigger_transitions, document=action.target
|
||||||
|
).distinct()
|
||||||
elif isinstance(action.action_object, Document):
|
elif isinstance(action.action_object, Document):
|
||||||
workflow_instances = WorkflowInstance.objects.filter(workflow__transitions__in=trigger_transitions, document=action.action_object).distinct()
|
workflow_instances = WorkflowInstance.objects.filter(
|
||||||
|
workflow__transitions__in=trigger_transitions, document=action.action_object
|
||||||
|
).distinct()
|
||||||
else:
|
else:
|
||||||
workflow_instances = WorkflowInstance.objects.none()
|
workflow_instances = WorkflowInstance.objects.none()
|
||||||
|
|
||||||
for workflow_instance in workflow_instances:
|
for workflow_instance in workflow_instances:
|
||||||
# Select the first transition that is valid for this workflow state
|
# Select the first transition that is valid for this workflow state
|
||||||
valid_transitions = list(set(trigger_transitions) & set(workflow_instance.get_transition_choices()))
|
valid_transitions = list(
|
||||||
|
set(trigger_transitions) & set(workflow_instance.get_transition_choices())
|
||||||
|
)
|
||||||
if valid_transitions:
|
if valid_transitions:
|
||||||
workflow_instance.do_transition(
|
workflow_instance.do_transition(
|
||||||
comment=_('Event trigger: %s') % EventType.get(name=action.verb).label,
|
comment=_('Event trigger: %s') % EventType.get(name=action.verb).label,
|
||||||
|
|||||||
Reference in New Issue
Block a user