diff --git a/HISTORY.rst b/HISTORY.rst index 020ffbcc56..e91047e81c 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -212,7 +212,17 @@ * Add new app to handle all dependencies. * Remove the licenses.py module and replace it with a dependencies.py module. - +* Backport ACL computation improvements. +* Remove model permission proxy models. +* Remove related access control argument. This is + now handled by the related field registration. +* Allow nested access control checking. +* check_access's permissions argument must now be + an interable. +* Remove permissions_related from links. +* Remove mayan_permission_attribute_check from + API permission. + 3.1.11 (2019-04-XX) =================== * Fix multiple tag selection wizard step. diff --git a/docs/releases/3.2.rst b/docs/releases/3.2.rst index d8d222844e..93ddf1bafc 100644 --- a/docs/releases/3.2.rst +++ b/docs/releases/3.2.rst @@ -245,6 +245,16 @@ Other changes * Add new app to handle all dependencies. * Remove the licenses.py module and replace it with a dependencies.py module. +* Backport ACL computation improvements. +* Remove model permission proxy models. +* Remove related access control argument. This is + now handled by the related field registration. +* Allow nested access control checking. +* check_access's permissions argument must now be + an interable. +* Remove permissions_related from links. +* Remove mayan_permission_attribute_check from + API permission. Removals -------- diff --git a/mayan/apps/acls/api_views.py b/mayan/apps/acls/api_views.py index 62e3cd0709..95b610e962 100644 --- a/mayan/apps/acls/api_views.py +++ b/mayan/apps/acls/api_views.py @@ -35,8 +35,8 @@ class APIObjectACLListView(generics.ListCreateAPIView): permission_required = permission_acl_edit AccessControlList.objects.check_access( - permissions=permission_required, user=self.request.user, - obj=content_object + obj=content_object, permissions=(permission_required,), + user=self.request.user ) return content_object @@ -94,8 +94,8 @@ class APIObjectACLView(generics.RetrieveDestroyAPIView): ) AccessControlList.objects.check_access( - permissions=permission_required, user=self.request.user, - obj=content_object + obj=content_object, permissions=(permission_required,), + user=self.request.user ) return content_object @@ -130,7 +130,8 @@ class APIObjectACLPermissionListView(generics.ListCreateAPIView): permission = permission_acl_edit AccessControlList.objects.check_access( - obj=content_object, permissions=permission, user=self.request.user + obj=content_object, permissions=(permission,), + user=self.request.user ) return content_object @@ -191,7 +192,8 @@ class APIObjectACLPermissionView(generics.RetrieveDestroyAPIView): permission = permission_acl_edit AccessControlList.objects.check_access( - obj=content_object, permissions=permission, user=self.request.user + obj=content_object, permissions=(permission,), + user=self.request.user ) return content_object diff --git a/mayan/apps/acls/apps.py b/mayan/apps/acls/apps.py index d097e0e750..59e81c12c4 100644 --- a/mayan/apps/acls/apps.py +++ b/mayan/apps/acls/apps.py @@ -10,6 +10,7 @@ from mayan.apps.events.links import ( ) from mayan.apps.navigation.classes 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 @@ -33,6 +34,10 @@ class ACLsApp(MayanAppConfig): model=AccessControlList ) + ModelPermission.register_inheritance( + model=AccessControlList, related='content_object', + ) + SourceColumn( attribute='role', is_sortable=True, source=AccessControlList, ) diff --git a/mayan/apps/acls/classes.py b/mayan/apps/acls/classes.py index d7ffae9b28..f730a8da60 100644 --- a/mayan/apps/acls/classes.py +++ b/mayan/apps/acls/classes.py @@ -8,8 +8,8 @@ logger = logging.getLogger(__name__) class ModelPermission(object): + _functions = {} _inheritances = {} - _proxies = {} _registry = {} @classmethod @@ -63,24 +63,23 @@ class ModelPermission(object): if class_permissions: permissions.extend(class_permissions) - proxy = cls._proxies.get(type(instance)) - - if proxy: - permissions.extend(cls._registry.get(proxy)) - pks = [ permission.stored_permission.pk for permission in set(permissions) ] 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 + def get_function(cls, model): + return cls._functions[model] @classmethod def get_inheritance(cls, model): return cls._inheritances[model] + + @classmethod + def register_function(cls, model, function): + cls._functions[model] = function + + @classmethod + def register_inheritance(cls, model, related): + cls._inheritances[model] = related diff --git a/mayan/apps/acls/links.py b/mayan/apps/acls/links.py index c534dabade..741868713a 100644 --- a/mayan/apps/acls/links.py +++ b/mayan/apps/acls/links.py @@ -29,23 +29,22 @@ def get_kwargs_factory(variable_name): return get_kwargs -link_acl_delete = Link( - args='resolved_object.pk', icon_class=icon_acl_delete, - permissions=(permission_acl_edit,), permissions_related='content_object', - tags='dangerous', text=_('Delete'), view='acls:acl_delete', -) -link_acl_list = Link( - icon_class=icon_acl_list, kwargs=get_kwargs_factory('resolved_object'), - permissions=(permission_acl_view,), text=_('ACLs'), view='acls:acl_list' -) link_acl_create = Link( icon_class=icon_acl_new, kwargs=get_kwargs_factory('resolved_object'), permissions=(permission_acl_edit,), text=_('New ACL'), view='acls:acl_create' ) +link_acl_delete = Link( + args='resolved_object.pk', icon_class=icon_acl_delete, + permissions=(permission_acl_edit,), tags='dangerous', text=_('Delete'), + view='acls:acl_delete' +) +link_acl_list = Link( + icon_class=icon_acl_list, kwargs=get_kwargs_factory('resolved_object'), + permissions=(permission_acl_view,), text=_('ACLs'), view='acls:acl_list' +) link_acl_permissions = Link( args='resolved_object.pk', icon_class=icon_acl_permissions, permissions=(permission_acl_edit,), - permissions_related='content_object', text=_('Permissions'), - view='acls:acl_permissions', + text=_('Permissions'), view='acls:acl_permissions' ) diff --git a/mayan/apps/acls/managers.py b/mayan/apps/acls/managers.py index 6e3c15b372..f70997d9b9 100644 --- a/mayan/apps/acls/managers.py +++ b/mayan/apps/acls/managers.py @@ -1,17 +1,23 @@ from __future__ import absolute_import, unicode_literals +from functools import reduce import logging +import operator +import warnings +from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied from django.db import models -from django.db.models import Q +from django.db.models import CharField, Value as V, Q +from django.db.models.functions import Concat from django.utils.encoding import force_text from django.utils.translation import ugettext from mayan.apps.common.utils import ( - resolve_attribute, return_attrib, return_related + get_related_field, resolve_attribute, return_related ) +from mayan.apps.common.warnings import InterfaceWarning from mayan.apps.permissions import Permission from mayan.apps.permissions.models import StoredPermission @@ -26,170 +32,235 @@ class AccessControlListManager(models.Manager): Implement a 3 tier permission system, involving a permissions, an actor and an object """ - def check_access(self, permissions, user, obj, related=None): - if user.is_superuser or user.is_staff: - logger.debug( - 'Permissions "%s" on "%s" granted to user "%s" as superuser ' - 'or staff', permissions, obj, user + def _get_acl_filters(self, queryset, stored_permission, user, related_field_name=None): + """ + This method does the bulk of the work. It generates filters for the + AccessControlList model to determine if there are ACL entries for the + members of the queryset's model provided. + """ + # Determine which of the cases we need to address + # 1: No related field + # 2: Related field + # 3: Related field that is Generic Foreign Key + # 4: No related field, but has an inherited related field, solved by + # recursion, branches to #2 or #3. + # 5: Inherited field of a related field + # -- Not addressed yet -- + # 6: Inherited field of a related field that is Generic Foreign Key + # 7: Has a related function + result = [] + + if related_field_name: + related_field = get_related_field( + model=queryset.model, related_field_name=related_field_name ) - return True - try: - return Permission.check_permissions( - permissions=permissions, user=user - ) - except PermissionDenied: - try: - stored_permissions = [ - permission.stored_permission for permission in permissions - ] - except TypeError: - # Not a list of permissions, just one - stored_permissions = (permissions.stored_permission,) + if isinstance(related_field, GenericForeignKey): + # Case 3: Generic Foreign Key, multiple ContentTypes + object + # id combinations + content_type_object_id_queryset = queryset.annotate( + ct_fk_combination=Concat( + related_field.ct_field, V('-'), related_field.fk_field, + output_field=CharField() + ) + ).values('ct_fk_combination') - if related: - obj = return_attrib(obj, related) + acl_filter = self.annotate( + ct_fk_combination=Concat( + 'content_type', V('-'), 'object_id', output_field=CharField() + ) + ).filter( + permissions=stored_permission, role__groups__user=user, + ct_fk_combination__in=content_type_object_id_queryset + ).values('object_id') - try: - parent_accessor = ModelPermission.get_inheritance( - model=obj._meta.model + field_lookup = 'object_id__in' + + result.append(Q(**{field_lookup: acl_filter})) + else: + # Case 2: Related field of a single type, single ContentType, + # multiple object id + content_type = ContentType.objects.get_for_model( + model=related_field.related_model ) - except AttributeError: - # AttributeError means non model objects: ie Statistics - # These can't have ACLs so we raise PermissionDenied + field_lookup = '{}_id__in'.format(related_field_name) + acl_filter = self.filter( + content_type=content_type, permissions=stored_permission, + role__groups__user=user + ).values('object_id') + # Don't add empty filters otherwise the default AND operator + # of the Q object will return an empty queryset when reduced + # and filter out objects that should be in the final queryset. + if acl_filter: + result.append(Q(**{field_lookup: acl_filter})) - # Force object to text to avoid UnicodeDecodeError - raise PermissionDenied( - ugettext('Insufficient access for: %s') % force_text(obj) + # Case 5: Related field, has an inherited related field itself + # Bubble up permssion check + # TODO: Add relationship support: OR or AND + # TODO: OR for document pages, version, doc, and types + # TODO: AND for new cabinet levels ACLs + try: + related_field_model_related_fields = ( + ModelPermission.get_inheritance( + model=related_field.related_model + ), + ) + except KeyError: + pass + else: + relation_result = [] + for related_field_model_related_field_name in related_field_model_related_fields: + related_field_name = '{}__{}'.format(related_field_name, related_field_model_related_field_name) + related_field_inherited_acl_queries = self._get_acl_filters( + queryset=queryset, stored_permission=stored_permission, + user=user, related_field_name=related_field_name + ) + if related_field_inherited_acl_queries: + relation_result.append(reduce(operator.and_, related_field_inherited_acl_queries)) + + if relation_result: + result.append(reduce(operator.or_, relation_result)) + else: + # Case 1: Original model, single ContentType, multiple object id + content_type = ContentType.objects.get_for_model(model=queryset.model) + field_lookup = 'id__in' + acl_filter = self.filter( + content_type=content_type, permissions=stored_permission, + role__groups__user=user + ).values('object_id') + result.append(Q(**{field_lookup: acl_filter})) + + # Case 4: Original model, has an inherited related field + try: + related_fields = ( + ModelPermission.get_inheritance( + model=queryset.model + ), ) except KeyError: pass else: - try: - return self.check_access( - obj=getattr(obj, parent_accessor), - permissions=permissions, user=user + relation_result = [] + + for related_field_name in related_fields: + inherited_acl_queries = self._get_acl_filters( + queryset=queryset, stored_permission=stored_permission, + related_field_name=related_field_name, user=user ) - except AttributeError: - # Has no such attribute, try it as a related field - try: - return self.check_access( - obj=return_related( - instance=obj, related_field=parent_accessor - ), permissions=permissions, user=user - ) - except PermissionDenied: - pass - except PermissionDenied: - pass + if inherited_acl_queries: + relation_result.append(reduce(operator.and_, inherited_acl_queries)) - 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))): - logger.debug( - 'Permissions "%s" on "%s" granted to user "%s" through role "%s" via inherited ACL', - permissions, obj, user, role - ) - 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).exists(): - logger.debug( - 'Permissions "%s" on "%s" denied for user "%s"', - permissions, obj, user - ) - raise PermissionDenied( - ugettext('Insufficient access for: %s') % force_text(obj) - ) - - logger.debug( - 'Permissions "%s" on "%s" granted to user "%s" through roles "%s" by direct ACL', - permissions, obj, user, user_roles - ) - - def filter_by_access(self, permission, user, queryset): - if user.is_superuser or user.is_staff: - logger.debug( - 'Unfiltered queryset returned to user "%s" as superuser or staff', - user - ) - return queryset - - try: - Permission.check_permissions( - permissions=(permission,), user=user - ) - except PermissionDenied: - user_roles = [] - for group in user.groups.all(): - for role in group.roles.all(): - user_roles.append(role) + if relation_result: + result.append(reduce(operator.or_, relation_result)) + # Case 7: Has a function try: - parent_accessor = ModelPermission.get_inheritance( + field_query_function = ModelPermission.get_function( model=queryset.model ) except KeyError: - parent_acl_query = Q() + pass else: - instance = queryset.first() - if instance: - parent_object = return_related( - instance=instance, related_field=parent_accessor + function_results = field_query_function() + + # Filter by the model's content type + content_type = ContentType.objects.get_for_model( + model=queryset.model + ) + acl_filter = self.filter( + content_type=content_type, permissions=stored_permission, + role__groups__user=user + ).values('object_id') + # Obtain an queryset of filtered, authorized model instances + acl_queryset = queryset.model._meta.default_manager.filter( + id__in=acl_filter + ).filter(**function_results['acl_filter']) + + if 'acl_values' in function_results: + acl_queryset = acl_queryset.values( + *function_results['acl_values'] ) - try: - # Try to see if parent_object is a function - parent_object() - except TypeError: - # Is not a function, try it as a field - 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( - 'object_id', flat=True - ) - } - ) - else: - # Is a function. Can't perform Q object filtering. - # Perform iterative filtering. - result = [] - for entry in queryset: - try: - self.check_access(permissions=permission, user=user, obj=entry) - except PermissionDenied: - pass - else: - result.append(entry.pk) + # Get the final query using the filtered queryset as the + # reference + result.append( + Q(**{function_results['field_lookup']: acl_queryset}) + ) - return queryset.filter(pk__in=result) - else: - parent_acl_query = Q() + return result - # Directly granted access - 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)) - logger.debug( - 'Filtered queryset returned to user "%s" based on roles "%s"', - user, user_roles + def check_access(self, obj, permissions, user, related=None): + """ + The `related` argument is ignored. + """ + if related: + warnings.warn( + 'Passing the argument `related` to check_access() is ' + 'deprecated. Use the ModelPermission\'s class ' + '.register_inheritance() class method to register the access ' + 'relationship between two models. The registered relationship ' + 'will be automatically used by check_access().', + InterfaceWarning ) - return queryset.filter(parent_acl_query | acl_query) + meta = getattr(obj, '_meta', None) + + if not meta: + logger.debug( + ugettext( + 'Object "%s" is not a model and cannot be checked for ' + 'access.' + ) % force_text(obj) + ) + return True else: + source_queryset = obj._meta.default_manager.all() + + restricted_queryset = obj._meta.default_manager.none() + for permission in permissions: + # Default relationship betweens permissions is OR + # TODO: Add support for AND relationship + restricted_queryset = restricted_queryset | self.restrict_queryset( + permission=permission, queryset=source_queryset, user=user + ) + + if restricted_queryset.filter(pk=obj.pk).exists(): + return True + else: + raise PermissionDenied( + ugettext(message='Insufficient access for: %s') % force_text( + s=obj + ) + ) + + def filter_by_access(self, permission, user, queryset): + return self.restrict_queryset( + permission=permission, queryset=queryset, user=user + ) + + def restrict_queryset(self, permission, queryset, user): + # Check directly granted permission via a role + try: + Permission.check_user_permissions( + permissions=(permission,), user=user + ) + except PermissionDenied: + acl_filters = self._get_acl_filters( + queryset=queryset, + stored_permission=permission.stored_permission, user=user + ) + + final_query = None + for acl_filter in acl_filters: + if final_query is None: + final_query = acl_filter + else: + final_query = final_query | acl_filter + + return queryset.filter(final_query) + else: + # User has direct permission assignment via a role, is superuser or + # is staff. Return the entire queryset. return queryset def get_inherited_permissions(self, obj, role): diff --git a/mayan/apps/acls/views.py b/mayan/apps/acls/views.py index 0fa619c04e..45bd0c5cb5 100644 --- a/mayan/apps/acls/views.py +++ b/mayan/apps/acls/views.py @@ -44,7 +44,7 @@ class ACLCreateView(SingleObjectCreateView): raise Http404 AccessControlList.objects.check_access( - obj=self.content_object, permissions=permission_acl_edit, + obj=self.content_object, permissions=(permission_acl_edit,), user=request.user ) @@ -86,7 +86,7 @@ class ACLDeleteView(SingleObjectDeleteView): acl = get_object_or_404(klass=AccessControlList, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - obj=acl.content_object, permissions=permission_acl_edit, + obj=acl.content_object, permissions=(permission_acl_edit,), user=request.user ) @@ -126,7 +126,7 @@ class ACLListView(SingleObjectListView): raise Http404 AccessControlList.objects.check_access( - obj=self.content_object, permissions=permission_acl_view, + obj=self.content_object, permissions=(permission_acl_view,), user=request.user ) diff --git a/mayan/apps/acls/workflow_actions.py b/mayan/apps/acls/workflow_actions.py index 1843f73745..1f2435faf3 100644 --- a/mayan/apps/acls/workflow_actions.py +++ b/mayan/apps/acls/workflow_actions.py @@ -89,7 +89,7 @@ class GrantAccessAction(WorkflowAction): try: AccessControlList.objects.check_access( - permissions=permission_acl_edit, user=request.user, obj=obj + obj=obj, permissions=(permission_acl_edit,), user=request.user ) except Exception as exception: raise ValidationError(exception) diff --git a/mayan/apps/cabinets/api_views.py b/mayan/apps/cabinets/api_views.py index 65ac40d791..beb02ad5ed 100644 --- a/mayan/apps/cabinets/api_views.py +++ b/mayan/apps/cabinets/api_views.py @@ -35,8 +35,8 @@ class APIDocumentCabinetListView(generics.ListAPIView): def get_queryset(self): document = get_object_or_404(klass=Document, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permissions=permission_document_view, user=self.request.user, - obj=document + obj=document, permissions=(permission_document_view,), + user=self.request.user ) queryset = document.get_cabinets() @@ -189,8 +189,8 @@ class APICabinetDocumentView(generics.RetrieveDestroyAPIView): instance = self.get_object() AccessControlList.objects.check_access( - permissions=permission_document_view, user=self.request.user, - obj=instance + obj=instance, permissions=(permission_document_view,), + user=self.request.user ) serializer = self.get_serializer(instance) diff --git a/mayan/apps/cabinets/apps.py b/mayan/apps/cabinets/apps.py index 0102800c8c..3944655b8b 100644 --- a/mayan/apps/cabinets/apps.py +++ b/mayan/apps/cabinets/apps.py @@ -73,8 +73,16 @@ class CabinetsApp(MayanAppConfig): permission_cabinet_remove_document ) ) - ModelPermission.register_inheritance( - model=Cabinet, related='get_root', + + def get_root_filter(): + return { + 'acl_filter': {'level': 0}, + 'acl_values': ('tree_id',), + 'field_lookup': 'tree_id__in' + } + + ModelPermission.register_function( + model=Cabinet, function=get_root_filter ) SourceColumn( diff --git a/mayan/apps/cabinets/views.py b/mayan/apps/cabinets/views.py index 8c199bfb67..b4764fa36e 100644 --- a/mayan/apps/cabinets/views.py +++ b/mayan/apps/cabinets/views.py @@ -63,7 +63,7 @@ class CabinetChildAddView(SingleObjectCreateView): cabinet = super(CabinetChildAddView, self).get_object(*args, **kwargs) AccessControlList.objects.check_access( - obj=cabinet.get_root(), permissions=permission_cabinet_edit, + obj=cabinet.get_root(), permissions=(permission_cabinet_edit,), user=self.request.user ) @@ -142,7 +142,7 @@ class CabinetDetailView(DocumentListView): permission_object = cabinet.get_root() AccessControlList.objects.check_access( - obj=permission_object, permissions=permission_cabinet_view, + obj=permission_object, permissions=(permission_cabinet_view,), user=self.request.user ) @@ -193,7 +193,7 @@ class DocumentCabinetListView(CabinetListView): self.document = get_object_or_404(klass=Document, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - obj=self.document, permissions=permission_document_view, + obj=self.document, permissions=(permission_document_view,), user=request.user ) @@ -288,7 +288,7 @@ class DocumentAddToCabinetView(MultipleObjectFormActionView): for cabinet in form.cleaned_data['cabinets']: AccessControlList.objects.check_access( - obj=cabinet, permissions=permission_cabinet_add_document, + obj=cabinet, permissions=(permission_cabinet_add_document,), user=self.request.user ) if cabinet in cabinet_membership: @@ -376,7 +376,7 @@ class DocumentRemoveFromCabinetView(MultipleObjectFormActionView): for cabinet in form.cleaned_data['cabinets']: AccessControlList.objects.check_access( - obj=cabinet, permissions=permission_cabinet_remove_document, + obj=cabinet, permissions=(permission_cabinet_remove_document,), user=self.request.user ) diff --git a/mayan/apps/checkouts/api_views.py b/mayan/apps/checkouts/api_views.py index ada9aa493d..cb80055d06 100644 --- a/mayan/apps/checkouts/api_views.py +++ b/mayan/apps/checkouts/api_views.py @@ -79,12 +79,13 @@ class APICheckedoutDocumentView(generics.RetrieveDestroyAPIView): if document.checkout_info().user == request.user: AccessControlList.objects.check_access( - obj=document, permissions=permission_document_check_in, + obj=document, permissions=(permission_document_check_in,), user=request.user ) else: AccessControlList.objects.check_access( - obj=document, permissions=permission_document_check_in_override, + obj=document, + permissions=(permission_document_check_in_override,), user=request.user ) diff --git a/mayan/apps/checkouts/serializers.py b/mayan/apps/checkouts/serializers.py index c3ef6145c5..b3df78d150 100644 --- a/mayan/apps/checkouts/serializers.py +++ b/mayan/apps/checkouts/serializers.py @@ -42,7 +42,7 @@ class NewDocumentCheckoutSerializer(serializers.ModelSerializer): document = Document.objects.get(pk=validated_data.pop('document_pk')) AccessControlList.objects.check_access( - obj=document, permissions=permission_document_check_out, + obj=document, permissions=(permission_document_check_out,), user=self.context['request'].user ) diff --git a/mayan/apps/checkouts/views.py b/mayan/apps/checkouts/views.py index 723b6187a3..9d797dd0f9 100644 --- a/mayan/apps/checkouts/views.py +++ b/mayan/apps/checkouts/views.py @@ -31,7 +31,7 @@ class CheckoutDocumentView(SingleObjectCreateView): self.document = get_object_or_404(klass=Document, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - obj=self.document, permissions=permission_document_check_out, + obj=self.document, permissions=(permission_document_check_out,), user=request.user ) @@ -168,13 +168,13 @@ class DocumentCheckinView(ConfirmView): if document.get_check_out_info().user == self.request.user: AccessControlList.objects.check_access( - obj=document, permissions=permission_document_check_in, + obj=document, permissions=(permission_document_check_in,), user=self.request.user ) else: AccessControlList.objects.check_access( obj=document, - permissions=permission_document_check_in_override, + permissions=(permission_document_check_in_override,), user=self.request.user ) diff --git a/mayan/apps/common/mixins.py b/mayan/apps/common/mixins.py index 32df7bb7c9..7ba82f2adf 100644 --- a/mayan/apps/common/mixins.py +++ b/mayan/apps/common/mixins.py @@ -286,8 +286,8 @@ class ObjectListPermissionFilterMixin(object): def dispatch(self, request, *args, **kwargs): if self.access_object_retrieve_method and self.object_permission: AccessControlList.objects.check_access( - permissions=(self.object_permission,), user=request.user, - obj=getattr(self, self.access_object_retrieve_method)() + obj=getattr(self, self.access_object_retrieve_method)(), + permissions=(self.object_permission,), user=request.user ) return super(ObjectListPermissionFilterMixin, self).dispatch(request, *args, **kwargs) @@ -296,7 +296,8 @@ class ObjectListPermissionFilterMixin(object): if not self.access_object_retrieve_method and self.object_permission: return AccessControlList.objects.filter_by_access( - self.object_permission, self.request.user, queryset=queryset + queryset=queryset, permission=self.object_permission, + user=self.request.user ) else: return queryset @@ -327,9 +328,10 @@ class ObjectPermissionCheckMixin(object): def dispatch(self, request, *args, **kwargs): if self.object_permission: AccessControlList.objects.check_access( - permissions=self.object_permission, user=request.user, obj=self.get_permission_object(), - related=getattr(self, 'object_permission_related', None) + permissions=(self.object_permission,), + related=getattr(self, 'object_permission_related', None), + user=request.user ) return super( @@ -427,7 +429,7 @@ class ViewPermissionCheckMixin(object): def dispatch(self, request, *args, **kwargs): if self.view_permission: - Permission.check_permissions( + Permission.check_user_permissions( permissions=(self.view_permission,), user=self.request.user ) diff --git a/mayan/apps/common/utils.py b/mayan/apps/common/utils.py index 7c0f57b06b..58683c662f 100644 --- a/mayan/apps/common/utils.py +++ b/mayan/apps/common/utils.py @@ -33,6 +33,26 @@ def encapsulate(function): return lambda: function +def get_related_field(model, related_field_name): + try: + local_field_name, remaining_field_path = related_field_name.split( + LOOKUP_SEP, 1 + ) + except ValueError: + local_field_name = related_field_name + remaining_field_path = None + + related_field = model._meta.get_field(local_field_name) + + if remaining_field_path: + return get_related_field( + model=related_field.related_model, + related_field_name=remaining_field_path + ) + + return related_field + + def introspect_attribute(attribute_name, obj): """ Resolve the attribute of model. Supports nested reference using dotted diff --git a/mayan/apps/common/views.py b/mayan/apps/common/views.py index e8cd3fd684..af330a8f5f 100644 --- a/mayan/apps/common/views.py +++ b/mayan/apps/common/views.py @@ -145,7 +145,7 @@ class ObjectErrorLogEntryListClearView(ConfirmView): class ObjectErrorLogEntryListView(SingleObjectListView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( - obj=self.get_object(), permissions=permission_error_log_view, + obj=self.get_object(), permissions=(permission_error_log_view,), user=request.user ) diff --git a/mayan/apps/common/warnings.py b/mayan/apps/common/warnings.py index ae39529700..24929d58db 100644 --- a/mayan/apps/common/warnings.py +++ b/mayan/apps/common/warnings.py @@ -5,3 +5,9 @@ class DatabaseWarning(UserWarning): """ Warning when using unsupported database backends """ + + +class InterfaceWarning(UserWarning): + """ + Warning when using obsolete internal interfaces + """ diff --git a/mayan/apps/converter/views.py b/mayan/apps/converter/views.py index 847eaef017..516f958e90 100644 --- a/mayan/apps/converter/views.py +++ b/mayan/apps/converter/views.py @@ -45,7 +45,7 @@ class TransformationCreateView(SingleObjectCreateView): AccessControlList.objects.check_access( obj=self.content_object, - permissions=permission_transformation_create, user=request.user + permissions=(permission_transformation_create,), user=request.user ) return super(TransformationCreateView, self).dispatch( @@ -96,7 +96,7 @@ class TransformationDeleteView(SingleObjectDeleteView): AccessControlList.objects.check_access( obj=self.transformation.content_object, - permissions=permission_transformation_delete, user=request.user + permissions=(permission_transformation_delete,), user=request.user ) return super(TransformationDeleteView, self).dispatch( @@ -145,7 +145,7 @@ class TransformationEditView(SingleObjectEditView): AccessControlList.objects.check_access( obj=self.transformation.content_object, - permissions=permission_transformation_edit, user=request.user + permissions=(permission_transformation_edit,), user=request.user ) return super(TransformationEditView, self).dispatch( @@ -202,7 +202,7 @@ class TransformationListView(SingleObjectListView): AccessControlList.objects.check_access( obj=self.content_object, - permissions=permission_transformation_view, user=request.user + permissions=(permission_transformation_view,), user=request.user ) return super(TransformationListView, self).dispatch( diff --git a/mayan/apps/document_comments/api_views.py b/mayan/apps/document_comments/api_views.py index 1a56b501af..7ae0ed67db 100644 --- a/mayan/apps/document_comments/api_views.py +++ b/mayan/apps/document_comments/api_views.py @@ -30,7 +30,7 @@ class APICommentListView(generics.ListCreateAPIView): ) AccessControlList.objects.check_access( - obj=document, permissions=permission_required, + obj=document, permissions=(permission_required,), user=self.request.user ) @@ -85,7 +85,7 @@ class APICommentView(generics.RetrieveDestroyAPIView): ) AccessControlList.objects.check_access( - obj=document, permissions=permission_required, + obj=document, permissions=(permission_required,), user=self.request.user ) diff --git a/mayan/apps/document_comments/views.py b/mayan/apps/document_comments/views.py index 728c1559d2..19b8e369a0 100644 --- a/mayan/apps/document_comments/views.py +++ b/mayan/apps/document_comments/views.py @@ -26,7 +26,7 @@ class DocumentCommentCreateView(SingleObjectCreateView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( - obj=self.get_document(), permissions=permission_comment_create, + obj=self.get_document(), permissions=(permission_comment_create,), user=request.user ) @@ -67,7 +67,7 @@ class DocumentCommentDeleteView(SingleObjectDeleteView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( obj=self.get_object().document, - permissions=permission_comment_delete, user=request.user + permissions=(permission_comment_delete,), user=request.user ) return super( @@ -120,7 +120,7 @@ class DocumentCommentListView(SingleObjectListView): def get_object_list(self): AccessControlList.objects.check_access( - obj=self.get_document(), permissions=permission_comment_view, + obj=self.get_document(), permissions=(permission_comment_view,), user=self.request.user ) diff --git a/mayan/apps/document_indexing/api_views.py b/mayan/apps/document_indexing/api_views.py index cb803bd4a7..e26a766116 100644 --- a/mayan/apps/document_indexing/api_views.py +++ b/mayan/apps/document_indexing/api_views.py @@ -67,7 +67,7 @@ class APIIndexNodeInstanceDocumentListView(generics.ListAPIView): ) AccessControlList.objects.check_access( obj=index_node_instance.index, - permissions=permission_document_indexing_view, + permissions=(permission_document_indexing_view,), user=self.request.user ) @@ -113,7 +113,7 @@ class APIDocumentIndexListView(generics.ListAPIView): def get_queryset(self): document = get_object_or_404(klass=Document, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - obj=document, permissions=permission_document_view, + obj=document, permissions=(permission_document_view,), user=self.request.user ) diff --git a/mayan/apps/document_indexing/views.py b/mayan/apps/document_indexing/views.py index 6f8b04d9c0..67940d8d41 100644 --- a/mayan/apps/document_indexing/views.py +++ b/mayan/apps/document_indexing/views.py @@ -204,7 +204,7 @@ class TemplateNodeCreateView(SingleObjectCreateView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( obj=self.get_parent_node().index, - permissions=permission_document_indexing_edit, user=request.user + permissions=(permission_document_indexing_edit,), user=request.user ) return super( @@ -311,7 +311,7 @@ class IndexInstanceNodeView(DocumentListView): AccessControlList.objects.check_access( obj=self.index_instance_node.index(), - permissions=permission_document_indexing_instance_view, + permissions=(permission_document_indexing_instance_view,), user=request.user ) @@ -378,7 +378,7 @@ class DocumentIndexNodeListView(SingleObjectListView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( - obj=self.get_document(), permissions=permission_document_view, + obj=self.get_document(), permissions=(permission_document_view,), user=request.user ) diff --git a/mayan/apps/document_signatures/apps.py b/mayan/apps/document_signatures/apps.py index ac4f24efb2..b1035ca51e 100644 --- a/mayan/apps/document_signatures/apps.py +++ b/mayan/apps/document_signatures/apps.py @@ -65,6 +65,7 @@ class DocumentSignaturesApp(MayanAppConfig): app_label='django_gpg', model_name='Key' ) + DetachedSignature = self.get_model(model_name='DetachedSignature') EmbeddedSignature = self.get_model(model_name='EmbeddedSignature') SignatureBaseModel = self.get_model(model_name='SignatureBaseModel') @@ -86,6 +87,12 @@ class DocumentSignaturesApp(MayanAppConfig): permission_document_version_signature_upload, ) ) + ModelPermission.register_inheritance( + model=SignatureBaseModel, related='document_version' + ) + ModelPermission.register_inheritance( + model=DetachedSignature, related='document_version' + ) SourceColumn( source=SignatureBaseModel, label=_('Date'), attribute='date' diff --git a/mayan/apps/document_signatures/links.py b/mayan/apps/document_signatures/links.py index 12780d58da..78d7054b10 100644 --- a/mayan/apps/document_signatures/links.py +++ b/mayan/apps/document_signatures/links.py @@ -42,47 +42,44 @@ link_document_version_signature_delete = Link( args='resolved_object.pk', condition=is_detached_signature, icon_class_path='mayan.apps.document_signatures.icons.icon_document_version_signature_delete', permissions=(permission_document_version_signature_delete,), - permissions_related='document_version.document', tags='dangerous', - text=_('Delete'), view='signatures:document_version_signature_delete', + tags='dangerous', text=_('Delete'), + view='signatures:document_version_signature_delete', ) link_document_version_signature_details = Link( args='resolved_object.pk', icon_class_path='mayan.apps.document_signatures.icons.icon_document_version_signature_details', permissions=(permission_document_version_signature_view,), - permissions_related='document_version.document', text=_('Details'), - view='signatures:document_version_signature_details', + text=_('Details'), view='signatures:document_version_signature_details', ) link_document_version_signature_list = Link( args='resolved_object.pk', icon_class_path='mayan.apps.document_signatures.icons.icon_document_version_signature_list', permissions=(permission_document_version_signature_view,), - permissions_related='document', text=_('Signatures'), - view='signatures:document_version_signature_list', + text=_('Signatures'), view='signatures:document_version_signature_list' ) link_document_version_signature_download = Link( args='resolved_object.pk', condition=is_detached_signature, permissions=(permission_document_version_signature_download,), - permissions_related='document_version.document', text=_('Download'), - view='signatures:document_version_signature_download', + text=_('Download'), view='signatures:document_version_signature_download' ) link_document_version_signature_upload = Link( args='resolved_object.pk', icon_class_path='mayan.apps.document_signatures.icons.icon_document_version_signature_upload', permissions=(permission_document_version_signature_upload,), - permissions_related='document', text=_('Upload signature'), - view='signatures:document_version_signature_upload', + text=_('Upload signature'), + view='signatures:document_version_signature_upload' ) link_document_version_signature_detached_create = Link( args='resolved_object.pk', icon_class_path='mayan.apps.document_signatures.icons.icon_document_version_signature_detached_create', permissions=(permission_document_version_sign_detached,), - permissions_related='document', text=_('Sign detached'), - view='signatures:document_version_signature_detached_create', + text=_('Sign detached'), + view='signatures:document_version_signature_detached_create' ) link_document_version_signature_embedded_create = Link( args='resolved_object.pk', icon_class_path='mayan.apps.document_signatures.icons.icon_document_version_signature_embedded_create', permissions=(permission_document_version_sign_embedded,), - permissions_related='document', text=_('Sign embedded'), - view='signatures:document_version_signature_embedded_create', + text=_('Sign embedded'), + view='signatures:document_version_signature_embedded_create' ) diff --git a/mayan/apps/document_signatures/views.py b/mayan/apps/document_signatures/views.py index 88b952f2fc..d0826c1f68 100644 --- a/mayan/apps/document_signatures/views.py +++ b/mayan/apps/document_signatures/views.py @@ -54,7 +54,7 @@ class DocumentVersionDetachedSignatureCreateView(FormView): passphrase = form.cleaned_data['passphrase'] or None AccessControlList.objects.check_access( - obj=key, permissions=permission_key_sign, user=self.request.user + obj=key, permissions=(permission_key_sign,), user=self.request.user ) try: @@ -109,7 +109,7 @@ class DocumentVersionDetachedSignatureCreateView(FormView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( obj=self.get_document_version().document, - permissions=permission_document_version_sign_detached, + permissions=(permission_document_version_sign_detached,), user=request.user ) @@ -146,7 +146,7 @@ class DocumentVersionEmbeddedSignatureCreateView(FormView): passphrase = form.cleaned_data['passphrase'] or None AccessControlList.objects.check_access( - obj=key, permissions=permission_key_sign, user=self.request.user + obj=key, permissions=(permission_key_sign,), user=self.request.user ) try: @@ -206,7 +206,7 @@ class DocumentVersionEmbeddedSignatureCreateView(FormView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( obj=self.get_document_version().document, - permissions=permission_document_version_sign_embedded, + permissions=(permission_document_version_sign_embedded,), user=request.user ) @@ -283,8 +283,9 @@ class DocumentVersionSignatureDownloadView(SingleObjectDownloadView): class DocumentVersionSignatureListView(SingleObjectListView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( - permissions=permission_document_version_signature_view, - user=request.user, obj=self.get_document_version() + obj=self.get_document_version(), + permissions=(permission_document_version_signature_view,), + user=request.user ) return super( @@ -345,7 +346,7 @@ class DocumentVersionSignatureUploadView(SingleObjectCreateView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( obj=self.get_document_version(), - permissions=permission_document_version_signature_upload, + permissions=(permission_document_version_signature_upload,), user=request.user ) diff --git a/mayan/apps/document_states/api_views.py b/mayan/apps/document_states/api_views.py index 0ba286e06b..78e4e31893 100644 --- a/mayan/apps/document_states/api_views.py +++ b/mayan/apps/document_states/api_views.py @@ -40,8 +40,8 @@ class APIDocumentTypeWorkflowListView(generics.ListAPIView): ) AccessControlList.objects.check_access( - permissions=permission_document_type_view, user=self.request.user, - obj=document_type + obj=document_type, permissions=(permission_document_type_view,), + user=self.request.user ) return document_type @@ -105,8 +105,8 @@ class APIWorkflowDocumentTypeList(generics.ListCreateAPIView): workflow = get_object_or_404(klass=Workflow, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permissions=permission_required, user=self.request.user, - obj=workflow + obj=workflow, permissions=(permission_required,), + user=self.request.user ) return workflow @@ -158,8 +158,8 @@ class APIWorkflowDocumentTypeView(generics.RetrieveDestroyAPIView): workflow = get_object_or_404(klass=Workflow, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permissions=permission_required, user=self.request.user, - obj=workflow + obj=workflow, permissions=(permission_required,), + user=self.request.user ) return workflow @@ -261,8 +261,8 @@ class APIWorkflowStateListView(generics.ListCreateAPIView): workflow = get_object_or_404(klass=Workflow, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permissions=permission_required, user=self.request.user, - obj=workflow + obj=workflow, permissions=(permission_required,), + user=self.request.user ) return workflow @@ -304,8 +304,8 @@ class APIWorkflowStateView(generics.RetrieveUpdateDestroyAPIView): workflow = get_object_or_404(klass=Workflow, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permissions=permission_required, user=self.request.user, - obj=workflow + obj=workflow, permissions=(permission_required,), + user=self.request.user ) return workflow @@ -357,8 +357,8 @@ class APIWorkflowTransitionListView(generics.ListCreateAPIView): workflow = get_object_or_404(klass=Workflow, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permissions=permission_required, user=self.request.user, - obj=workflow + obj=workflow, permissions=(permission_required,), + user=self.request.user ) return workflow @@ -411,8 +411,8 @@ class APIWorkflowTransitionView(generics.RetrieveUpdateDestroyAPIView): workflow = get_object_or_404(klass=Workflow, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permissions=permission_required, user=self.request.user, - obj=workflow + obj=workflow, permissions=(permission_required,), + user=self.request.user ) return workflow @@ -435,8 +435,8 @@ class APIWorkflowInstanceListView(generics.ListAPIView): document = get_object_or_404(klass=Document, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permissions=permission_workflow_view, user=self.request.user, - obj=document + obj=document, permissions=(permission_workflow_view,), + user=self.request.user ) return document @@ -460,8 +460,8 @@ class APIWorkflowInstanceView(generics.RetrieveAPIView): document = get_object_or_404(klass=Document, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permissions=permission_workflow_view, user=self.request.user, - obj=document + obj=document, permissions=(permission_workflow_view,), + user=self.request.user ) return document @@ -488,9 +488,10 @@ class APIWorkflowInstanceLogEntryListView(generics.ListCreateAPIView): Failing that, check for ACLs for any of the workflow's transitions. Failing that, then raise PermissionDenied """ + # TODO: Improvement above AccessControlList.objects.check_access( - permissions=permission_workflow_view, user=self.request.user, - obj=document + obj=document, permissions=(permission_workflow_view,), + user=self.request.user ) return document diff --git a/mayan/apps/document_states/models.py b/mayan/apps/document_states/models.py index 8b8388eba7..622922b64f 100644 --- a/mayan/apps/document_states/models.py +++ b/mayan/apps/document_states/models.py @@ -417,8 +417,9 @@ class WorkflowInstance(models.Model): all transition options. """ AccessControlList.objects.check_access( - permissions=permission_workflow_transition, - user=_user, obj=self.workflow + obj=self.workflow, + permissions=(permission_workflow_transition,), + user=_user ) except PermissionDenied: """ @@ -427,7 +428,8 @@ class WorkflowInstance(models.Model): """ queryset = AccessControlList.objects.filter_by_access( permission=permission_workflow_transition, - user=_user, queryset=queryset + queryset=queryset, + user=_user ) return queryset else: diff --git a/mayan/apps/document_states/views/workflow_instance_views.py b/mayan/apps/document_states/views/workflow_instance_views.py index 9f346147c3..28d52d3bb6 100644 --- a/mayan/apps/document_states/views/workflow_instance_views.py +++ b/mayan/apps/document_states/views/workflow_instance_views.py @@ -23,8 +23,8 @@ __all__ = ( class DocumentWorkflowInstanceListView(SingleObjectListView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( - permissions=permission_workflow_view, user=request.user, - obj=self.get_document() + obj=self.get_document(), permissions=(permission_workflow_view,), + user=request.user ) return super( @@ -58,8 +58,8 @@ class DocumentWorkflowInstanceListView(SingleObjectListView): class WorkflowInstanceDetailView(SingleObjectListView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( - permissions=permission_workflow_view, user=request.user, - obj=self.get_workflow_instance().document + obj=self.get_workflow_instance().document, + permissions=(permission_workflow_view,), user=request.user ) return super( diff --git a/mayan/apps/document_states/views/workflow_proxy_views.py b/mayan/apps/document_states/views/workflow_proxy_views.py index 31ab54f4e3..8e7a72abcd 100644 --- a/mayan/apps/document_states/views/workflow_proxy_views.py +++ b/mayan/apps/document_states/views/workflow_proxy_views.py @@ -27,8 +27,8 @@ class WorkflowDocumentListView(DocumentListView): ) AccessControlList.objects.check_access( - permissions=permission_workflow_view, user=request.user, - obj=self.workflow + obj=self.workflow, permissions=(permission_workflow_view,), + user=request.user ) return super( @@ -111,8 +111,8 @@ class WorkflowStateDocumentListView(DocumentListView): ) AccessControlList.objects.check_access( - permissions=permission_workflow_view, user=self.request.user, - obj=workflow_state.workflow + obj=workflow_state.workflow, + permissions=(permission_workflow_view,), user=self.request.user ) return workflow_state @@ -121,8 +121,8 @@ class WorkflowStateDocumentListView(DocumentListView): class WorkflowStateListView(SingleObjectListView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( - permissions=permission_workflow_view, user=request.user, - obj=self.get_workflow() + obj=self.get_workflow(), permissions=(permission_workflow_view,), + user=request.user ) return super( diff --git a/mayan/apps/document_states/views/workflow_views.py b/mayan/apps/document_states/views/workflow_views.py index 88fa19856b..d160b7386e 100644 --- a/mayan/apps/document_states/views/workflow_views.py +++ b/mayan/apps/document_states/views/workflow_views.py @@ -345,7 +345,7 @@ class SetupWorkflowStateCreateView(SingleObjectCreateView): def get_workflow(self): workflow = get_object_or_404(klass=Workflow, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permissions=(permission_workflow_edit,), obj=workflow, + obj=workflow, permissions=(permission_workflow_edit,), user=self.request.user ) return workflow @@ -380,7 +380,7 @@ class SetupWorkflowStateDeleteView(SingleObjectDeleteView): def get_workflow(self): workflow = get_object_or_404(klass=Workflow, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permissions=(permission_workflow_edit,), obj=workflow, + obj=workflow, permissions=(permission_workflow_edit,), user=self.request.user ) return workflow @@ -410,8 +410,8 @@ class SetupWorkflowStateListView(SingleObjectListView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( - permissions=permission_workflow_view, user=request.user, - obj=self.get_workflow() + obj=self.get_workflow(), permissions=(permission_workflow_view,), + user=request.user ) return super( @@ -492,7 +492,7 @@ class SetupWorkflowTransitionCreateView(SingleObjectCreateView): def get_workflow(self): workflow = get_object_or_404(klass=Workflow, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permissions=(permission_workflow_edit,), obj=workflow, + obj=workflow, permissions=(permission_workflow_edit,), user=self.request.user ) return workflow @@ -580,8 +580,9 @@ class SetupWorkflowTransitionTriggerEventListView(FormView): def dispatch(self, *args, **kwargs): AccessControlList.objects.check_access( - permissions=permission_workflow_edit, - user=self.request.user, obj=self.get_object().workflow + obj=self.get_object().workflow, + permissions=(permission_workflow_edit,), + user=self.request.user ) EventType.refresh() diff --git a/mayan/apps/documents/api_views.py b/mayan/apps/documents/api_views.py index baba77087d..4e047c0b07 100644 --- a/mayan/apps/documents/api_views.py +++ b/mayan/apps/documents/api_views.py @@ -16,7 +16,7 @@ from mayan.apps.rest_api.permissions import MayanPermission from .literals import DOCUMENT_IMAGE_TASK_TIMEOUT from .models import ( - Document, DocumentType, RecentDocument + DeletedDocument, Document, DocumentType, RecentDocument ) from .permissions import ( permission_document_create, permission_document_delete, @@ -42,14 +42,14 @@ from .tasks import task_generate_document_page_image logger = logging.getLogger(__name__) -class APIDeletedDocumentListView(generics.ListAPIView): +class APITrashedDocumentListView(generics.ListAPIView): """ Returns a list of all the trashed documents. """ filter_backends = (MayanObjectPermissionsFilter,) mayan_object_permissions = {'GET': (permission_document_view,)} permission_classes = (MayanPermission,) - queryset = Document.trash.all() + queryset = DeletedDocument.objects.all() serializer_class = DeletedDocumentSerializer @@ -64,7 +64,7 @@ class APIDeletedDocumentView(generics.RetrieveDestroyAPIView): 'GET': (permission_document_view,) } permission_classes = (MayanPermission,) - queryset = Document.trash.all() + queryset = DeletedDocument.objects.all() serializer_class = DeletedDocumentSerializer @@ -76,7 +76,7 @@ class APIDeletedDocumentRestoreView(generics.GenericAPIView): 'POST': (permission_document_restore,) } permission_classes = (MayanPermission,) - queryset = Document.trash.all() + queryset = DeletedDocument.objects.all() def get_serializer(self, *args, **kwargs): return None @@ -143,8 +143,8 @@ class APIDocumentListView(generics.ListCreateAPIView): def perform_create(self, serializer): AccessControlList.objects.check_access( - permissions=(permission_document_create,), user=self.request.user, - obj=serializer.validated_data['document_type'] + obj=serializer.validated_data['document_type'], + permissions=(permission_document_create,), user=self.request.user ) serializer.save(_user=self.request.user) @@ -164,7 +164,8 @@ class APIDocumentPageImageView(generics.RetrieveAPIView): document = get_object_or_404(Document.passthrough, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permission_required, self.request.user, document + obj=document, permissions=(permission_required,), + user=self.request.user ) return document @@ -231,7 +232,8 @@ class APIDocumentPageView(generics.RetrieveUpdateAPIView): document = get_object_or_404(Document, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permission_required, self.request.user, document + obj=document, permissions=(permission_required,), + user=self.request.user ) return document @@ -309,8 +311,8 @@ class APIDocumentTypeDocumentListView(generics.ListAPIView): def get_queryset(self): document_type = get_object_or_404(DocumentType, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permissions=permission_document_type_view, user=self.request.user, - obj=document_type + obj=document_type, permissions=(permission_document_type_view,), + user=self.request.user ) return document_type.documents.all() @@ -326,8 +328,8 @@ class APIDocumentVersionDownloadView(DownloadMixin, generics.RetrieveAPIView): document = get_object_or_404(Document, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permissions=(permission_document_download,), user=self.request.user, - obj=document + obj=document, permissions=(permission_document_download,), + user=self.request.user ) return document @@ -420,7 +422,8 @@ class APIDocumentVersionPageListView(generics.ListAPIView): document = get_object_or_404(Document, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permission_document_view, self.request.user, document + obj=document, permissions=(permission_document_view,), + user=self.request.user ) return document @@ -442,7 +445,6 @@ class APIDocumentVersionsListView(generics.ListCreateAPIView): mayan_object_permissions = { 'GET': (permission_document_version_view,), } - mayan_permission_attribute_check = 'document' permission_classes = (MayanPermission,) def create(self, request, *args, **kwargs): @@ -471,8 +473,8 @@ class APIDocumentVersionsListView(generics.ListCreateAPIView): document = get_object_or_404(Document, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permissions=(permission_document_new_version,), - user=self.request.user, obj=document + obj=document, permissions=(permission_document_new_version,), + user=self.request.user, ) serializer.save(document=document, _user=self.request.user) @@ -497,7 +499,8 @@ class APIDocumentVersionView(generics.RetrieveUpdateDestroyAPIView): document = get_object_or_404(Document, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permission_required, self.request.user, document + obj=document, permissions=(permission_required,), + user=self.request.user ) return document diff --git a/mayan/apps/documents/apps.py b/mayan/apps/documents/apps.py index dad610800e..a4364053ef 100644 --- a/mayan/apps/documents/apps.py +++ b/mayan/apps/documents/apps.py @@ -220,10 +220,6 @@ class DocumentsApp(MayanAppConfig): ) ) - ModelPermission.register_proxy( - source=Document, model=DocumentType, - ) - ModelPermission.register_inheritance( model=Document, related='document_type', ) diff --git a/mayan/apps/documents/tasks.py b/mayan/apps/documents/tasks.py index 55dd75150c..857eb38e66 100644 --- a/mayan/apps/documents/tasks.py +++ b/mayan/apps/documents/tasks.py @@ -53,13 +53,13 @@ def task_clear_image_cache(): @app.task(ignore_result=True) -def task_delete_document(deleted_document_id): +def task_delete_document(trashed_document_id): DeletedDocument = apps.get_model( app_label='documents', model_name='DeletedDocument' ) logger.debug(msg='Executing') - deleted_document = DeletedDocument.objects.get(pk=deleted_document_id) + deleted_document = DeletedDocument.objects.get(pk=trashed_document_id) deleted_document.delete() logger.debug(msg='Finshed') diff --git a/mayan/apps/documents/tests/test_deleted_document_views.py b/mayan/apps/documents/tests/test_trashed_document_views.py similarity index 94% rename from mayan/apps/documents/tests/test_deleted_document_views.py rename to mayan/apps/documents/tests/test_trashed_document_views.py index 1e04aa8cc5..180ed538ce 100644 --- a/mayan/apps/documents/tests/test_deleted_document_views.py +++ b/mayan/apps/documents/tests/test_trashed_document_views.py @@ -9,7 +9,7 @@ from ..permissions import ( from .base import GenericDocumentViewTestCase -class DeletedDocumentTestCase(GenericDocumentViewTestCase): +class TrashedDocumentTestCase(GenericDocumentViewTestCase): def _request_document_restore_view(self): return self.post( viewname='documents:document_restore', kwargs={ @@ -22,7 +22,7 @@ class DeletedDocumentTestCase(GenericDocumentViewTestCase): self.assertEqual(Document.objects.count(), 0) response = self._request_document_restore_view() - self.assertEqual(response.status_code, 403) + self.assertEqual(response.status_code, 302) self.assertEqual(DeletedDocument.objects.count(), 1) self.assertEqual(Document.objects.count(), 0) @@ -50,7 +50,7 @@ class DeletedDocumentTestCase(GenericDocumentViewTestCase): def test_document_trash_no_permissions(self): response = self._request_document_trash_view() - self.assertEqual(response.status_code, 403) + self.assertEqual(response.status_code, 302) self.assertEqual(DeletedDocument.objects.count(), 0) self.assertEqual(Document.objects.count(), 1) @@ -79,7 +79,7 @@ class DeletedDocumentTestCase(GenericDocumentViewTestCase): self.assertEqual(DeletedDocument.objects.count(), 1) response = self._request_document_delete_view() - self.assertEqual(response.status_code, 403) + self.assertEqual(response.status_code, 302) self.assertEqual(Document.objects.count(), 0) self.assertEqual(DeletedDocument.objects.count(), 1) diff --git a/mayan/apps/documents/urls.py b/mayan/apps/documents/urls.py index e19d8d2daf..268a11b10b 100644 --- a/mayan/apps/documents/urls.py +++ b/mayan/apps/documents/urls.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals from django.conf.urls import url from .api_views import ( - APIDeletedDocumentListView, APIDeletedDocumentRestoreView, + APITrashedDocumentListView, APIDeletedDocumentRestoreView, APIDeletedDocumentView, APIDocumentDownloadView, APIDocumentView, APIDocumentListView, APIDocumentVersionDownloadView, APIDocumentPageImageView, APIDocumentPageView, @@ -13,31 +13,33 @@ from .api_views import ( APIRecentDocumentListView ) from .views import ( - ClearImageCacheView, DeletedDocumentDeleteView, - DeletedDocumentDeleteManyView, DeletedDocumentListView, - DocumentDocumentTypeEditView, DocumentDownloadFormView, + ClearImageCacheView, DocumentDocumentTypeEditView, DocumentDownloadFormView, DocumentDownloadView, DocumentDuplicatesListView, DocumentEditView, DocumentListView, DocumentPageListView, DocumentPageNavigationFirst, DocumentPageNavigationLast, DocumentPageNavigationNext, DocumentPageNavigationPrevious, DocumentPageRotateLeftView, DocumentPageRotateRightView, DocumentPageView, DocumentPageViewResetView, DocumentPageZoomInView, DocumentPageZoomOutView, DocumentPreviewView, - DocumentPrint, DocumentRestoreView, DocumentRestoreManyView, - DocumentTransformationsClearView, DocumentTransformationsCloneView, - DocumentTrashView, DocumentTrashManyView, DocumentTypeCreateView, + DocumentPrint, DocumentTransformationsClearView, + DocumentTransformationsCloneView, DocumentTypeCreateView, DocumentTypeDeleteView, DocumentTypeDocumentListView, DocumentTypeFilenameCreateView, DocumentTypeFilenameDeleteView, DocumentTypeFilenameEditView, DocumentTypeFilenameListView, DocumentTypeListView, DocumentTypeEditView, DocumentUpdatePageCountView, DocumentVersionDownloadFormView, DocumentVersionDownloadView, DocumentVersionListView, DocumentVersionRevertView, DocumentVersionView, - DocumentView, DuplicatedDocumentListView, EmptyTrashCanView, + DocumentView, DuplicatedDocumentListView, RecentAccessDocumentListView, RecentAddedDocumentListView, ScanDuplicatedDocuments ) from .views.favorite_document_views import ( FavoriteAddView, FavoriteDocumentListView, FavoriteRemoveView ) +from .views.trashed_document_views import ( + DocumentTrashView, EmptyTrashCanView, TrashedDocumentDeleteView, + TrashedDocumentListView, TrashedDocumentRestoreView +) + urlpatterns_favorite_documents = [ url( @@ -62,6 +64,42 @@ urlpatterns_favorite_documents = [ view=FavoriteRemoveView.as_view(), name='document_multiple_remove_from_favorites' ), + url( + regex=r'^trash_can/empty/$', view=EmptyTrashCanView.as_view(), + name='trash_can_empty' + ), +] + +urlpatterns_trashed_documents = [ + url( + regex=r'^(?P\d+)/trash/$', view=DocumentTrashView.as_view(), + name='document_trash' + ), + url( + regex=r'^multiple/trash/$', view=DocumentTrashView.as_view(), + name='document_multiple_trash' + ), + url( + regex=r'^list/deleted/$', view=TrashedDocumentListView.as_view(), + name='document_list_deleted' + ), + url( + regex=r'^(?P\d+)/restore/$', + view=TrashedDocumentRestoreView.as_view(), name='document_restore' + ), + url( + regex=r'^multiple/restore/$', view=TrashedDocumentRestoreView.as_view(), + name='document_multiple_restore' + ), + url( + regex=r'^(?P\d+)/delete/$', + view=TrashedDocumentDeleteView.as_view(), name='document_delete' + ), + url( + regex=r'^multiple/delete/$', + view=TrashedDocumentDeleteView.as_view(), + name='document_multiple_delete' + ), ] urlpatterns = [ @@ -78,10 +116,6 @@ urlpatterns = [ view=RecentAddedDocumentListView.as_view(), name='document_list_recent_added' ), - url( - regex=r'^list/deleted/$', view=DeletedDocumentListView.as_view(), - name='document_list_deleted' - ), url( regex=r'^list/duplicated/$', view=DuplicatedDocumentListView.as_view(), @@ -100,23 +134,6 @@ urlpatterns = [ view=DocumentDuplicatesListView.as_view(), name='document_duplicates_list' ), - url( - regex=r'^(?P\d+)/restore/$', view=DocumentRestoreView.as_view(), - name='document_restore' - ), - url( - regex=r'^multiple/restore/$', view=DocumentRestoreManyView.as_view(), - name='document_multiple_restore' - ), - url( - regex=r'^(?P\d+)/delete/$', - view=DeletedDocumentDeleteView.as_view(), name='document_delete' - ), - url( - regex=r'^multiple/delete/$', - view=DeletedDocumentDeleteManyView.as_view(), - name='document_multiple_delete' - ), url( regex=r'^(?P\d+)/type/$', view=DocumentDocumentTypeEditView.as_view(), @@ -126,14 +143,6 @@ urlpatterns = [ regex=r'^multiple/type/$', view=DocumentDocumentTypeEditView.as_view(), name='document_multiple_document_type_edit' ), - url( - regex=r'^(?P\d+)/trash/$', view=DocumentTrashView.as_view(), - name='document_trash' - ), - url( - regex=r'^multiple/trash/$', view=DocumentTrashManyView.as_view(), - name='document_multiple_trash' - ), url( regex=r'^(?P\d+)/edit/$', view=DocumentEditView.as_view(), name='document_edit' @@ -218,11 +227,6 @@ urlpatterns = [ regex=r'^cache/clear/$', view=ClearImageCacheView.as_view(), name='document_clear_image_cache' ), - url( - regex=r'^trash_can/empty/$', view=EmptyTrashCanView.as_view(), - name='trash_can_empty' - ), - url( regex=r'^page/(?P\d+)/$', view=DocumentPageView.as_view(), name='document_page_view' @@ -323,7 +327,7 @@ urlpatterns = [ ), ] urlpatterns.extend(urlpatterns_favorite_documents) - +urlpatterns.extend(urlpatterns_trashed_documents) api_urls = [ url( @@ -384,7 +388,7 @@ api_urls = [ ), url( regex=r'^trashed_documents/$', - view=APIDeletedDocumentListView.as_view(), name='trasheddocument-list' + view=APITrashedDocumentListView.as_view(), name='trasheddocument-list' ), url( regex=r'^trashed_documents/(?P[0-9]+)/$', diff --git a/mayan/apps/documents/views/document_page_views.py b/mayan/apps/documents/views/document_page_views.py index a658bd2818..d471042693 100644 --- a/mayan/apps/documents/views/document_page_views.py +++ b/mayan/apps/documents/views/document_page_views.py @@ -38,8 +38,8 @@ logger = logging.getLogger(__name__) class DocumentPageListView(SingleObjectListView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( - permissions=permission_document_view, user=self.request.user, - obj=self.get_document() + obj=self.get_document(), permissions=(permission_document_view,), + user=self.request.user ) return super( @@ -66,8 +66,8 @@ class DocumentPageNavigationBase(RedirectView): document_page = self.get_object() AccessControlList.objects.check_access( - permissions=permission_document_view, user=request.user, - obj=document_page.document + obj=document_page.document, + permissions=(permission_document_view,), user=request.user ) return super(DocumentPageNavigationBase, self).dispatch( @@ -170,8 +170,8 @@ class DocumentPageView(SimpleView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( - permissions=permission_document_view, user=request.user, - obj=self.get_object().document + obj=self.get_object().document, + permissions=(permission_document_view,), user=request.user ) return super( @@ -214,11 +214,11 @@ class DocumentPageViewResetView(RedirectView): class DocumentPageInteractiveTransformation(RedirectView): def dispatch(self, request, *args, **kwargs): - object = self.get_object() + obj = self.get_object() AccessControlList.objects.check_access( - permissions=permission_document_view, user=request.user, - obj=object + obj=obj, permissions=(permission_document_view,), + user=request.user ) return super(DocumentPageInteractiveTransformation, self).dispatch( diff --git a/mayan/apps/documents/views/document_type_views.py b/mayan/apps/documents/views/document_type_views.py index 79ee912b50..dd37e71444 100644 --- a/mayan/apps/documents/views/document_type_views.py +++ b/mayan/apps/documents/views/document_type_views.py @@ -138,8 +138,9 @@ class DocumentTypeFilenameCreateView(SingleObjectCreateView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( - permissions=permission_document_type_edit, user=request.user, - obj=self.get_document_type() + obj=self.get_document_type(), + permissions=(permission_document_type_edit,), + user=request.user ) return super(DocumentTypeFilenameCreateView, self).dispatch( diff --git a/mayan/apps/documents/views/document_version_views.py b/mayan/apps/documents/views/document_version_views.py index 304f7eebf0..111c46b827 100644 --- a/mayan/apps/documents/views/document_version_views.py +++ b/mayan/apps/documents/views/document_version_views.py @@ -32,8 +32,9 @@ logger = logging.getLogger(__name__) class DocumentVersionListView(SingleObjectListView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( - permissions=permission_document_version_view, user=request.user, - obj=self.get_document() + obj=self.get_document(), + permissions=(permission_document_version_view,), + user=request.user ) self.get_document().add_as_recent_document_for_user(request.user) diff --git a/mayan/apps/documents/views/document_views.py b/mayan/apps/documents/views/document_views.py index 9d5f3fc750..c80200e76c 100644 --- a/mayan/apps/documents/views/document_views.py +++ b/mayan/apps/documents/views/document_views.py @@ -318,8 +318,8 @@ class DocumentDownloadView(SingleObjectDownloadView): class DocumentDuplicatesListView(DocumentListView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( - permissions=permission_document_view, user=self.request.user, - obj=self.get_document() + obj=self.get_document(), permissions=(permission_document_view,), + user=self.request.user ) return super( @@ -582,8 +582,8 @@ class DocumentTransformationsCloneView(FormView): instance = get_object_or_404(klass=Document, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permissions=permission_transformation_edit, - user=self.request.user, obj=instance + obj=instance, permissions=(permission_transformation_edit,), + user=self.request.user ) instance.add_as_recent_document_for_user(self.request.user) @@ -597,8 +597,8 @@ class DocumentPrint(FormView): def dispatch(self, request, *args, **kwargs): instance = self.get_object() AccessControlList.objects.check_access( - permissions=permission_document_print, user=self.request.user, - obj=instance + obj=instance, permissions=(permission_document_print,), + user=self.request.user ) instance.add_as_recent_document_for_user(self.request.user) diff --git a/mayan/apps/documents/views/trashed_document_views.py b/mayan/apps/documents/views/trashed_document_views.py index e546f5a56b..1e2d3a033f 100644 --- a/mayan/apps/documents/views/trashed_document_views.py +++ b/mayan/apps/documents/views/trashed_document_views.py @@ -3,13 +3,13 @@ from __future__ import absolute_import, unicode_literals import logging from django.contrib import messages -from django.shortcuts import get_object_or_404 -from django.urls import reverse, reverse_lazy -from django.utils.translation import ugettext_lazy as _ +from django.urls import reverse_lazy +from django.utils.translation import ugettext_lazy as _, ungettext from mayan.apps.acls.models import AccessControlList -from mayan.apps.common.generics import ConfirmView -from mayan.apps.common.mixins import MultipleInstanceActionMixin +from mayan.apps.common.generics import ( + ConfirmView, MultipleObjectConfirmActionView +) from ..icons import icon_document_list_deleted from ..models import DeletedDocument, Document @@ -23,64 +23,100 @@ from ..tasks import task_delete_document from .document_views import DocumentListView __all__ = ( - 'DeletedDocumentDeleteView', 'DeletedDocumentDeleteManyView', - 'DeletedDocumentListView', 'DocumentRestoreView', 'DocumentRestoreManyView', - 'DocumentTrashView', 'DocumentTrashManyView', 'EmptyTrashCanView' + 'DocumentTrashView', 'EmptyTrashCanView', 'TrashedDocumentDeleteView', + 'TrashedDocumentListView', 'TrashedDocumentRestoreView' ) logger = logging.getLogger(__name__) -class DeletedDocumentDeleteView(ConfirmView): +class DocumentTrashView(MultipleObjectConfirmActionView): + model = Document + object_permission = permission_document_trash + pk_url_kwarg = 'pk' + success_message_singular = _( + '%(count)d document moved to the trash.' + ) + success_message_plural = _( + '%(count)d documents moved to the trash.' + ) + + def get_extra_context(self): + queryset = self.get_object_list() + + result = { + 'title': ungettext( + single='Move the selected document to the trash?', + plural='Move the selected documents to the trash?', + number=queryset.count() + ) + } + + return result + + def object_action(self, form, instance): + instance.delete() + + +class EmptyTrashCanView(ConfirmView): extra_context = { - 'title': _('Delete the selected document?') + 'title': _('Empty trash?') } - - def object_action(self, instance): - source_document = get_object_or_404( - klass=Document.passthrough, pk=instance.pk - ) - - AccessControlList.objects.check_access( - permissions=permission_document_delete, user=self.request.user, - obj=source_document - ) - - task_delete_document.apply_async( - kwargs={'deleted_document_id': instance.pk} - ) + view_permission = permission_empty_trash + action_cancel_redirect = post_action_redirect = reverse_lazy( + 'documents:document_list_deleted' + ) def view_action(self): - instance = get_object_or_404( - klass=DeletedDocument, pk=self.kwargs['pk'] - ) - self.object_action(instance=instance) - messages.success( - self.request, _('Document: %(document)s deleted.') % { - 'document': instance - } - ) + for deleted_document in DeletedDocument.objects.all(): + task_delete_document.apply_async( + kwargs={'trashed_document_id': deleted_document.pk} + ) + + messages.success(self.request, _('Trash emptied successfully')) -class DeletedDocumentDeleteManyView(MultipleInstanceActionMixin, DeletedDocumentDeleteView): - extra_context = { - 'title': _('Delete the selected documents?') - } +class TrashedDocumentDeleteView(MultipleObjectConfirmActionView): model = DeletedDocument - success_message = '%(count)d document deleted.' - success_message_plural = '%(count)d documents deleted.' + object_permission = permission_document_delete + pk_url_kwarg = 'pk' + success_message_singular = _( + '%(count)d trashed document deleted.' + ) + success_message_plural = _( + '%(count)d trashed documents deleted.' + ) + + def get_extra_context(self): + queryset = self.get_object_list() + + result = { + 'title': ungettext( + single='Delete the selected trashed document?', + plural='Delete the selected trashed documents?', + number=queryset.count() + ) + } + + return result + + def object_action(self, form, instance): + task_delete_document.apply_async( + kwargs={'trashed_document_id': instance.pk} + ) -class DeletedDocumentListView(DocumentListView): +class TrashedDocumentListView(DocumentListView): object_permission = None def get_document_queryset(self): return AccessControlList.objects.filter_by_access( - permission_document_view, self.request.user, - queryset=DeletedDocument.trash.all() + permission=permission_document_view, + queryset=DeletedDocument.trash.all(), + user=self.request.user ) def get_extra_context(self): - context = super(DeletedDocumentListView, self).get_extra_context() + context = super(TrashedDocumentListView, self).get_extra_context() context.update( { 'hide_link': True, @@ -99,103 +135,29 @@ class DeletedDocumentListView(DocumentListView): return context -class DocumentRestoreView(ConfirmView): - extra_context = { - 'title': _('Restore the selected document?') - } - - def object_action(self, instance): - source_document = get_object_or_404( - klass=Document.passthrough, pk=instance.pk - ) - - AccessControlList.objects.check_access( - permissions=permission_document_restore, user=self.request.user, - obj=source_document - ) - - instance.restore() - - def view_action(self): - instance = get_object_or_404( - klass=DeletedDocument, pk=self.kwargs['pk'] - ) - - self.object_action(instance=instance) - - messages.success( - self.request, _('Document: %(document)s restored.') % { - 'document': instance - } - ) - - -class DocumentRestoreManyView(MultipleInstanceActionMixin, DocumentRestoreView): - extra_context = { - 'title': _('Restore the selected documents?') - } +class TrashedDocumentRestoreView(MultipleObjectConfirmActionView): model = DeletedDocument - success_message = '%(count)d document restored.' - success_message_plural = '%(count)d documents restored.' - - -class DocumentTrashView(ConfirmView): - def get_extra_context(self): - return { - 'object': self.get_object(), - 'title': _('Move "%s" to the trash?') % self.get_object() - } - - def get_object(self): - return get_object_or_404(klass=Document, pk=self.kwargs['pk']) - - def get_post_action_redirect(self): - return reverse('documents:document_list_recent_access') - - def object_action(self, instance): - AccessControlList.objects.check_access( - permissions=permission_document_trash, user=self.request.user, - obj=instance - ) - - instance.delete() - - def view_action(self): - instance = self.get_object() - - self.object_action(instance=instance) - - messages.success( - self.request, _('Document: %(document)s moved to trash successfully.') % { - 'document': instance - } - ) - - -class DocumentTrashManyView(MultipleInstanceActionMixin, DocumentTrashView): - model = Document - success_message = '%(count)d document moved to the trash.' - success_message_plural = '%(count)d documents moved to the trash.' - - def get_extra_context(self): - return { - 'title': _('Move the selected documents to the trash?') - } - - -class EmptyTrashCanView(ConfirmView): - extra_context = { - 'title': _('Empty trash?') - } - view_permission = permission_empty_trash - action_cancel_redirect = post_action_redirect = reverse_lazy( - 'documents:document_list_deleted' + object_permission = permission_document_restore + pk_url_kwarg = 'pk' + success_message_singular = _( + '%(count)d trashed document restored.' + ) + success_message_plural = _( + '%(count)d trashed documents restored.' ) - def view_action(self): - for deleted_document in DeletedDocument.objects.all(): - task_delete_document.apply_async( - kwargs={'deleted_document_id': deleted_document.pk} - ) + def get_extra_context(self): + queryset = self.get_object_list() - messages.success(self.request, _('Trash emptied successfully')) + result = { + 'title': ungettext( + single='Restore the selected trashed document?', + plural='Restore the selected trashed documents?', + number=queryset.count() + ) + } + + return result + + def object_action(self, form, instance): + instance.restore() diff --git a/mayan/apps/events/api_views.py b/mayan/apps/events/api_views.py index 195a117832..dff3b7a3f2 100644 --- a/mayan/apps/events/api_views.py +++ b/mayan/apps/events/api_views.py @@ -42,8 +42,8 @@ class APIObjectEventListView(generics.ListAPIView): obj = self.get_object() AccessControlList.objects.check_access( - permissions=permission_events_view, user=self.request.user, - obj=obj + obj=obj, permissions=(permission_events_view,), + user=self.request.user ) return any_stream(obj) diff --git a/mayan/apps/events/classes.py b/mayan/apps/events/classes.py index ac4a822fc1..fea995efe0 100644 --- a/mayan/apps/events/classes.py +++ b/mayan/apps/events/classes.py @@ -113,8 +113,9 @@ class EventType(object): if result.target: try: AccessControlList.objects.check_access( - permissions=permission_events_view, - user=user, obj=result.target + obj=result.target, + permissions=(permission_events_view,), + user=user ) except PermissionDenied: pass @@ -139,8 +140,9 @@ class EventType(object): if relationship.exists(): try: AccessControlList.objects.check_access( - permissions=permission_events_view, - user=user, obj=result.target + obj=result.target, + permissions=(permission_events_view,), + user=user ) except PermissionDenied: pass @@ -161,8 +163,9 @@ class EventType(object): if relationship.exists(): try: AccessControlList.objects.check_access( - permissions=permission_events_view, - user=user, obj=result.action_object + obj=result.action_object, + permissions=(permission_events_view,), + user=user ) except PermissionDenied: pass @@ -191,7 +194,6 @@ class ModelEventType(object): Class to allow matching a model to a specific set of events. """ _inheritances = {} - _proxies = {} _registry = {} @classmethod @@ -211,11 +213,6 @@ class ModelEventType(object): if class_events: events.extend(class_events) - proxy = cls._proxies.get(type(instance)) - - if proxy: - events.extend(cls._registry.get(proxy)) - pks = [ event.id for event in set(events) ] @@ -237,7 +234,3 @@ class ModelEventType(object): @classmethod def register_inheritance(cls, model, related): cls._inheritances[model] = related - - @classmethod - def register_proxy(cls, source, model): - cls._proxies[model] = source diff --git a/mayan/apps/events/views.py b/mayan/apps/events/views.py index 933cc610d7..3b0ce90233 100644 --- a/mayan/apps/events/views.py +++ b/mayan/apps/events/views.py @@ -234,7 +234,7 @@ class ObjectEventTypeSubscriptionListView(FormView): raise Http404 AccessControlList.objects.check_access( - obj=content_object, permissions=permission_events_view, + obj=content_object, permissions=(permission_events_view,), user=self.request.user ) diff --git a/mayan/apps/linking/api_views.py b/mayan/apps/linking/api_views.py index 66d2cac738..63f23ac69d 100644 --- a/mayan/apps/linking/api_views.py +++ b/mayan/apps/linking/api_views.py @@ -35,7 +35,7 @@ class APIResolvedSmartLinkDocumentListView(generics.ListAPIView): document = get_object_or_404(klass=Document, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - obj=document, permissions=permission_document_view, + obj=document, permissions=(permission_document_view,), user=self.request.user ) @@ -48,7 +48,7 @@ class APIResolvedSmartLinkDocumentListView(generics.ListAPIView): ) AccessControlList.objects.check_access( - obj=smart_link, permissions=permission_smart_link_view, + obj=smart_link, permissions=(permission_smart_link_view,), user=self.request.user ) @@ -91,7 +91,7 @@ class APIResolvedSmartLinkView(generics.RetrieveAPIView): document = get_object_or_404(klass=Document, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - obj=document, permissions=permission_document_view, + obj=document, permissions=(permission_document_view,), user=self.request.user ) @@ -128,7 +128,7 @@ class APIResolvedSmartLinkListView(generics.ListAPIView): document = get_object_or_404(klass=Document, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - obj=document, permissions=permission_document_view, + obj=document, permissions=(permission_document_view,), user=self.request.user ) @@ -187,7 +187,7 @@ class APISmartLinkConditionListView(generics.ListCreateAPIView): smart_link = get_object_or_404(klass=SmartLink, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - obj=smart_link, permissions=permission_required, + obj=smart_link, permissions=(permission_required,), user=self.request.user ) @@ -230,7 +230,7 @@ class APISmartLinkConditionView(generics.RetrieveUpdateDestroyAPIView): smart_link = get_object_or_404(klass=SmartLink, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - obj=smart_link, permissions=permission_required, + obj=smart_link, permissions=(permission_required,), user=self.request.user ) diff --git a/mayan/apps/linking/views.py b/mayan/apps/linking/views.py index c34b936ae7..0284f09932 100644 --- a/mayan/apps/linking/views.py +++ b/mayan/apps/linking/views.py @@ -88,12 +88,12 @@ class ResolvedSmartLinkView(DocumentListView): ) AccessControlList.objects.check_access( - obj=self.document, permissions=permission_document_view, + obj=self.document, permissions=(permission_document_view,), user=request.user ) AccessControlList.objects.check_access( - obj=self.smart_link, permissions=permission_smart_link_view, + obj=self.smart_link, permissions=(permission_smart_link_view,), user=request.user ) @@ -109,8 +109,9 @@ class ResolvedSmartLinkView(DocumentListView): try: AccessControlList.objects.check_access( - obj=self.smart_link, permissions=permission_smart_link_edit, - user=self.request.user, + obj=self.smart_link, + permissions=(permission_smart_link_edit,), + user=self.request.user ) except PermissionDenied: pass @@ -205,7 +206,7 @@ class DocumentSmartLinkListView(SmartLinkListView): self.document = get_object_or_404(klass=Document, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - obj=self.document, permissions=permission_document_view, + obj=self.document, permissions=(permission_document_view,), user=request.user ) @@ -319,7 +320,8 @@ class SmartLinkConditionCreateView(SingleObjectCreateView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( - obj=self.get_smart_link(), permissions=permission_smart_link_edit, + obj=self.get_smart_link(), + permissions=(permission_smart_link_edit,), user=request.user ) @@ -359,7 +361,7 @@ class SmartLinkConditionEditView(SingleObjectEditView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( obj=self.get_object().smart_link, - permissions=permission_smart_link_edit, user=request.user + permissions=(permission_smart_link_edit,), user=request.user ) return super( @@ -388,7 +390,7 @@ class SmartLinkConditionDeleteView(SingleObjectDeleteView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( obj=self.get_object().smart_link, - permissions=permission_smart_link_edit, user=request.user + permissions=(permission_smart_link_edit,), user=request.user ) return super( diff --git a/mayan/apps/mailer/views.py b/mayan/apps/mailer/views.py index cc09818a0f..5413763652 100644 --- a/mayan/apps/mailer/views.py +++ b/mayan/apps/mailer/views.py @@ -85,8 +85,8 @@ class MailDocumentView(MultipleObjectFormActionView): def object_action(self, form, instance): AccessControlList.objects.check_access( - permissions=permission_user_mailer_use, user=self.request.user, - obj=form.cleaned_data['user_mailer'] + obj=form.cleaned_data['user_mailer'], + permissions=(permission_user_mailer_use,), user=self.request.user ) task_send_document.apply_async( @@ -261,8 +261,8 @@ class UserMailerTestView(FormView): def get_object(self): user_mailer = get_object_or_404(klass=UserMailer, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permissions=permission_user_mailer_use, user=self.request.user, - obj=user_mailer + obj=user_mailer, permissions=(permission_user_mailer_use,), + user=self.request.user ) return user_mailer diff --git a/mayan/apps/metadata/api_views.py b/mayan/apps/metadata/api_views.py index 879dab4f33..2a87e8e3ff 100644 --- a/mayan/apps/metadata/api_views.py +++ b/mayan/apps/metadata/api_views.py @@ -43,8 +43,8 @@ class APIDocumentMetadataListView(generics.ListCreateAPIView): ) AccessControlList.objects.check_access( - permissions=permission_required, user=self.request.user, - obj=document + obj=document, permissions=(permission_required,), + user=self.request.user ) return document @@ -103,8 +103,8 @@ class APIDocumentMetadataView(generics.RetrieveUpdateDestroyAPIView): ) AccessControlList.objects.check_access( - permissions=permission_required, user=self.request.user, - obj=document + obj=document, permissions=(permission_required,), + user=self.request.user ) return document @@ -175,8 +175,8 @@ class APIDocumentTypeMetadataTypeListView(generics.ListCreateAPIView): ) AccessControlList.objects.check_access( - permissions=permission_required, user=self.request.user, - obj=document_type + obj=document_type, permissions=(permission_required,), + user=self.request.user ) return document_type @@ -232,8 +232,8 @@ class APIDocumentTypeMetadataTypeView(generics.RetrieveUpdateDestroyAPIView): ) AccessControlList.objects.check_access( - permissions=permission_required, user=self.request.user, - obj=document_type + obj=document_type, permissions=(permission_required,), + user=self.request.user ) return document_type diff --git a/mayan/apps/metadata/views.py b/mayan/apps/metadata/views.py index 178a39c784..13606e948a 100644 --- a/mayan/apps/metadata/views.py +++ b/mayan/apps/metadata/views.py @@ -397,8 +397,9 @@ class DocumentMetadataEditView(MultipleObjectFormActionView): class DocumentMetadataListView(SingleObjectListView): def dispatch(self, request, *args, **kwargs): AccessControlList.objects.check_access( - permissions=permission_metadata_document_view, - user=self.request.user, obj=self.get_document() + obj=self.get_document(), + permissions=(permission_metadata_document_view,), + user=self.request.user ) return super(DocumentMetadataListView, self).dispatch( @@ -725,8 +726,8 @@ class SetupDocumentTypeMetadataTypes(FormView): obj = get_object_or_404(klass=self.model, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permissions=(permission_metadata_type_edit,), - user=self.request.user, obj=obj + obj=obj, permissions=(permission_metadata_type_edit,), + user=self.request.user ) return obj diff --git a/mayan/apps/navigation/classes.py b/mayan/apps/navigation/classes.py index 76ac3b50ec..d8bc54fc7c 100644 --- a/mayan/apps/navigation/classes.py +++ b/mayan/apps/navigation/classes.py @@ -48,7 +48,7 @@ class Link(object): conditional_disable=None, description=None, html_data=None, html_extra_classes=None, icon_class=None, icon_class_path=None, keep_query=False, kwargs=None, name=None, permissions=None, - permissions_related=None, remove_from_query=None, tags=None, url=None + remove_from_query=None, tags=None, url=None ): self.args = args or [] self.badge_text = badge_text @@ -62,7 +62,6 @@ class Link(object): self.kwargs = kwargs or {} self.name = name self.permissions = permissions or [] - self.permissions_related = permissions_related self.remove_from_query = remove_from_query or [] self.tags = tags self.text = text @@ -117,13 +116,13 @@ class Link(object): try: AccessControlList.objects.check_access( obj=resolved_object, permissions=self.permissions, - related=self.permissions_related, user=request.user + user=request.user ) except PermissionDenied: return None else: try: - Permission.check_permissions( + Permission.check_user_permissions( permissions=self.permissions, user=request.user ) except PermissionDenied: @@ -567,7 +566,7 @@ class SourceColumn(object): except KeyError: try: # Might be a subclass, try its root class - result.extend(cls._registry[source.__class__.__mro__[-2]]) + result = cls._registry[source.__class__.__mro__[-2]] except KeyError: try: # Might be an inherited class insance, try its source class diff --git a/mayan/apps/navigation/utils.py b/mayan/apps/navigation/utils.py index 34b3f98eb8..41de539cd5 100644 --- a/mayan/apps/navigation/utils.py +++ b/mayan/apps/navigation/utils.py @@ -27,7 +27,7 @@ def get_cascade_condition(app_label, model_name, object_permission, view_permiss if view_permission: try: - Permission.check_permissions( + Permission.check_user_permissions( permissions=(view_permission,), user=context.request.user ) except PermissionDenied: diff --git a/mayan/apps/permissions/classes.py b/mayan/apps/permissions/classes.py index ef857f862c..3d6b94da7f 100644 --- a/mayan/apps/permissions/classes.py +++ b/mayan/apps/permissions/classes.py @@ -73,7 +73,8 @@ class Permission(object): ) @classmethod - def check_permissions(cls, permissions, user): + def check_user_permissions(cls, permissions, user): + # TODO: Remove list check. Add permissions arguments will be lists. try: for permission in permissions: if permission.stored_permission.user_has_this(user=user): diff --git a/mayan/apps/permissions/tests/test_models.py b/mayan/apps/permissions/tests/test_models.py index 9297b27695..74adff0788 100644 --- a/mayan/apps/permissions/tests/test_models.py +++ b/mayan/apps/permissions/tests/test_models.py @@ -26,7 +26,7 @@ class PermissionTestCase(GroupTestMixin, PermissionTestMixin, RoleTestMixin, Use def test_no_permissions(self): with self.assertRaises(PermissionDenied): - Permission.check_permissions( + Permission.check_user_permissions( permissions=(self.test_permission,), user=self.test_user ) @@ -36,7 +36,7 @@ class PermissionTestCase(GroupTestMixin, PermissionTestMixin, RoleTestMixin, Use self.test_role.groups.add(self.test_group) try: - Permission.check_permissions( + Permission.check_user_permissions( permissions=(self.test_permission,), user=self.test_user ) except PermissionDenied: diff --git a/mayan/apps/rest_api/permissions.py b/mayan/apps/rest_api/permissions.py index 69147c1f86..9b650989a2 100644 --- a/mayan/apps/rest_api/permissions.py +++ b/mayan/apps/rest_api/permissions.py @@ -12,14 +12,14 @@ from mayan.apps.permissions import Permission class MayanPermission(BasePermission): def has_permission(self, request, view): - required_permission = getattr( + required_permissions = getattr( view, 'mayan_view_permissions', {} ).get(request.method, None) - if required_permission: + if required_permissions: try: - Permission.check_permissions( - permissions=required_permission, user=request.user + Permission.check_user_permissions( + permissions=required_permissions, user=request.user ) except PermissionDenied: return False @@ -29,23 +29,16 @@ class MayanPermission(BasePermission): return True def has_object_permission(self, request, view, obj): - required_permission = getattr( + required_permissions = getattr( view, 'mayan_object_permissions', {} ).get(request.method, None) - if required_permission: + if required_permissions: try: - if hasattr(view, 'mayan_permission_attribute_check'): - AccessControlList.objects.check_access( - permissions=required_permission, - user=request.user, obj=obj, - related=view.mayan_permission_attribute_check - ) - else: - AccessControlList.objects.check_access( - permissions=required_permission, user=request.user, - obj=obj - ) + AccessControlList.objects.check_access( + obj=obj, permissions=required_permissions, + user=request.user + ) except PermissionDenied: return False else: diff --git a/mayan/apps/sources/tests/test_views.py b/mayan/apps/sources/tests/test_views.py index d2b7f00c7e..50a401684d 100644 --- a/mayan/apps/sources/tests/test_views.py +++ b/mayan/apps/sources/tests/test_views.py @@ -248,7 +248,7 @@ class StagingFolderViewTestCase(GenericViewTestCase): } ) - def test_staging_folder_delete_no_permission(self): + def test_staging_file_delete_no_permission(self): staging_folder = StagingFolderSource.objects.create( label=TEST_SOURCE_LABEL, folder_path=self.temporary_directory, @@ -263,11 +263,11 @@ class StagingFolderViewTestCase(GenericViewTestCase): response = self._request_staging_file_delete_view( staging_folder=staging_folder, staging_file=staging_file ) - self.assertEqual(response.status_code, 403) + self.assertEqual(response.status_code, 404) self.assertEqual(len(list(staging_folder.get_files())), 1) - def test_staging_folder_delete_with_permission(self): + def test_staging_file_delete_with_permission(self): self.grant_permission(permission=permission_staging_file_delete) staging_folder = StagingFolderSource.objects.create( diff --git a/mayan/apps/sources/views.py b/mayan/apps/sources/views.py index 7660b0baaa..d7313d3e6b 100644 --- a/mayan/apps/sources/views.py +++ b/mayan/apps/sources/views.py @@ -14,12 +14,13 @@ from django.utils.translation import ugettext_lazy as _ from mayan.apps.acls.models import AccessControlList from mayan.apps.checkouts.models import NewVersionBlock -from mayan.apps.common.menus import menu_facet -from mayan.apps.common.models import SharedUploadedFile from mayan.apps.common.generics import ( ConfirmView, MultiFormView, SingleObjectCreateView, SingleObjectDeleteView, SingleObjectEditView, SingleObjectListView ) +from mayan.apps.common.menus import menu_facet +from mayan.apps.common.mixins import ExternalObjectMixin +from mayan.apps.common.models import SharedUploadedFile from mayan.apps.documents.models import DocumentType, Document from mayan.apps.documents.permissions import ( permission_document_create, permission_document_new_version @@ -205,7 +206,7 @@ class UploadInteractiveView(UploadBaseView): ) AccessControlList.objects.check_access( - obj=self.document_type, permissions=permission_document_create, + obj=self.document_type, permissions=(permission_document_create,), user=request.user ) @@ -386,7 +387,7 @@ class UploadInteractiveVersionView(UploadBaseView): ) AccessControlList.objects.check_access( - obj=self.document, permissions=permission_document_new_version, + obj=self.document, permissions=(permission_document_new_version,), user=self.request.user ) @@ -474,28 +475,22 @@ class UploadInteractiveVersionView(UploadBaseView): return context -class StagingFileDeleteView(SingleObjectDeleteView): - object_permission = permission_staging_file_delete - object_permission_related = 'staging_folder' +class StagingFileDeleteView(ExternalObjectMixin, SingleObjectDeleteView): + external_object_class = StagingFolderSource + external_object_permission = permission_staging_file_delete def get_extra_context(self): return { - 'object': self.get_object(), + 'object': self.object, 'object_name': _('Staging file'), - 'source': self.get_source(), + 'title': _('Delete staging file "%s"?') % self.object, } def get_object(self): - source = self.get_source() - return source.get_file( + return self.external_object.get_file( encoded_filename=self.kwargs['encoded_filename'] ) - def get_source(self): - return get_object_or_404( - klass=StagingFolderSource, pk=self.kwargs['pk'] - ) - # Setup views class SetupSourceCheckView(ConfirmView): diff --git a/mayan/apps/tags/api_views.py b/mayan/apps/tags/api_views.py index 86d0daba2e..a060fe707e 100644 --- a/mayan/apps/tags/api_views.py +++ b/mayan/apps/tags/api_views.py @@ -89,7 +89,7 @@ class APITagDocumentListView(generics.ListAPIView): tag = get_object_or_404(klass=Tag, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permissions=permission_tag_view, user=self.request.user, obj=tag + obj=tag, permissions=(permission_tag_view,), user=self.request.user ) return tag.documents.all() @@ -113,8 +113,8 @@ class APIDocumentTagListView(generics.ListCreateAPIView): document = self.get_document() AccessControlList.objects.check_access( - permissions=permission_document_view, user=self.request.user, - obj=document + obj=document, permissions=(permission_document_view,), + user=self.request.user ) return document.attached_tags().all() @@ -165,8 +165,8 @@ class APIDocumentTagView(generics.RetrieveDestroyAPIView): document = get_object_or_404(klass=Document, pk=self.kwargs['document_pk']) AccessControlList.objects.check_access( - permissions=permission_document_view, user=self.request.user, - obj=document + obj=document, permissions=(permission_document_view,), + user=self.request.user ) return document diff --git a/mayan/apps/tags/serializers.py b/mayan/apps/tags/serializers.py index ddaac4d0e1..ed3cfa495e 100644 --- a/mayan/apps/tags/serializers.py +++ b/mayan/apps/tags/serializers.py @@ -112,7 +112,7 @@ class NewDocumentTagSerializer(serializers.Serializer): try: AccessControlList.objects.check_access( - obj=tag, permissions=permission_tag_attach, + obj=tag, permissions=(permission_tag_attach,), user=self.context['request'].user ) except PermissionDenied: diff --git a/mayan/apps/tags/views.py b/mayan/apps/tags/views.py index 56f551fafb..f3bd979e44 100644 --- a/mayan/apps/tags/views.py +++ b/mayan/apps/tags/views.py @@ -100,7 +100,7 @@ class TagAttachActionView(MultipleObjectFormActionView): for tag in form.cleaned_data['tags']: AccessControlList.objects.check_access( - obj=tag, permissions=permission_tag_attach, + obj=tag, permissions=(permission_tag_attach,), user=self.request.user ) @@ -251,7 +251,7 @@ class DocumentTagListView(TagListView): self.document = get_object_or_404(klass=Document, pk=self.kwargs['pk']) AccessControlList.objects.check_access( - obj=self.document, permissions=permission_document_view, + obj=self.document, permissions=(permission_document_view,), user=request.user, ) @@ -348,7 +348,7 @@ class TagRemoveActionView(MultipleObjectFormActionView): for tag in form.cleaned_data['tags']: AccessControlList.objects.check_access( - obj=tag, permissions=permission_tag_remove, + obj=tag, permissions=(permission_tag_remove,), user=self.request.user ) diff --git a/mayan/apps/user_management/api_views.py b/mayan/apps/user_management/api_views.py index 2d914424db..134cbe0e97 100644 --- a/mayan/apps/user_management/api_views.py +++ b/mayan/apps/user_management/api_views.py @@ -149,8 +149,7 @@ class APIUserGroupList(generics.ListCreateAPIView): user = get_object_or_404(klass=get_user_model(), pk=self.kwargs['pk']) AccessControlList.objects.check_access( - permissions=(permission,), user=self.request.user, - obj=user + obj=user, permissions=(permission,), user=self.request.user ) return user