# HG changeset patch # User Thierry Florac # Date 1564139094 -7200 # Node ID a89ab799a1624ef851f8c9fe937aa487e012775a # Parent a5b80a5c43de60b141df932939f54af125c7aaf0 Updated permissions management of associations containers and items (including menus) diff -r a5b80a5c43de -r a89ab799a162 src/pyams_content/component/association/__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) diff -r a5b80a5c43de -r a89ab799a162 src/pyams_content/features/footer/__init__.py --- 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""" diff -r a5b80a5c43de -r a89ab799a162 src/pyams_content/features/header/__init__.py --- 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""" diff -r a5b80a5c43de -r a89ab799a162 src/pyams_content/features/menu/__init__.py --- 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 # diff -r a5b80a5c43de -r a89ab799a162 src/pyams_content/features/menu/zmi/__init__.py --- 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)