From 78c374dfe656b3bb2dab7a6b4ad7ed111e185d01 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Tue, 21 Aug 2018 18:42:40 -0400 Subject: [PATCH] Add ACL support for metadata types. Signed-off-by: Roberto Rosario --- HISTORY.rst | 3 +- mayan/apps/metadata/apps.py | 14 +- mayan/apps/metadata/tests/literals.py | 2 + mayan/apps/metadata/tests/mixins.py | 55 +++++- mayan/apps/metadata/tests/test_views.py | 220 +++++++++++++++++++++++- mayan/apps/metadata/views.py | 21 ++- 6 files changed, 294 insertions(+), 21 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index d28566bda0..7c3c2e7c33 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -56,7 +56,8 @@ - Add new revertsettings management command. - Add new permission to edit setting via the UI. - Renamed setting LOCK_MANAGER_DEFAULT_BACKEND to LOCK_MANAGER_BACKEND. -- Add help text to settings. +- Add help texts to more setting options. +- Add ACL support for metadata types. 3.0.1 (2018-07-08) ================= diff --git a/mayan/apps/metadata/apps.py b/mayan/apps/metadata/apps.py index dc9439c26b..2782febbba 100644 --- a/mayan/apps/metadata/apps.py +++ b/mayan/apps/metadata/apps.py @@ -9,6 +9,8 @@ from django.db.models.signals import post_delete, post_save from django.utils.translation import ugettext_lazy as _ from acls import ModelPermission +from acls.links import link_acl_list +from acls.permissions import permission_acl_edit, permission_acl_view from common import ( MayanAppConfig, menu_facet, menu_multi_item, menu_object, menu_secondary, menu_setup, menu_sidebar @@ -147,10 +149,9 @@ class MetadataApp(MayanAppConfig): ) ModelPermission.register( model=MetadataType, permissions=( - permission_events_view, - permission_metadata_type_delete, - permission_metadata_type_edit, - permission_metadata_type_view + permission_acl_edit, permission_acl_view, + permission_events_view, permission_metadata_type_delete, + permission_metadata_type_edit, permission_metadata_type_view ) ) @@ -223,10 +224,9 @@ class MetadataApp(MayanAppConfig): menu_object.bind_links( links=( link_setup_metadata_type_edit, - link_setup_metadata_type_document_types, + link_setup_metadata_type_document_types, link_acl_list, link_object_event_types_user_subcriptions_list, - link_events_for_object, - link_setup_metadata_type_delete, + link_events_for_object, link_setup_metadata_type_delete, ), sources=(MetadataType,) ) menu_secondary.bind_links( diff --git a/mayan/apps/metadata/tests/literals.py b/mayan/apps/metadata/tests/literals.py index d7b7d93068..d35c2c0e84 100644 --- a/mayan/apps/metadata/tests/literals.py +++ b/mayan/apps/metadata/tests/literals.py @@ -11,8 +11,10 @@ TEST_INVALID_DATE = '___________' TEST_LOOKUP_TEMPLATE = '1,2,3' TEST_METADATA_TYPE_LABEL = 'test metadata type' TEST_METADATA_TYPE_LABEL_2 = 'test metadata type label 2' +TEST_METADATA_TYPE_LABEL_EDITED = 'test metadata type label edited' TEST_METADATA_TYPE_NAME = 'test' TEST_METADATA_TYPE_NAME_2 = 'test metadata type name 2' +TEST_METADATA_TYPE_NAME_EDITED = 'test metadata type name edited' TEST_METADATA_VALUE = 'test value' TEST_METADATA_VALUE_EDITED = 'test value edited' TEST_METADATA_VALUE_UNICODE = 'espaƱol' diff --git a/mayan/apps/metadata/tests/mixins.py b/mayan/apps/metadata/tests/mixins.py index b06c9ad290..0c6601ed02 100644 --- a/mayan/apps/metadata/tests/mixins.py +++ b/mayan/apps/metadata/tests/mixins.py @@ -2,7 +2,10 @@ from __future__ import unicode_literals from ..models import MetadataType -from .literals import TEST_METADATA_TYPE_NAME, TEST_METADATA_TYPE_LABEL +from .literals import ( + TEST_METADATA_TYPE_LABEL, TEST_METADATA_TYPE_LABEL_EDITED, + TEST_METADATA_TYPE_NAME, TEST_METADATA_TYPE_NAME_EDITED +) class MetadataTypeMixin(object): @@ -11,3 +14,53 @@ class MetadataTypeMixin(object): self.metadata_type = MetadataType.objects.create( name=TEST_METADATA_TYPE_NAME, label=TEST_METADATA_TYPE_LABEL ) + + +class MetadataTestsMixin(object): + def _create_metadata_type(self): + self.metadata_type = MetadataType.objects.create( + label=TEST_METADATA_TYPE_LABEL, + name=TEST_METADATA_TYPE_NAME + ) + + def _request_metadata_type_create_view(self): + return self.post( + viewname='metadata:setup_metadata_type_create', data={ + 'label': TEST_METADATA_TYPE_LABEL, + 'name': TEST_METADATA_TYPE_NAME + } + ) + + def _request_metadata_type_delete_view(self): + return self.post( + viewname='metadata:setup_metadata_type_delete', args=( + self.metadata_type.pk, + ), + ) + + def _request_metadata_type_edit_view(self): + return self.post( + viewname='metadata:setup_metadata_type_edit', args=( + self.metadata_type.pk,), data={ + 'label': TEST_METADATA_TYPE_LABEL_EDITED, + 'name': TEST_METADATA_TYPE_NAME_EDITED + } + ) + + def _request_metadata_type_relationship_edit_view(self): + # This request assumes there is only one document type and + # blindly sets the first form of the formset. + + return self.post( + viewname='metadata:setup_metadata_type_document_types', + args=(self.metadata_type.pk,), data={ + 'form-TOTAL_FORMS': '1', + 'form-INITIAL_FORMS': '0', + 'form-0-relationship_type': 'required', + } + ) + + def _request_metadata_type_list_view(self): + return self.get( + viewname='metadata:setup_metadata_type_list', + ) diff --git a/mayan/apps/metadata/tests/test_views.py b/mayan/apps/metadata/tests/test_views.py index ccc6578b71..44d5fa7963 100644 --- a/mayan/apps/metadata/tests/test_views.py +++ b/mayan/apps/metadata/tests/test_views.py @@ -3,26 +3,33 @@ from __future__ import unicode_literals import logging from django.core.files.base import File + +from common.tests import GenericViewTestCase from documents.models import DocumentType from documents.permissions import ( - permission_document_properties_edit, permission_document_view + permission_document_properties_edit, permission_document_type_edit, + permission_document_view ) from documents.tests import ( - GenericDocumentViewTestCase, TEST_DOCUMENT_TYPE_2_LABEL, - TEST_SMALL_DOCUMENT_PATH, + DocumentTestMixin, GenericDocumentViewTestCase, + TEST_DOCUMENT_TYPE_2_LABEL, TEST_SMALL_DOCUMENT_PATH, ) from ..models import MetadataType from ..permissions import ( permission_metadata_document_add, permission_metadata_document_remove, - permission_metadata_document_edit + permission_metadata_document_edit, permission_metadata_type_create, + permission_metadata_type_delete, permission_metadata_type_edit, + permission_metadata_type_view ) from .literals import ( TEST_DOCUMENT_METADATA_VALUE_2, TEST_METADATA_TYPE_LABEL, - TEST_METADATA_TYPE_LABEL_2, TEST_METADATA_TYPE_NAME, - TEST_METADATA_TYPE_NAME_2, TEST_METADATA_VALUE_EDITED + TEST_METADATA_TYPE_LABEL_2, TEST_METADATA_TYPE_LABEL_EDITED, + TEST_METADATA_TYPE_NAME, TEST_METADATA_TYPE_NAME_2, + TEST_METADATA_TYPE_NAME_EDITED, TEST_METADATA_VALUE_EDITED ) +from .mixins import MetadataTestsMixin class DocumentMetadataTestCase(GenericDocumentViewTestCase): @@ -295,3 +302,204 @@ class DocumentMetadataTestCase(GenericDocumentViewTestCase): ) self.assertContains(response, 'Edit', status_code=200) + + +class MetadataTypeViewTestCase(DocumentTestMixin, MetadataTestsMixin, GenericViewTestCase): + auto_create_document_type = False + auto_upload_document = False + + def test_metadata_type_create_view_no_permission(self): + self.login_user() + + response = self._request_metadata_type_create_view() + + self.assertEqual(response.status_code, 403) + + def test_metadata_type_create_view_with_access(self): + self.login_user() + + self.grant_permission(permission=permission_metadata_type_create) + response = self._request_metadata_type_create_view() + + self.assertEqual(response.status_code, 302) + + self.assertQuerysetEqual( + qs=MetadataType.objects.values('label', 'name'), + values=[ + { + 'label': TEST_METADATA_TYPE_LABEL, + 'name': TEST_METADATA_TYPE_NAME + } + ], transform=dict + ) + + def test_metadata_type_delete_view_no_permission(self): + self.login_user() + self._create_metadata_type() + + response = self._request_metadata_type_delete_view() + + self.assertEqual(response.status_code, 403) + self.assertQuerysetEqual( + qs=MetadataType.objects.values('label', 'name'), + values=[ + { + 'label': TEST_METADATA_TYPE_LABEL, + 'name': TEST_METADATA_TYPE_NAME + } + ], transform=dict + ) + + def test_metadata_type_delete_view_with_access(self): + self.login_user() + self._create_metadata_type() + + self.grant_access( + permission=permission_metadata_type_delete, + obj=self.metadata_type + ) + response = self._request_metadata_type_delete_view() + + self.assertEqual(response.status_code, 302) + + self.assertEqual(MetadataType.objects.count(), 0) + + def test_metadata_type_edit_view_no_permission(self): + self.login_user() + self._create_metadata_type() + + response = self._request_metadata_type_edit_view() + + self.assertEqual(response.status_code, 403) + self.assertQuerysetEqual( + qs=MetadataType.objects.values('label', 'name'), + values=[ + { + 'label': TEST_METADATA_TYPE_LABEL, + 'name': TEST_METADATA_TYPE_NAME + } + ], transform=dict + ) + + def test_metadata_type_edit_view_with_access(self): + self.login_user() + self._create_metadata_type() + + self.grant_access( + permission=permission_metadata_type_edit, + obj=self.metadata_type + ) + response = self._request_metadata_type_edit_view() + + self.assertEqual(response.status_code, 302) + + self.assertQuerysetEqual( + qs=MetadataType.objects.values('label', 'name'), + values=[ + { + 'label': TEST_METADATA_TYPE_LABEL_EDITED, + 'name': TEST_METADATA_TYPE_NAME_EDITED + } + ], transform=dict + ) + + def test_metadata_type_list_view_no_permission(self): + self.login_user() + self._create_metadata_type() + + response = self._request_metadata_type_list_view() + self.assertNotContains( + response=response, text=self.metadata_type, status_code=200 + ) + + def test_metadata_type_list_view_with_access(self): + self.login_user() + self._create_metadata_type() + + self.grant_access( + permission=permission_metadata_type_view, + obj=self.metadata_type + ) + response = self._request_metadata_type_list_view() + self.assertContains( + response=response, text=self.metadata_type, status_code=200 + ) + + def test_metadata_type_relationship_view_no_permission(self): + self.login_user() + self._create_metadata_type() + self.create_document_type() + self.upload_document() + + response = self._request_metadata_type_relationship_edit_view() + + self.assertEqual(response.status_code, 403) + + self.document_type.refresh_from_db() + + self.assertEqual(self.document_type.metadata.count(), 0) + + def test_metadata_type_relationship_view_with_document_type_access(self): + self.login_user() + self._create_metadata_type() + self.create_document_type() + self.upload_document() + + self.grant_access( + permission=permission_document_type_edit, obj=self.document_type + ) + + response = self._request_metadata_type_relationship_edit_view() + + self.assertEqual(response.status_code, 403) + + self.document_type.refresh_from_db() + + self.assertEqual(self.document_type.metadata.count(), 0) + + def test_metadata_type_relationship_view_with_metadata_type_access(self): + self.login_user() + self._create_metadata_type() + self.create_document_type() + self.upload_document() + + self.grant_access( + permission=permission_metadata_type_edit, obj=self.metadata_type + ) + + response = self._request_metadata_type_relationship_edit_view() + + self.assertEqual(response.status_code, 302) + + self.document_type.refresh_from_db() + + self.assertEqual(self.document_type.metadata.count(), 0) + + def test_metadata_type_relationship_view_with_metadata_type_and_document_type_access(self): + self.login_user() + self._create_metadata_type() + self.create_document_type() + self.upload_document() + + self.grant_access( + permission=permission_metadata_type_edit, obj=self.metadata_type + ) + self.grant_access( + permission=permission_document_type_edit, obj=self.document_type + ) + + response = self._request_metadata_type_relationship_edit_view() + + self.assertEqual(response.status_code, 302) + + self.document_type.refresh_from_db() + + self.assertQuerysetEqual( + qs=self.document_type.metadata.values('metadata_type', 'required'), + values=[ + { + 'metadata_type': self.metadata_type.pk, + 'required': True, + } + ], transform=dict + ) diff --git a/mayan/apps/metadata/views.py b/mayan/apps/metadata/views.py index 76ff225062..d1231e3518 100644 --- a/mayan/apps/metadata/views.py +++ b/mayan/apps/metadata/views.py @@ -538,8 +538,8 @@ class MetadataTypeCreateView(SingleObjectCreateView): class MetadataTypeDeleteView(SingleObjectDeleteView): model = MetadataType + object_permission = permission_metadata_type_delete post_action_redirect = reverse_lazy('metadata:setup_metadata_type_list') - view_permission = permission_metadata_type_delete def get_extra_context(self): return { @@ -552,8 +552,8 @@ class MetadataTypeDeleteView(SingleObjectDeleteView): class MetadataTypeEditView(SingleObjectEditView): form_class = MetadataTypeForm model = MetadataType + object_permission = permission_metadata_type_edit post_action_redirect = reverse_lazy('metadata:setup_metadata_type_list') - view_permission = permission_metadata_type_edit def get_extra_context(self): return { @@ -568,7 +568,7 @@ class MetadataTypeEditView(SingleObjectEditView): class MetadataTypeListView(SingleObjectListView): - view_permission = permission_metadata_type_view + object_permission = permission_metadata_type_view def get_extra_context(self): return { @@ -591,7 +591,6 @@ class SetupDocumentTypeMetadataTypes(FormView): main_model = 'document_type' model = DocumentType submodel = MetadataType - view_permission = permission_document_type_edit def form_valid(self, form): try: @@ -617,7 +616,13 @@ class SetupDocumentTypeMetadataTypes(FormView): } def get_object(self): - return get_object_or_404(self.model, pk=self.kwargs['pk']) + obj = get_object_or_404(self.model, pk=self.kwargs['pk']) + + AccessControlList.objects.check_access( + permissions=(permission_metadata_type_edit,), + user=self.request.user, obj=obj + ) + return obj def get_extra_context(self): return { @@ -647,7 +652,11 @@ class SetupDocumentTypeMetadataTypes(FormView): return reverse('documents:document_type_list') def get_queryset(self): - return self.submodel.objects.all() + queryset = self.submodel.objects.all() + return AccessControlList.objects.filter_by_access( + permission=permission_document_type_edit, + user=self.request.user, queryset=queryset + ) class SetupMetadataTypesDocumentTypes(SetupDocumentTypeMetadataTypes):