From 344a1d81e5d476deec95d8c6cd81086439d4857c Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Mon, 28 Jan 2013 16:00:06 +0100 Subject: [PATCH] allow better control of create actions by checking 'AssignAsParent': check action permissions + related fixes --- README.txt | 6 ++---- browser/action.py | 6 +++++- browser/common.py | 15 +++++++++++++-- browser/node.py | 29 +++++++++++++++++------------ browser/node_macros.pt | 5 +++-- compound/blog/browser.py | 1 + knowledge/browser.py | 3 +++ organize/browser/event.py | 1 + organize/stateful/configure.zcml | 8 ++++---- security/common.py | 2 +- security/interfaces.py | 8 +++++--- security/setter.py | 2 +- 12 files changed, 56 insertions(+), 30 deletions(-) diff --git a/README.txt b/README.txt index 8140683..6ec6fc2 100755 --- a/README.txt +++ b/README.txt @@ -2,8 +2,6 @@ loops - Linked Objects for Organization and Processing Services =============================================================== - ($Id$) - The loops platform consists up of three basic types of objects: (1) concepts: simple interconnected objects usually representing @@ -612,7 +610,7 @@ Actions >>> view.controller = Controller(view, request) >>> #view.setupController() - >>> actions = view.getActions('portlet') + >>> actions = view.getAllowedActions('portlet') >>> len(actions) 2 @@ -849,7 +847,7 @@ In order to provide suitable links for viewing or editing a target you may ask a view which view and edit actions it supports. We directly use the target object's view here: - >>> actions = view.virtualTarget.getActions('object', page=view) + >>> actions = view.virtualTarget.getAllowedActions('object', page=view) >>> #actions[0].url 'http://127.0.0.1/loops/views/m1/m11/m111/.target23' diff --git a/browser/action.py b/browser/action.py index 68176d9..06600c5 100644 --- a/browser/action.py +++ b/browser/action.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2012 Helmut Merz helmutm@cy55.de +# Copyright (c) 2013 Helmut Merz helmutm@cy55.de # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -129,6 +129,7 @@ actions.register('edit_object', 'portlet', DialogAction, viewName='edit_object.html', dialogName='edit', prerequisites=['registerDojoEditor'], + permission='zope.ManageContent', ) actions.register('edit_concept', 'portlet', DialogAction, @@ -137,6 +138,7 @@ actions.register('edit_concept', 'portlet', DialogAction, viewName='edit_concept.html', dialogName='edit', prerequisites=['registerDojoEditor'], + permission='zope.ManageContent', ) actions.register('create_concept', 'portlet', DialogAction, @@ -146,6 +148,7 @@ actions.register('create_concept', 'portlet', DialogAction, dialogName='createConcept', qualifier='create_concept', innerForm='inner_concept_form.html', + permission='loops.AssignAsParent', ) actions.register('create_subtype', 'portlet', DialogAction, @@ -155,4 +158,5 @@ actions.register('create_subtype', 'portlet', DialogAction, dialogName='createConcept', qualifier='subtype', innerForm='inner_concept_form.html', + permission='loops.AssignAsParent', ) diff --git a/browser/common.py b/browser/common.py index 5c51a59..897ff96 100644 --- a/browser/common.py +++ b/browser/common.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2012 Helmut Merz helmutm@cy55.de +# Copyright (c) 2013 Helmut Merz helmutm@cy55.de # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -45,7 +45,7 @@ from zope.publisher.browser import applySkin from zope.publisher.interfaces.browser import IBrowserSkinType, IBrowserView from zope import schema from zope.schema.vocabulary import SimpleTerm -from zope.security import canAccess, checkPermission +from zope.security import canAccess from zope.security.interfaces import ForbiddenAttribute, Unauthorized from zope.security.proxy import removeSecurityProxy from zope.traversing.browser import absoluteURL @@ -67,6 +67,7 @@ from loops.i18n.browser import I18NView from loops.interfaces import IResource, IView, INode, ITypeConcept from loops.organize.tracking import access from loops.resource import Resource +from loops.security.common import checkPermission from loops.security.common import canAccessObject, canListObject, canWriteObject from loops.type import ITypeConcept from loops import util @@ -706,6 +707,16 @@ class BaseView(GenericView, I18NView): """ return [] + def getAllowedActions(self, category='object', page=None, target=None): + result = [] + for act in self.getActions(category, page=page, target=target): + if act.permission is not None: + ctx = (target is not None and target.context) or self.context + if not checkPermission(act.permission, ctx): + continue + result.append(act) + return result + @Lazy def showObjectActions(self): return not IUnauthenticatedPrincipal.providedBy(self.request.principal) diff --git a/browser/node.py b/browser/node.py index 5a9a238..2763ada 100644 --- a/browser/node.py +++ b/browser/node.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2012 Helmut Merz helmutm@cy55.de +# Copyright (c) 2013 Helmut Merz helmutm@cy55.de # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -51,17 +51,18 @@ from cybertools.typology.interfaces import IType, ITypeManager from cybertools.util.jeep import Jeep from cybertools.xedit.browser import ExternalEditorView from loops.browser.action import actions, DialogAction +from loops.browser.common import BaseView +from loops.browser.concept import ConceptView from loops.common import adapted, AdapterBase, baseObject from loops.i18n.browser import i18n_macros, LanguageInfo from loops.interfaces import IConcept, IResource, IDocument, IMediaAsset, INode from loops.interfaces import IViewConfiguratorSchema -from loops.resource import MediaAsset -from loops import util -from loops.util import _ -from loops.browser.common import BaseView -from loops.browser.concept import ConceptView from loops.organize.interfaces import IPresence from loops.organize.tracking import access +from loops.resource import MediaAsset +from loops.security.common import canWriteObject +from loops import util +from loops.util import _ from loops.versioning.util import getVersion @@ -149,13 +150,15 @@ class NodeView(BaseView): priority=20) cm.register('portlet_left', 'navigation', title='Navigation', subMacro=node_macros.macros['menu']) - if canWrite(self.context, 'title') or ( + if canWriteObject(self.context) or ( # TODO: is this useful in any case? self.virtualTargetObject is not None and - canWrite(self.virtualTargetObject, 'title')): + canWriteObject(self.virtualTargetObject)): # check if there are any available actions; # store list of actions in macro object (evaluate only once) - actions = [act for act in self.getActions('portlet') if act.condition] + actions = [act for act in self.getAllowedActions('portlet', + target=self.virtualTarget) + if act.condition] if actions: cm.register('portlet_right', 'actions', title=_(u'Actions'), subMacro=node_macros.macros['actions'], @@ -534,7 +537,7 @@ class NodeView(BaseView): return self.makeTargetUrl(self.url, util.getUidForObject(target), target.title) - def getActions(self, category='object', target=None): + def getActions(self, category='object', page=None, target=None): actions = [] #self.registerDojo() self.registerDojoFormAll() @@ -557,9 +560,11 @@ class NodeView(BaseView): description='Open concept map editor in new window', url=cmeUrl, target=target)) if self.checkAction('create_resource', 'portlet', target): - actions.append(DialogAction(self, title='Create Resource...', + actions.append(DialogAction(self, name='create_resource', + title='Create Resource...', description='Create a new resource object.', - page=self, target=target)) + page=self, target=target, + permission='zope.ManageContent')) return actions actions = dict(portlet=getPortletActions) diff --git a/browser/node_macros.pt b/browser/node_macros.pt index 7f65b19..b67f1fb 100644 --- a/browser/node_macros.pt +++ b/browser/node_macros.pt @@ -293,7 +293,8 @@
- +
@@ -322,7 +323,7 @@
Log out
- +
diff --git a/compound/blog/browser.py b/compound/blog/browser.py index 032b99a..dcc3b88 100755 --- a/compound/blog/browser.py +++ b/compound/blog/browser.py @@ -52,6 +52,7 @@ actions.register('createBlogPost', 'portlet', DialogAction, fixedType=True, innerForm='inner_concept_form.html', prerequisites=['registerDojoDateWidget'], # +'registerDojoTextWidget'? + permission='loops.AssignAsParent', ) diff --git a/knowledge/browser.py b/knowledge/browser.py index fd9a8ed..c749f23 100644 --- a/knowledge/browser.py +++ b/knowledge/browser.py @@ -50,6 +50,7 @@ actions.register('createTopic', 'portlet', DialogAction, typeToken='.loops/concepts/topic', fixedType=True, innerForm='inner_concept_form.html', + permission='loops.AssignAsParent', ) actions.register('editTopic', 'portlet', DialogAction, @@ -57,6 +58,7 @@ actions.register('editTopic', 'portlet', DialogAction, description=_(u'Modify topic.'), viewName='edit_concept.html', dialogName='editTopic', + permission='zope.ManageContent', ) actions.register('createQualification', 'portlet', DialogAction, @@ -66,6 +68,7 @@ actions.register('createQualification', 'portlet', DialogAction, dialogName='createQualification', prerequisites=['registerDojoDateWidget', 'registerDojoNumberWidget', 'registerDojoTextarea'], + permission='loops.AssignAsParent', ) diff --git a/organize/browser/event.py b/organize/browser/event.py index ee9bbdc..8f43548 100644 --- a/organize/browser/event.py +++ b/organize/browser/event.py @@ -56,6 +56,7 @@ actions.register('createEvent', 'portlet', DialogAction, typeToken='.loops/concepts/event', fixedType=True, prerequisites=['registerDojoDateWidget'], + permission='loops.AssignAsParent', ) actions.register('editEvent', 'portlet', DialogAction, diff --git a/organize/stateful/configure.zcml b/organize/stateful/configure.zcml index 74c7663..7833208 100644 --- a/organize/stateful/configure.zcml +++ b/organize/stateful/configure.zcml @@ -27,7 +27,7 @@ + name="simple_publishing" /> @@ -41,7 +41,7 @@ + name="task_states" /> @@ -55,7 +55,7 @@ + name="publishable_task" /> @@ -69,7 +69,7 @@ + name="classification_quality" /> diff --git a/security/common.py b/security/common.py index f3b1b07..bb8c874 100644 --- a/security/common.py +++ b/security/common.py @@ -75,7 +75,7 @@ def canListObject(obj, noCheck=False): return canAccess(obj, 'title') def canWriteObject(obj): - return canWrite(obj, 'title') + return canWrite(obj, 'title') or canAssignAsParent(obj) def canEditRestricted(obj): return checkPermission('loops.EditRestricted', obj) diff --git a/security/interfaces.py b/security/interfaces.py index ef4876e..283db83 100644 --- a/security/interfaces.py +++ b/security/interfaces.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2009 Helmut Merz helmutm@cy55.de +# Copyright (c) 2013 Helmut Merz helmutm@cy55.de # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -18,8 +18,6 @@ """ Interfaces for loops security management. - -$Id$ """ from zope.interface import Interface, Attribute @@ -35,6 +33,10 @@ class ISecuritySetter(Interface): context object. """ + def setStateSecurity(): + """ Set the security according to the state(s) of the object. + """ + def setDefaultRolePermissions(): """ Set some default role permission assignments (grants) on the context object. diff --git a/security/setter.py b/security/setter.py index e9248df..135db0e 100644 --- a/security/setter.py +++ b/security/setter.py @@ -123,7 +123,6 @@ class LoopsObjectSecuritySetter(BaseSecuritySetter): rpm = self.rolePermissionManager for p, r, s in rpm.getRolesAndPermissions(): setRolePermission(rpm, p, r, Unset) - self.setStateSecurity() def setStateSecurity(self): statesDefs = (self.globalOptions('organize.stateful.concept', []) + @@ -151,6 +150,7 @@ class LoopsObjectSecuritySetter(BaseSecuritySetter): settings[(p, r)] = s self.setDefaultRolePermissions() self.setRolePermissions(settings) + self.setStateSecurity() def setRolePermissions(self, settings): for (p, r), s in settings.items():