Updated permissions management of associations containers and items (including menus)
authorThierry Florac <tflorac@ulthar.net>
Fri, 26 Jul 2019 13:04:54 +0200
changeset 1338 a89ab799a162
parent 1337 a5b80a5c43de
child 1339 401655442ef0
Updated permissions management of associations containers and items (including menus)
src/pyams_content/component/association/__init__.py
src/pyams_content/features/footer/__init__.py
src/pyams_content/features/header/__init__.py
src/pyams_content/features/menu/__init__.py
src/pyams_content/features/menu/zmi/__init__.py
--- a/src/pyams_content/component/association/__init__.py	Fri Jul 26 13:02:34 2019 +0200
+++ b/src/pyams_content/component/association/__init__.py	Fri Jul 26 13:04:54 2019 +0200
@@ -10,8 +10,6 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
-
 from persistent import Persistent
 from pyramid.events import subscriber
 from pyramid.threadlocal import get_current_registry
@@ -21,13 +19,17 @@
 from zope.lifecycleevent.interfaces import IObjectAddedEvent, IObjectModifiedEvent, IObjectRemovedEvent
 from zope.schema.fieldproperty import FieldProperty
 
-from pyams_content.component.association.interfaces import IAssociationContainerTarget, IAssociationItem
+from pyams_content.component.association.interfaces import IAssociationContainer, IAssociationContainerTarget, \
+    IAssociationItem
 from pyams_form.interfaces.form import IFormContextPermissionChecker
 from pyams_utils.adapter import ContextAdapter, adapter_config
 from pyams_utils.traversing import get_parent
 from pyams_utils.url import absolute_url
 
 
+__docformat__ = 'restructuredtext'
+
+
 @implementer(IAssociationItem)
 class AssociationItem(Persistent, Contained):
     """Base association item persistent class"""
@@ -45,13 +47,15 @@
 
 
 @adapter_config(context=IAssociationItem, provides=IFormContextPermissionChecker)
-class AssociationItemPermissionChecker(ContextAdapter):
-    """Association item permission checker"""
+@adapter_config(context=IAssociationContainer, provides=IFormContextPermissionChecker)
+class MenuPermissionChecker(ContextAdapter):
+    """Menu permission checker"""
 
     @property
     def edit_permission(self):
-        content = get_parent(self.context, IAssociationContainerTarget)
-        return IFormContextPermissionChecker(content).edit_permission
+        parent = get_parent(self.context, IAssociationContainerTarget)
+        if parent is not None:
+            return IFormContextPermissionChecker(parent).edit_permission
 
 
 @subscriber(IObjectAddedEvent, context_selector=IAssociationItem)
--- a/src/pyams_content/features/footer/__init__.py	Fri Jul 26 13:02:34 2019 +0200
+++ b/src/pyams_content/features/footer/__init__.py	Fri Jul 26 13:04:54 2019 +0200
@@ -10,9 +10,8 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
-
 from persistent import Persistent
+from pyams_cache.beaker import get_cache
 from pyramid.events import subscriber
 from zope.lifecycleevent.interfaces import IObjectModifiedEvent
 from zope.location import Location
@@ -20,10 +19,11 @@
 from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
 from zope.traversing.interfaces import ITraversable
 
-from pyams_cache.beaker import get_cache
 from pyams_content.features.footer.interfaces import FOOTER_RENDERERS, FOOTER_RENDERER_SETTINGS_KEY, \
     FOOTER_SETTINGS_KEY, IFooterRenderer, IFooterRendererSettings, IFooterSettings, IFooterTarget
 from pyams_content.features.renderer import RenderedContentMixin
+from pyams_form.interfaces.form import IFormContextPermissionChecker
+from pyams_portal.interfaces import MANAGE_TEMPLATE_PERMISSION
 from pyams_portal.portlet import PORTLETS_CACHE_NAME, PORTLETS_CACHE_REGION
 from pyams_utils.adapter import ContextAdapter, adapter_config, get_annotation_adapter
 from pyams_utils.factory import factory_config
@@ -33,6 +33,9 @@
 from pyams_utils.vocabulary import vocabulary_config
 
 
+__docformat__ = 'restructuredtext'
+
+
 @factory_config(IFooterSettings)
 class FooterSettings(BaseInheritInfo, RenderedContentMixin, Persistent, Location):
     """Footer settings persistent class"""
@@ -117,6 +120,13 @@
     return IFooterRendererSettings(settings, None)
 
 
+@adapter_config(context=IFooterRendererSettings, provides=IFormContextPermissionChecker)
+class FooterRendererSettingsPermissionChecker(ContextAdapter):
+    """Footer renderer settings permission checker"""
+
+    edit_permission = MANAGE_TEMPLATE_PERMISSION
+
+
 @vocabulary_config(name=FOOTER_RENDERERS)
 class FooterRendererVocabulary(SimpleVocabulary):
     """Footer renderers vocabulary"""
--- a/src/pyams_content/features/header/__init__.py	Fri Jul 26 13:02:34 2019 +0200
+++ b/src/pyams_content/features/header/__init__.py	Fri Jul 26 13:04:54 2019 +0200
@@ -10,9 +10,8 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
-
 from persistent import Persistent
+from pyams_cache.beaker import get_cache
 from pyramid.events import subscriber
 from zope.lifecycleevent.interfaces import IObjectModifiedEvent
 from zope.location import Location
@@ -20,10 +19,11 @@
 from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
 from zope.traversing.interfaces import ITraversable
 
-from pyams_cache.beaker import get_cache
 from pyams_content.features.header.interfaces import HEADER_RENDERERS, HEADER_RENDERER_SETTINGS_KEY, \
     HEADER_SETTINGS_KEY, IHeaderRenderer, IHeaderRendererSettings, IHeaderSettings, IHeaderTarget
 from pyams_content.features.renderer import RenderedContentMixin
+from pyams_form.interfaces.form import IFormContextPermissionChecker
+from pyams_portal.interfaces import MANAGE_TEMPLATE_PERMISSION
 from pyams_portal.portlet import PORTLETS_CACHE_NAME, PORTLETS_CACHE_REGION
 from pyams_utils.adapter import ContextAdapter, adapter_config, get_annotation_adapter
 from pyams_utils.factory import factory_config
@@ -33,6 +33,9 @@
 from pyams_utils.vocabulary import vocabulary_config
 
 
+__docformat__ = 'restructuredtext'
+
+
 @factory_config(IHeaderSettings)
 class HeaderSettings(BaseInheritInfo, RenderedContentMixin, Persistent, Location):
     """Header settings persistent class"""
@@ -117,6 +120,13 @@
     return IHeaderRendererSettings(settings, None)
 
 
+@adapter_config(context=IHeaderRendererSettings, provides=IFormContextPermissionChecker)
+class HeaderRendererSettingsPermissionChecker(ContextAdapter):
+    """Header renderer settings permission checker"""
+
+    edit_permission = MANAGE_TEMPLATE_PERMISSION
+
+
 @vocabulary_config(name=HEADER_RENDERERS)
 class HeaderRendererVocabulary(SimpleVocabulary):
     """Header renderers vocabulary"""
--- a/src/pyams_content/features/menu/__init__.py	Fri Jul 26 13:02:34 2019 +0200
+++ b/src/pyams_content/features/menu/__init__.py	Fri Jul 26 13:04:54 2019 +0200
@@ -10,15 +10,13 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
-
-from zope.interface import implementer
 from zope.schema.fieldproperty import FieldProperty
 
 from pyams_content.component.association.container import AssociationContainer
 from pyams_content.component.association.interfaces import IAssociationInfo
 from pyams_content.component.links import InternalLink, InternalLinkAssociationInfoAdapter, InternalReferenceMixin
-from pyams_content.features.menu.interfaces import IDynamicMenu, IMenu, IMenuLink, IMenusContainer
+from pyams_content.features.menu.interfaces import IDynamicMenu, IMenu, IMenuLink, IMenusContainer, \
+    IMenusContainerTarget
 from pyams_content.reference.pictograms import IPictogramTable
 from pyams_content.shared.common import IWfSharedContent
 from pyams_content.shared.site.interfaces import ISiteContainer
@@ -33,6 +31,9 @@
 from pyams_zmi.layer import IAdminLayer
 
 
+__docformat__ = 'restructuredtext'
+
+
 #
 # Menus classes
 #
--- a/src/pyams_content/features/menu/zmi/__init__.py	Fri Jul 26 13:02:34 2019 +0200
+++ b/src/pyams_content/features/menu/zmi/__init__.py	Fri Jul 26 13:04:54 2019 +0200
@@ -10,12 +10,11 @@
 # FOR A PARTICULAR PURPOSE.
 #
 
-__docformat__ = 'restructuredtext'
-
 import json
 
 from pyramid.decorator import reify
 from pyramid.exceptions import NotFound
+from pyramid.httpexceptions import HTTPForbidden
 from pyramid.renderers import render
 from pyramid.view import view_config
 from z3c.form import field
@@ -23,21 +22,20 @@
 from z3c.table.interfaces import IColumn, IValues
 from zope.interface import Interface, alsoProvides, implementer
 
-from pyams_content import _
 from pyams_content.component.association.zmi import AssociationsTable, AssociationsTablePublicNameColumn
 from pyams_content.component.links.zmi import ExternalLinkAddForm, ExternalLinkAddMenu, ExternalLinkPropertiesEditForm, \
     InternalLinkAddForm, InternalLinkAddMenu, InternalLinkPropertiesEditForm, MailtoLinkAddForm, MailtoLinkAddMenu, \
     MailtoLinkPropertiesEditForm
-from pyams_content.features.menu import IMenu, IMenuLink, IMenusContainer, Menu
+from pyams_content.features.menu import IMenu, IMenusContainer, Menu
 from pyams_content.features.menu.interfaces import IMenuExternalLink, IMenuInternalLink, IMenuLinksContainer, \
     IMenuLinksContainerTarget, IMenuMailtoLink, IMenusContainerTarget
 from pyams_content.reference.pictograms.zmi.widget import PictogramSelectFieldWidget
 from pyams_form.form import AJAXAddForm, AJAXEditForm, ajax_config
 from pyams_form.interfaces.form import IFormContextPermissionChecker
+from pyams_form.security import ProtectedFormObjectMixin
 from pyams_i18n.column import I18nAttrColumn
 from pyams_i18n.interfaces import II18n
 from pyams_pagelet.pagelet import pagelet_config
-from pyams_portal.interfaces import MANAGE_TEMPLATE_PERMISSION
 from pyams_sequence.interfaces import ISequentialIdInfo
 from pyams_skin.container import delete_container_element, switch_element_visibility
 from pyams_skin.event import get_json_switched_table_refresh_event, get_json_table_row_refresh_event
@@ -45,7 +43,7 @@
 from pyams_skin.layer import IPyAMSLayer
 from pyams_skin.table import BaseTable, I18nColumn, SorterColumn, TrashColumn, VisibilitySwitcherColumn, get_table_id
 from pyams_skin.viewlet.toolbar import ToolbarAction
-from pyams_utils.adapter import ContextAdapter, ContextRequestViewAdapter, adapter_config
+from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config
 from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
 from pyams_utils.traversing import get_parent
 from pyams_utils.url import absolute_url
@@ -56,6 +54,11 @@
 from pyams_zmi.zmi.table import InnerTableView
 
 
+__docformat__ = 'restructuredtext'
+
+from pyams_content import _
+
+
 #
 # Custom marker interfaces
 #
@@ -74,7 +77,7 @@
 
 @viewlet_config(name='add-menu.action', context=IMenusContainerTarget, layer=IPyAMSLayer,
                 view=IMenusView, manager=IWidgetTitleViewletManager, weight=10)
-class MenuAddAction(ToolbarAction):
+class MenuAddAction(ProtectedFormObjectMixin, ToolbarAction):
     """Menu add action"""
 
     label = _("Add menu...")
@@ -83,7 +86,7 @@
 
 
 @pagelet_config(name='add-menu.html', context=IMenusContainer, layer=IPyAMSLayer,
-                permission=MANAGE_TEMPLATE_PERMISSION)
+                permission=VIEW_SYSTEM_PERMISSION)
 @ajax_config(name='add-menu.json', context=IMenusContainer, layer=IPyAMSLayer, base=AJAXAddForm)
 class MenuAddForm(AdminDialogAddForm):
     """Menu add form"""
@@ -94,7 +97,7 @@
     fields = field.Fields(IMenu).select('title', 'reference', 'dynamic_menu', 'pictogram_name')
     fields['pictogram_name'].widgetFactory = PictogramSelectFieldWidget
 
-    edit_permission = MANAGE_TEMPLATE_PERMISSION
+    edit_permission = None  # managed by IFormContextPermissionChecker adapter
 
     def create(self, data):
         return Menu()
@@ -115,7 +118,7 @@
         }
 
 
-@pagelet_config(name='properties.html', context=IMenu, layer=IPyAMSLayer, permission=MANAGE_TEMPLATE_PERMISSION)
+@pagelet_config(name='properties.html', context=IMenu, layer=IPyAMSLayer, permission=VIEW_SYSTEM_PERMISSION)
 @ajax_config(name='properties.json', context=IMenu, layer=IPyAMSLayer)
 @implementer(IPropertiesEditForm)
 class MenuPropertiesEditForm(AdminDialogEditForm):
@@ -129,7 +132,7 @@
     fields = field.Fields(IMenu).select('title', 'reference', 'dynamic_menu', 'pictogram_name')
     fields['pictogram_name'].widgetFactory = PictogramSelectFieldWidget
 
-    edit_permission = MANAGE_TEMPLATE_PERMISSION
+    edit_permission = None  # managed by IFormContextPermissionChecker adapter
 
     def get_ajax_output(self, changes):
         output = super(self.__class__, self).get_ajax_output(changes)
@@ -147,13 +150,13 @@
 # Menus table views
 #
 
-class MenusTable(BaseTable):
+class MenusTable(ProtectedFormObjectMixin, BaseTable):
     """Menus table"""
 
     prefix = 'menus'
     associations_name = 'menus'
 
-    permission = MANAGE_TEMPLATE_PERMISSION
+    # permission = MANAGE_TEMPLATE_PERMISSION
     hide_header = True
     hide_body_toolbar = True
     sortOn = None
@@ -197,19 +200,15 @@
 
 @adapter_config(name='sorter', context=(IMenusContainerTarget, IPyAMSLayer, MenusTable),
                 provides=IColumn)
-class MenusTableSorterColumn(SorterColumn):
+class MenusTableSorterColumn(ProtectedFormObjectMixin, SorterColumn):
     """Menus table sorter column"""
 
-    permission = MANAGE_TEMPLATE_PERMISSION
-
 
 @adapter_config(name='show-hide', context=(IMenusContainerTarget, IPyAMSLayer, MenusTable),
                 provides=IColumn)
-class MenusTableShowHideColumn(VisibilitySwitcherColumn):
+class MenusTableShowHideColumn(ProtectedFormObjectMixin, VisibilitySwitcherColumn):
     """Menus table visibility switcher column"""
 
-    permission = MANAGE_TEMPLATE_PERMISSION
-
 
 @adapter_config(name='name', context=(IMenusContainerTarget, IPyAMSLayer, MenusTable), provides=IColumn)
 class MenusTableNameColumn(I18nColumn, I18nAttrColumn):
@@ -241,11 +240,9 @@
 
 
 @adapter_config(name='trash', context=(IMenusContainerTarget, IPyAMSLayer, MenusTable), provides=IColumn)
-class MenusTableTrashColumn(TrashColumn):
+class MenusTableTrashColumn(ProtectedFormObjectMixin, TrashColumn):
     """Menus table trash column"""
 
-    permission = MANAGE_TEMPLATE_PERMISSION
-
 
 @implementer(IMenusView)
 class MenusView(InnerTableView):
@@ -264,30 +261,39 @@
 #
 
 @view_config(name='set-menus-order.json', context=IMenusContainer, request_type=IPyAMSLayer,
-             permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+             renderer='json', xhr=True)
 def set_menus_order(request):
     """Update menus order"""
+    permission = IFormContextPermissionChecker(request.context).edit_permission
+    if not request.has_permission(permission):
+        raise HTTPForbidden()
     order = list(map(str, json.loads(request.params.get('names'))))
     request.context.updateOrder(order)
     return {'status': 'success'}
 
 
 @view_config(name='switch-menu-visibility.json', context=IMenusContainer, request_type=IPyAMSLayer,
-             permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+             renderer='json', xhr=True)
 def set_menu_visibility(request):
     """Set menu visibility"""
+    permission = IFormContextPermissionChecker(request.context).edit_permission
+    if not request.has_permission(permission):
+        raise HTTPForbidden()
     return switch_element_visibility(request, IMenusContainer)
 
 
 @view_config(name='delete-element.json', context=IMenusContainer, request_type=IPyAMSLayer,
-             permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+             renderer='json', xhr=True)
 def delete_menu(request):
     """Delete menu"""
+    permission = IFormContextPermissionChecker(request.context).edit_permission
+    if not request.has_permission(permission):
+        raise HTTPForbidden()
     return delete_container_element(request, ignore_permission=True)
 
 
 @view_config(name='get-menu-items.json', context=IMenusContainer, request_type=IPyAMSLayer,
-             permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+             permission=VIEW_SYSTEM_PERMISSION, renderer='json', xhr=True)
 def get_menu_items_table(request):
     """Get menu items table"""
     menu = request.context.get(str(request.params.get('object_name')))
@@ -320,7 +326,6 @@
     def prefix(self):
         return '{0}_links'.format(self.associations_name)
 
-    permission = MANAGE_TEMPLATE_PERMISSION
     hide_header = True
     hide_body_toolbar = True
 
@@ -374,18 +379,24 @@
 #
 
 @view_config(name='set-associations-order.json', context=IMenuLinksContainer, request_type=IPyAMSLayer,
-             permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+             renderer='json', xhr=True)
 def set_associations_order(request):
     """Update associations order"""
+    permission = IFormContextPermissionChecker(request.context).edit_permission
+    if not request.has_permission(permission):
+        raise HTTPForbidden()
     order = list(map(str, json.loads(request.params.get('names'))))
     request.context.updateOrder(order)
     return {'status': 'success'}
 
 
 @view_config(name='switch-association-visibility.json', context=IMenuLinksContainer, request_type=IPyAMSLayer,
-             permission=MANAGE_TEMPLATE_PERMISSION, renderer='json', xhr=True)
+             renderer='json', xhr=True)
 def set_association_visibility(request):
     """Set association visibility"""
+    permission = IFormContextPermissionChecker(request.context).edit_permission
+    if not request.has_permission(permission):
+        raise HTTPForbidden()
     return switch_element_visibility(request, IMenuLinksContainer)
 
 
@@ -393,13 +404,6 @@
 # Link add and edit forms
 #
 
-@adapter_config(context=IMenuLink, provides=IFormContextPermissionChecker)
-class MenuLinkPermissionChecker(ContextAdapter):
-    """Menu link permission checker"""
-
-    edit_permission = MANAGE_TEMPLATE_PERMISSION
-
-
 class LinkAJAXAddForm(AJAXAddForm):
     """Menu link add form, JSON renderer"""
 
@@ -443,19 +447,19 @@
 @viewlet_config(name='add-internal-link.menu', context=IMenuLinksContainerTarget, layer=IPyAMSLayer,
                 view=IMenuLinksView, manager=IToolbarAddingMenu, weight=50)
 @viewlet_config(name='add-internal-link.menu', context=IMenu, layer=IPyAMSLayer,
-                view=MenuLinksTable, manager=IToolbarAddingMenu, permission=MANAGE_TEMPLATE_PERMISSION, weight=50)
+                view=MenuLinksTable, manager=IToolbarAddingMenu, weight=50)
 class MenuInternalLinkAddMenu(InternalLinkAddMenu):
     """Header internal link add menu"""
 
 
 @pagelet_config(name='add-internal-link.html', context=IMenuLinksContainer, layer=IPyAMSLayer,
-                permission=MANAGE_TEMPLATE_PERMISSION)
+                permission=VIEW_SYSTEM_PERMISSION)
 @ajax_config(name='add-internal-link.json', context=IMenuLinksContainer, layer=IPyAMSLayer,
              base=LinkAJAXAddForm)
 class MenuInternalLinkAddForm(InternalLinkAddForm):
     """Menu internal link add form"""
 
-    edit_permission = MANAGE_TEMPLATE_PERMISSION
+    edit_permission = None  # managed by IFormContextPermissionChecker adapter
 
     def create(self, data):
         result = super(MenuInternalLinkAddForm, self).create(data)
@@ -486,19 +490,19 @@
 @viewlet_config(name='add-external-link.menu', context=IMenuLinksContainerTarget, view=IMenuLinksView,
                 layer=IPyAMSLayer, manager=IToolbarAddingMenu, weight=51)
 @viewlet_config(name='add-external-link.menu', context=IMenu, layer=IPyAMSLayer,
-                view=MenuLinksTable, manager=IToolbarAddingMenu, permission=MANAGE_TEMPLATE_PERMISSION, weight=51)
+                view=MenuLinksTable, manager=IToolbarAddingMenu, weight=51)
 class MenuExternalLinkAddMenu(ExternalLinkAddMenu):
     """Menu external link add menu"""
 
 
 @pagelet_config(name='add-external-link.html', context=IMenuLinksContainer, layer=IPyAMSLayer,
-                permission=MANAGE_TEMPLATE_PERMISSION)
+                permission=VIEW_SYSTEM_PERMISSION)
 @ajax_config(name='add-external-link.json', context=IMenuLinksContainer, layer=IPyAMSLayer,
              base=LinkAJAXAddForm)
 class MenuExternalLinkAddForm(ExternalLinkAddForm):
     """Menu external link add form"""
 
-    edit_permission = MANAGE_TEMPLATE_PERMISSION
+    edit_permission = None  # managed by IFormContextPermissionChecker adapter
 
     def create(self, data):
         result = super(MenuExternalLinkAddForm, self).create(data)
@@ -529,19 +533,19 @@
 @viewlet_config(name='add-mailto-link.menu', context=IMenuLinksContainerTarget, view=IMenuLinksView,
                 layer=IPyAMSLayer, manager=IToolbarAddingMenu, weight=51)
 @viewlet_config(name='add-mailto-link.menu', context=IMenu, layer=IPyAMSLayer,
-                view=MenuLinksTable, manager=IToolbarAddingMenu, permission=MANAGE_TEMPLATE_PERMISSION, weight=51)
+                view=MenuLinksTable, manager=IToolbarAddingMenu, weight=51)
 class MenuMailtoLinkAddMenu(MailtoLinkAddMenu):
     """Menu mailto link add menu"""
 
 
 @pagelet_config(name='add-mailto-link.html', context=IMenuLinksContainer, layer=IPyAMSLayer,
-                permission=MANAGE_TEMPLATE_PERMISSION)
+                permission=VIEW_SYSTEM_PERMISSION)
 @ajax_config(name='add-mailto-link.json', context=IMenuLinksContainer, layer=IPyAMSLayer,
              base=LinkAJAXAddForm)
 class MenuMailtoLinkAddForm(MailtoLinkAddForm):
     """Menu mailto link add form"""
 
-    edit_permission = MANAGE_TEMPLATE_PERMISSION
+    edit_permission = None  # managed by IFormContextPermissionChecker adapter
 
     def create(self, data):
         result = super(MenuMailtoLinkAddForm, self).create(data)