diff --git a/browser/common.py b/browser/common.py index 3c034d5..c7eb71e 100644 --- a/browser/common.py +++ b/browser/common.py @@ -120,13 +120,17 @@ class BaseView(GenericView, I18NView): self.context = removeSecurityProxy(context) try: if not self.checkPermissions(): - raise Unauthorized('%r: title' % (self.context)) + raise Unauthorized(str(self.contextInfo)) except ForbiddenAttribute: # ignore when testing pass def checkPermissions(self): return canAccessObject(self.context) + @Lazy + def contextInfo(self): + return dict(view=self, context=getName(self.context)) + @Lazy def conceptMacros(self): return concept_macros.macros diff --git a/browser/configure.zcml b/browser/configure.zcml index 7f0af0d..e59aefe 100644 --- a/browser/configure.zcml +++ b/browser/configure.zcml @@ -554,7 +554,7 @@ name="create_object.html" for="loops.interfaces.INode" class="loops.browser.form.CreateObjectForm" - permission="zope.ManageContent" + permission="zope.View" /> + permission="zope.View" /> + permission="zope.View" /> diff --git a/browser/form.py b/browser/form.py index 02c0974..1e946ea 100644 --- a/browser/form.py +++ b/browser/form.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2009 Helmut Merz helmutm@cy55.de +# Copyright (c) 2010 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 @@ -35,7 +35,9 @@ from zope.cachedescriptors.property import Lazy from zope.contenttype import guess_content_type from zope.publisher.browser import FileUpload from zope.publisher.interfaces import BadRequest +from zope.security.interfaces import ForbiddenAttribute, Unauthorized from zope.security.proxy import isinstance, removeSecurityProxy +from zope.traversing.api import getName from cybertools.ajax import innerHtml from cybertools.browser.form import FormController @@ -58,6 +60,7 @@ from loops.i18n.browser import I18NView from loops.query import ConceptQuery, IQueryConcept from loops.resource import Resource from loops.schema.field import relation_macros +from loops.security.common import canAccessObject, canListObject, canWriteObject from loops.type import ITypeConcept from loops import util from loops.util import _ @@ -77,12 +80,20 @@ class ObjectForm(NodeView): isPopup = False showAssignments = True - def __init__(self, context, request): - super(ObjectForm, self).__init__(context, request) - # target is the object the view acts upon - this is not necessarily - # the same object as the context (the object the view was created for) - self.target = context - #self.registerDojoForm() + def checkPermissions(self): + obj = self.target + if obj is None: + obj = self.context + return canWriteObject(obj) + + @Lazy + def target(self): + return self.virtualTargetObject or self.context + + @Lazy + def contextInfo(self): + return dict(view=self, context=getName(self.context), + target=getName(self.target)) def closeAction(self, submit=False): if self.isPopup: @@ -196,18 +207,13 @@ class ObjectForm(NodeView): class EditObjectForm(ObjectForm): - @Lazy - def macro(self): - return self.template.macros['edit'] - title = _(u'Edit Resource') form_action = 'edit_resource' dialog_name = 'edit' - def __init__(self, context, request): - super(EditObjectForm, self).__init__(context, request) - #self.url = self.url # keep virtual target URL (???) - self.target = self.virtualTargetObject + @Lazy + def macro(self): + return self.template.macros['edit'] @property def assignments(self): @@ -251,13 +257,13 @@ class EditConceptPage(EditConceptForm): class CreateObjectForm(ObjectForm): - @property - def macro(self): return self.template.macros['create'] - defaultTitle = u'Create Resource, Type = ' form_action = 'create_resource' dialog_name = 'create' + @property + def macro(self): return self.template.macros['create'] + @Lazy def fixedType(self): return self.request.form.get('fixed_type') @@ -445,6 +451,25 @@ class EditObject(FormController, I18NView): prefix = 'form.' conceptPrefix = 'assignments.' + def __init__(self, context, request): + super(EditObject, self).__init__(context, request) + try: + if not self.checkPermissions(): + raise Unauthorized(str(self.contextInfo)) + except ForbiddenAttribute: # ignore when testing + pass + + def checkPermissions(self): + return canWriteObject(self.target) + + @Lazy + def contextInfo(self): + return dict(formcontroller=self, view=self.view, target=getName(self.target)) + + @Lazy + def target(self): + return self.view.virtualTargetObject or self.context + @Lazy def adapted(self): return adapted(self.object, self.languageInfoForUpdate) @@ -476,7 +501,7 @@ class EditObject(FormController, I18NView): def update(self): # create new version if necessary - target = self.view.virtualTargetObject + target = self.target obj = self.checkCreateVersion(target) if obj != target: # make sure new version is used by the view diff --git a/compound/README.txt b/compound/README.txt index b6adcb2..4d0a049 100644 --- a/compound/README.txt +++ b/compound/README.txt @@ -176,12 +176,15 @@ standard checker defined in the test setup. The automatic assignment of the blog post is done in the form controller used for creating the blog post. + >>> home = views['home'] + >>> home.target = myBlog + >>> from loops.compound.blog.browser import CreateBlogPostForm, CreateBlogPost >>> input = {'title': u'John\'s first post', 'text': u'Text of John\'s post', ... 'date': '2008-02-02T15:54:11', ... 'privateComment': u'John\'s private comment', ... 'form.type': '.loops/concepts/blogpost'} - >>> cbpForm = CreateBlogPostForm(myBlog, TestRequest(form=input)) + >>> cbpForm = CreateBlogPostForm(home, TestRequest(form=input)) >>> cbpController = CreateBlogPost(cbpForm, cbpForm.request) >>> cbpController.update() False diff --git a/compound/blog/browser.py b/compound/blog/browser.py index 992acfd..14dc259 100755 --- a/compound/blog/browser.py +++ b/compound/blog/browser.py @@ -36,7 +36,7 @@ from loops.browser.form import CreateConceptForm, EditConceptForm from loops.browser.form import CreateConcept, EditConcept from loops.common import adapted from loops.organize.party import getPersonForUser -from loops.security.common import checkPermission +from loops.security.common import checkPermission, canAccessObject from loops import util from loops.util import _ @@ -175,20 +175,30 @@ class EditBlogPostForm(EditConceptForm): title = _(u'Edit Blog Post') form_action = 'edit_blogpost' + def checkPermissions(self): + return canAccessObject(self.target) + class CreateBlogPostForm(CreateConceptForm): title = _(u'Create Blog Post') form_action = 'create_blogpost' + def checkPermissions(self): + return canAccessObject(self.target) + class EditBlogPost(EditConcept): - pass + def checkPermissions(self): + return canAccessObject(self.target) class CreateBlogPost(CreateConcept): + def checkPermissions(self): + return canAccessObject(self.target) + def collectAutoConcepts(self): #super(CreateBlogPost, self).collectConcepts(fieldName, value) person = getPersonForUser(self.container, self.request) diff --git a/compound/blog/configure.zcml b/compound/blog/configure.zcml index 009827b..6c8673c 100644 --- a/compound/blog/configure.zcml +++ b/compound/blog/configure.zcml @@ -46,14 +46,14 @@ name="create_blogpost.html" for="loops.interfaces.INode" class="loops.compound.blog.browser.CreateBlogPostForm" - permission="zope.ManageContent" + permission="zope.View" /> diff --git a/locales/es/LC_MESSAGES/loops.mo b/locales/es/LC_MESSAGES/loops.mo index cbec38d..60ecd41 100644 Binary files a/locales/es/LC_MESSAGES/loops.mo and b/locales/es/LC_MESSAGES/loops.mo differ diff --git a/locales/es/LC_MESSAGES/loops.po b/locales/es/LC_MESSAGES/loops.po index 89a270f..325d4d1 100644 --- a/locales/es/LC_MESSAGES/loops.po +++ b/locales/es/LC_MESSAGES/loops.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: $Id$\n" "POT-Creation-Date: 2007-05-22 12:00 CET\n" -"PO-Revision-Date: 2010-05-16 12:00 CET\n" +"PO-Revision-Date: 2010-06-11 12:00 CET\n" "Last-Translator: Helmut Merz \n" "Language-Team: loops developers \n" "MIME-Version: 1.0\n" @@ -159,16 +159,16 @@ msgid "Create a new event" msgstr "Einen neuen Termin anlegen" msgid "Create Task..." -msgstr "Aufgabe anlegen..." +msgstr "Crear tarea..." msgid "Create a new task" -msgstr "Eine neue Aufgabe anlegen" +msgstr "Crear una nueva tarea" msgid "Edit Task..." -msgstr "Aufgabe bearbeiten..." +msgstr "Editar tarea..." msgid "Modify task" -msgstr "Aufgabe bearbeiten" +msgstr "Modificar la tarea" msgid "Create Work Item..." msgstr "Aktivität anlegen..." @@ -243,7 +243,7 @@ msgid "Topic" msgstr "Thema" msgid "Task" -msgstr "Aufgabe" +msgstr "Tarea" msgid "Domain" msgstr "Bereich" diff --git a/organize/comment/browser.py b/organize/comment/browser.py index 238401e..1d6acad 100644 --- a/organize/comment/browser.py +++ b/organize/comment/browser.py @@ -35,6 +35,7 @@ from loops.browser.node import NodeView from loops.organize.comment.base import Comment from loops.organize.party import getPersonForUser from loops.organize.tracking.report import TrackDetails +from loops.security.common import canAccessObject from loops.setup import addObject from loops import util from loops.util import _ @@ -91,6 +92,9 @@ class CreateCommentForm(ObjectForm): template = comment_macros + def checkPermissions(self): + return canAccessObject(self.target) + @Lazy def macro(self): return self.template.macros['create_comment'] @@ -98,6 +102,9 @@ class CreateCommentForm(ObjectForm): class CreateComment(EditObject): + def checkPermissions(self): + return canAccessObject(self.target) + @Lazy def personId(self): p = getPersonForUser(self.context, self.request) diff --git a/organize/util.py b/organize/util.py index cc2e4bc..1fb94c7 100644 --- a/organize/util.py +++ b/organize/util.py @@ -58,8 +58,15 @@ def getPrincipalFolder(context=None, authPluginId=None, ignoreErrors=False): return plugin -def getGroupsFolder(context=None, name='gloops'): - return getPrincipalFolder(authPluginId=name, ignoreErrors=True) +def getGroupsFolder(context=None, name='gloops', create=False): + gf = getPrincipalFolder(authPluginId=name, ignoreErrors=True) + if gf is None and create: + pau = component.getUtility(IAuthentication, context=context) + gf = pau[name] = PrincipalFolder() + gf.prefix = name + '.' + pau.authenticatorPlugins = tuple( + list(pau.authenticatorPlugins) + ['name']) + return gf def getGroupId(group): diff --git a/organize/work/browser.py b/organize/work/browser.py index 7330956..ebfb2ec 100644 --- a/organize/work/browser.py +++ b/organize/work/browser.py @@ -47,6 +47,7 @@ from loops.organize.stateful.browser import StateAction from loops.organize.tracking.browser import BaseTrackView from loops.organize.tracking.report import TrackDetails from loops.organize.work.base import WorkItem +from loops.security.common import canAccessObject, canListObject, canWriteObject from loops import util from loops.util import _ @@ -277,10 +278,19 @@ class CreateWorkItemForm(ObjectForm, BaseTrackView): template = work_macros + def checkPermissions(self): + return canAccessObject(self.task or self.target) + @Lazy def macro(self): return self.template.macros['create_workitem'] + @Lazy + def task(self): + uid = self.track.taskId + if uid: + return util.getObjectForUid(uid) + @Lazy def track(self): id = self.request.form.get('id') @@ -352,6 +362,17 @@ class CreateWorkItemForm(ObjectForm, BaseTrackView): class CreateWorkItem(EditObject, BaseTrackView): + def checkPermissions(self): + return canAccessObject(self.task or self.target) + + @Lazy + def task(self): + if self.track is None: + return None + uid = self.track.taskId + if uid: + return util.getObjectForUid(uid) + @Lazy def track(self): id = self.request.form.get('id') @@ -368,7 +389,8 @@ class CreateWorkItem(EditObject, BaseTrackView): @Lazy def object(self): - return self.view.virtualTargetObject + return self.target + #return self.view.virtualTargetObject def processForm(self): form = self.request.form diff --git a/organize/work/configure.zcml b/organize/work/configure.zcml index 17cebc3..8547bd0 100644 --- a/organize/work/configure.zcml +++ b/organize/work/configure.zcml @@ -50,7 +50,7 @@ name="create_workitem.html" for="loops.interfaces.INode" class="loops.organize.work.browser.CreateWorkItemForm" - permission="zope.ManageContent" /> + permission="zope.View" /> + + @@ -42,13 +46,19 @@ + + + + + @@ -56,19 +66,19 @@ + - - - + + + diff --git a/security/browser.py b/security/browser.py index 7846b96..b37209f 100644 --- a/security/browser.py +++ b/security/browser.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2009 Helmut Merz helmutm@cy55.de +# Copyright (c) 2010 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 @@ -22,8 +22,10 @@ Security-related views. $Id$ """ +from zope.app.authentication.groupfolder import GroupInformation from zope.app.pagetemplate import ViewPageTemplateFile from zope.app.security.interfaces import IPermission +from zope.app.security.settings import Allow, Deny, Unset from zope.app.securitypolicy.browser import granting from zope.app.securitypolicy.browser.rolepermissionview import RolePermissionView from zope.app.securitypolicy.interfaces import IPrincipalRoleManager, \ @@ -32,13 +34,17 @@ from zope.app.securitypolicy.interfaces import IPrincipalPermissionManager, \ IPrincipalPermissionMap from zope.app.securitypolicy.zopepolicy import SettingAsBoolean from zope import component +from zope.event import notify from zope.interface import implements from zope.cachedescriptors.property import Lazy +from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent from zope.security.proxy import removeSecurityProxy -from zope.traversing.api import getParent, getParents +from zope.traversing.api import getName, getParent, getParents from loops.common import adapted +from loops.organize.util import getGroupsFolder from loops.security.common import WorkspaceInformation +from loops.security.common import localPermissions, localRoles, setPrincipalRole from loops.security.interfaces import ISecuritySetter @@ -162,7 +168,11 @@ class PermissionView(object): return result def getPermissions(self): - return sorted(name for name, perm in component.getUtilitiesFor(IPermission)) + return sorted(name for name, perm in component.getUtilitiesFor(IPermission) + if name in localPermissions) + + def hideRole(self, role): + return role not in localRoles class ManageWorkspaceView(PermissionView): @@ -176,12 +186,86 @@ class ManageWorkspaceView(PermissionView): wi = context.workspaceInformation = WorkspaceInformation(context) PermissionView.__init__(self, wi, request) + def update(self, testing=None): + if 'SUBMIT_PERMS' in self.request.form: + super(ManageWorkspaceView, self).update(testing) + elif 'save_wsinfo' in self.request.form: + self.saveWSInfo() + + def saveWSInfo(self): + gn = {} + form = self.request.form + gfName = self.context.workspaceGroupsFolderName + gf = getGroupsFolder(self.context, gfName, create=True) + parentRM = IPrincipalRoleManager(self.parent) + wsiRM = IPrincipalRoleManager(self.context) + for pn in form.get('predicate_name', []): + groupName = form.get('group_name_' + pn) + gn[pn] = groupName + if groupName and groupName not in gf: + group = GroupInformation(groupName) + notify(ObjectCreatedEvent(group)) + gf[groupName] = group + notify(ObjectModifiedEvent(group)) + roleParent = bool(form.get('role_parent_' + pn)) + roleWSI = bool(form.get('role_wsi_' + pn)) + roleName = 'loops.' + pn.lstrip('is').title() + gid = '.'.join((gfName, groupName)) + setPrincipalRole(parentRM, roleName, gid, + roleParent and Allow or None) + setPrincipalRole(wsiRM, roleName, gid, + roleWSI and Allow or None) + self.context.workspaceGroupNames = gn + @Lazy def permission_macros(self): return permission_template.macros + @Lazy + def parent(self): + return self.context.getParent() + @Lazy def adapted(self): - return adapted(getParent(self.context)) + return adapted(self.parent) + + def getGroupsInfo(self): + root = self.parent.getLoopsRoot() + conceptManager = root.getConceptManager() + def getDefaultGroupName(predicateName): + rootName = '_'.join([getName(obj) for obj in + reversed(getParents(conceptManager)[:-1])]) + objName = getName(self.parent) + return '.'.join((rootName, objName, predicateName.strip('is'))) + apn = [pn for pn in self.context.allocationPredicateNames + if pn in conceptManager] + gn = self.context.workspaceGroupNames + if not isinstance(gn, dict): # backwards compatibility + gn = {} + result = [dict(predicateName=pn, + predicateTitle=conceptManager[pn].title, + groupName='', groupExists=False, + roleParent=False, roleWSI=False) + for pn in apn] + gfName = self.context.workspaceGroupsFolderName + gf = getGroupsFolder(self.context, gfName) + if gf is None: + return result + parentRMget = IPrincipalRoleManager(self.parent).getPrincipalsForRole + wsiRMget = IPrincipalRoleManager(self.context).getPrincipalsForRole + for item in result: + pn = item['predicateName'] + groupName = item['groupName'] = gn.get(pn, getDefaultGroupName(pn)) + roleName = 'loops.' + pn.lstrip('is').title() + if gf is not None and groupName in gf: + item['groupExists'] = True + gid = '.'.join((gfName, groupName)) + item['roleParent'] = isSet(parentRMget(roleName), gid) + item['roleWSI'] = isSet(wsiRMget(roleName), gid) + return result +def isSet(entry, id): + for name, setting in entry: + if name == id: + return SettingAsBoolean[setting] diff --git a/security/common.py b/security/common.py index 8b1c11c..8601800 100644 --- a/security/common.py +++ b/security/common.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2009 Helmut Merz helmutm@cy55.de +# Copyright (c) 2010 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 @@ -23,7 +23,6 @@ $Id$ """ from persistent import Persistent -from persistent.list import PersistentList from zope import component from zope.annotation.interfaces import IAttributeAnnotatable from zope.app.container.interfaces import IObjectAddedEvent @@ -52,6 +51,15 @@ allRolesExceptOwner = ( 'loops.Member', 'loops.Master',) allRolesExceptOwnerAndMaster = tuple(allRolesExceptOwner[:-1]) minorPrivilegedRoles = ('zope.Anonymous', 'zope.Member',) +localRoles = ('zope.Anonymous', 'zope.Member', 'zope.ContentManager', + 'loops.Staff', 'loops.Member', 'loops.Master', 'loops.Owner') + +localPermissions = ('zope.ManageContent', 'zope.View', 'loops.ManageWorkspaces', + 'loops.ViewRestricted', 'loops.EditRestricted', 'loops.AssignAsParent',) + +allocationPredicateNames = ('ismaster', 'ismember') + +workspaceGroupsFolderName = 'gloops_ws' # checking and querying functions @@ -70,6 +78,9 @@ def canWriteObject(obj): def canEditRestricted(obj): return checkPermission('loops.EditRestricted', obj) +def canAssignAsParent(obj): + return checkPermission('loops.AssignAsParent', obj) + def checkPermission(permission, obj): return baseCheckPermission(permission, obj) @@ -174,11 +185,15 @@ class WorkspaceInformation(Persistent): __name__ = u'workspace_information' propagateRolePermissions = 'workspace' + allocationPredicateNames = allocationPredicateNames + workspaceGroupsFolderName = workspaceGroupsFolderName def __init__(self, parent): self.__parent__ = parent - self.workspaceGroupNames = PersistentList() + self.workspaceGroupNames = {} def getName(self): return self.__name__ + def getParent(self): + return self.__parent__ diff --git a/security/interfaces.py b/security/interfaces.py index 02d329a..5b325f9 100644 --- a/security/interfaces.py +++ b/security/interfaces.py @@ -70,6 +70,6 @@ class IWorkspaceInformation(Interface): security-related stuff for sub-objects. """ - propagateRolePermissions = Attribute('Which role permissions should be ' - 'propagated to children?') + propagateRolePermissions = Attribute('Whose role permissions should be ' + 'propagated to children (workspace_informaton or parent)?') diff --git a/security/manage_permissionform.pt b/security/manage_permissionform.pt index 718a10d..fbea0b9 100644 --- a/security/manage_permissionform.pt +++ b/security/manage_permissionform.pt @@ -37,37 +37,32 @@ tal:attributes="value permId" />
- +
- - - - - - + + + + + + + - + tal:define="ir repeat/setting/index; + roleId python:path('view/roles')[ir].id" + tal:attributes="style python:view.hideRole(roleId) and + 'visibility: collapse' or ''"> + tal:content="roleId" /> - + - - - + - -
RoleUsers/GroupsAcquired SettingSetting
RoleUsers/GroupsAcquired SettingSetting
- Manager - - User xy - - + tal:replace="structure users" /> +
Direct Settings +xyz
+ +
diff --git a/security/manage_workspace.pt b/security/manage_workspace.pt index 8d9da25..2f3158e 100644 --- a/security/manage_workspace.pt +++ b/security/manage_workspace.pt @@ -2,10 +2,51 @@ i18n:domain="zope">
-

Assign Permissions to Roles for Children of this Object

+

Define Workspace Properties

- +

+ + + + + + + + + + + + + + + + + + +
Assign role in
PredicateGroup nameExistsParentWS Info
+ + + + + + + +
+
+ +
+
+

Assign Permissions to Roles for Children of this Object

+