diff --git a/HISTORY.rst b/HISTORY.rst index 7c3c2e7c33..848f47f2c6 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -58,6 +58,11 @@ - Renamed setting LOCK_MANAGER_DEFAULT_BACKEND to LOCK_MANAGER_BACKEND. - Add help texts to more setting options. - Add ACL support for metadata types. +- Add cascade permission checks for links. Avoid allowing users + to reach a empty views because they don't access to any of + the view's objects. +- Apply link permission cascade checks to the message of the day, + indexing and parsing, setup link. 3.0.1 (2018-07-08) ================= diff --git a/mayan/apps/document_indexing/links.py b/mayan/apps/document_indexing/links.py index 374b06d959..fb6b2b63e0 100644 --- a/mayan/apps/document_indexing/links.py +++ b/mayan/apps/document_indexing/links.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.utils.translation import ugettext_lazy as _ -from navigation import Link +from navigation import Link, get_cascade_condition from .icons import ( icon_document_index_list, icon_index, icon_rebuild_index_instances @@ -10,6 +10,7 @@ from .icons import ( from .permissions import ( permission_document_indexing_create, permission_document_indexing_edit, permission_document_indexing_delete, permission_document_indexing_rebuild, + permission_document_indexing_view ) @@ -26,7 +27,11 @@ link_index_main_menu = Link( view='indexing:index_list' ) link_index_setup = Link( - icon_class=icon_index, text=_('Indexes'), + condition=get_cascade_condition( + app_label='document_indexing', model_name='Index', + object_permission=permission_document_indexing_view, + view_permission=permission_document_indexing_create, + ), icon_class=icon_index, text=_('Indexes'), view='indexing:index_setup_list' ) link_index_setup_list = Link( diff --git a/mayan/apps/document_parsing/links.py b/mayan/apps/document_parsing/links.py index dab2411bbf..baadc9fc3b 100644 --- a/mayan/apps/document_parsing/links.py +++ b/mayan/apps/document_parsing/links.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.utils.translation import ugettext_lazy as _ -from navigation import Link +from navigation import Link, get_cascade_condition from .icons import ( icon_document_content, icon_document_parsing_errors_list, @@ -44,7 +44,11 @@ link_document_type_parsing_settings = Link( view='document_parsing:document_type_parsing_settings', ) link_document_type_submit = Link( - icon_class=icon_document_type_submit, text=_('Parse documents per type'), + condition=get_cascade_condition( + app_label='documents', model_name='DocumentType', + object_permission=permission_document_type_parsing_setup + ), icon_class=icon_document_type_submit, + text=_('Parse documents per type'), view='document_parsing:document_type_submit' ) link_error_list = Link( diff --git a/mayan/apps/motd/links.py b/mayan/apps/motd/links.py index 9d771469b2..1bdfb3e369 100644 --- a/mayan/apps/motd/links.py +++ b/mayan/apps/motd/links.py @@ -1,15 +1,19 @@ from __future__ import absolute_import, unicode_literals +from django.apps import apps +from django.core.exceptions import PermissionDenied from django.utils.translation import ugettext_lazy as _ -from navigation import Link +from navigation import Link, get_cascade_condition +from permissions import Permission from .icons import icon_message_list from .permissions import ( permission_message_create, permission_message_delete, - permission_message_edit, + permission_message_edit, permission_message_view ) + link_message_create = Link( permissions=(permission_message_create,), text=_('Create message'), view='motd:message_create' @@ -23,6 +27,10 @@ link_message_edit = Link( view='motd:message_edit' ) link_message_list = Link( - icon_class=icon_message_list, text=_('Message of the day'), + condition=get_cascade_condition( + app_label='motd', model_name='Message', + object_permission=permission_message_view, + view_permission=permission_message_create, + ), icon_class=icon_message_list, text=_('Message of the day'), view='motd:message_list' ) diff --git a/mayan/apps/navigation/__init__.py b/mayan/apps/navigation/__init__.py index d1d759d9f6..76eca37938 100644 --- a/mayan/apps/navigation/__init__.py +++ b/mayan/apps/navigation/__init__.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals from .classes import Link, Menu, SourceColumn # NOQA +from .utils import get_cascade_condition # NOQA default_app_config = 'navigation.apps.NavigationApp' diff --git a/mayan/apps/navigation/utils.py b/mayan/apps/navigation/utils.py new file mode 100644 index 0000000000..f933786ee6 --- /dev/null +++ b/mayan/apps/navigation/utils.py @@ -0,0 +1,41 @@ +from __future__ import absolute_import, unicode_literals + +from django.apps import apps +from django.core.exceptions import PermissionDenied +from django.utils.translation import ugettext_lazy as _ + +from permissions import Permission + + +def get_cascade_condition(app_label, model_name, object_permission, view_permission=None): + """ + Return a function that first checks to see if the user has the view + permission. If not, then filters the objects with the object permission + and return True if there is at least one item in the filtered queryset. + This is used to avoid showing a link that ends up in a view with an + empty results set. + """ + def condition(context): + AccessControlList = apps.get_model( + app_label='acls', model_name='AccessControlList' + ) + Model = apps.get_model(app_label=app_label, model_name=model_name) + + if view_permission: + try: + Permission.check_permissions( + requester=context.request.user, + permissions=(view_permission,) + ) + except PermissionDenied: + pass + else: + return True + + queryset = AccessControlList.objects.filter_by_access( + permission=object_permission, user=context.request.user, + queryset=Model.objects.all() + ) + return queryset.count() > 0 + + return condition