diff --git a/concept.py b/concept.py index 69bdfad..f87b078 100644 --- a/concept.py +++ b/concept.py @@ -165,16 +165,6 @@ class Concept(Contained, Persistent): pi.relations.append(rel) return result - @property - def isWorkspace(self): - ct = self.conceptType - if ct != self.getConceptManager().getTypeConcept(): - from loops.config.base import DummyOptions - options = component.queryAdapter(adapted(self), IOptions) or DummyOptions() - if options('security.isWorkspace'): - return True - return IOptions(adapted(ct))('security.isWorkspace') - # concept relations def getClients(self, relationships=None): diff --git a/integrator/interfaces.py b/integrator/interfaces.py index d535d87..25c6b93 100644 --- a/integrator/interfaces.py +++ b/integrator/interfaces.py @@ -25,7 +25,7 @@ $Id$ from zope.interface import Interface, Attribute from zope import interface, component, schema -from loops.interfaces import IConceptSchema +from loops.interfaces import IConceptSchema, ILoopsAdapter from loops.util import _ @@ -41,7 +41,7 @@ class IExternalSourceInfo(Interface): # external collections -class IExternalCollection(IConceptSchema): +class IExternalCollection(IConceptSchema, ILoopsAdapter): """ A concept representing a collection of resources that may be actively retrieved from an external system using the parameters given. diff --git a/interfaces.py b/interfaces.py index 19c1a71..fb0f059 100644 --- a/interfaces.py +++ b/interfaces.py @@ -104,12 +104,8 @@ class IConcept(IConceptSchema, ILoopsObject, IPotentialTarget): source="loops.conceptTypeSource", required=True) - isWorkspace = Attribute('Marks a concept as responsible for providing ' - 'special permission settings (children grants) ' - 'for its sub-objects (children or resources).') - workspaceInformation = Attribute('An object with additional ' - 'workspace-related information, e.g. children grants.') + 'workspace-related information.') def getType(): """ Return a concept that provides the object's type. diff --git a/organize/interfaces.py b/organize/interfaces.py index 6e35ea0..3b395d5 100644 --- a/organize/interfaces.py +++ b/organize/interfaces.py @@ -34,6 +34,7 @@ from cybertools.organize.interfaces import IPerson as IBasePerson from cybertools.organize.interfaces import ITask from loops.interfaces import IConceptSchema from loops.organize.util import getPrincipalFolder +from loops.interfaces import ILoopsAdapter from loops import util from loops.util import _ @@ -87,7 +88,7 @@ class LoginName(schema.TextLine): mapping=dict(userId=userId))) -class IPerson(IConceptSchema, IBasePerson): +class IPerson(IConceptSchema, IBasePerson, ILoopsAdapter): """ Resembles a human being with a name (first and last name), a birth date, and a set of addresses. This interface only lists fields used in addition to those provided by the @@ -101,7 +102,7 @@ class IPerson(IConceptSchema, IBasePerson): required=False,) -class IAddress(IConceptSchema, IBaseAddress): +class IAddress(IConceptSchema, IBaseAddress, ILoopsAdapter): """ See cybertools.organize. """ @@ -158,7 +159,7 @@ class IMemberRegistrationManager(Interface): # task -class ITask(IConceptSchema, ITask): +class ITask(IConceptSchema, ITask, ILoopsAdapter): pass diff --git a/security/browser.py b/security/browser.py index 5fef50a..af9bb6c 100644 --- a/security/browser.py +++ b/security/browser.py @@ -23,18 +23,150 @@ $Id$ """ from zope.app.pagetemplate import ViewPageTemplateFile +from zope.app.security.interfaces import IPermission +from zope.app.securitypolicy.browser import granting +from zope.app.securitypolicy.browser.rolepermissionview import RolePermissionView +from zope.app.securitypolicy.interfaces import IPrincipalRoleManager, \ + IRolePermissionMap +from zope.app.securitypolicy.interfaces import IPrincipalPermissionManager, \ + IPrincipalPermissionMap +from zope.app.securitypolicy.zopepolicy import SettingAsBoolean from zope import component from zope.interface import implements from zope.cachedescriptors.property import Lazy from zope.security.proxy import removeSecurityProxy +from zope.traversing.api import getParent, getParents +from loops.common import adapted from loops.security.common import WorkspaceInformation -from loops.security.perm import PermissionView +from loops.security.interfaces import ISecuritySetter permission_template = ViewPageTemplateFile('manage_permissionform.pt') +class Granting(granting.Granting): + + def status(self): + value = super(Granting, self).status() + if value: + setter = ISecuritySetter(adapted(self.context), None) + if setter is not None: + setter.propagatePrincipalRoles() + return value + + +class PermissionView(object): + """ View for permission editing. + """ + + def __init__(self, context, request): + self.context = context + # make sure the real view (delegate) updates our context when + # talking about the context's parent: + self.__parent__ = context + self.request = request + self.delegate = RolePermissionView() + self.delegate.context = self + self.delegate.request = request + self.permissionId = request.get('permission_to_manage') or 'zope.View' + + def pagetip(self): + return self.delegate.pagetip() + + def roles(self): + return self.delegate.roles() + + def permissions(self): + return self.delegate.permissions() + + def availableSettings(self, noacquire=False): + return self.delegate.availableSettings(noacquire) + + def permissionRoles(self): + return self.delegate.permissionRoles() + + def permissionForID(self, pid): + return self.delegate.permissionForID(pid) + + @Lazy + def permission(self): + return self.permissionForID(self.permissionId) + + def roleForID(self, rid): + return self.delegate.roleForID(rid) + + def update(self, testing=None): + value = self.delegate.update(testing) + if value: + setter = ISecuritySetter(self.adapted, None) + if setter is not None: + setter.propagateRolePermissions() + return value + + @Lazy + def adapted(self): + return adapted(self.context) + + def getAcquiredPermissionSetting(self, role, perm): + for obj in getParents(self.context): + rpm = IRolePermissionMap(obj, None) + if rpm is not None: + setting = rpm.getSetting(perm, role) + setting = SettingAsBoolean[setting] + if setting is not None: + return setting and '+' or '-' + return '' + + def listUsersForRole(self, rid): + result = '' + direct = IPrincipalRoleManager(self.context).getPrincipalsForRole(rid) + if direct: + result = '' + self.renderEntry(direct) + '' + acquired = [] + for obj in getParents(self.context): + prm = IPrincipalRoleManager(obj, None) + if prm is not None: + entry = prm.getPrincipalsForRole(rid) + if entry: + acquired.append(self.renderEntry(entry)) + if acquired: + if result: + result += '
' + result += '
'.join(acquired) + return result + + def renderEntry(self, entry): + result = [] + for e in entry: + value = SettingAsBoolean[e[1]] + value = (value is False and '-') or (value and '+') or '' + result.append(value + e[0]) + return ', '.join(result) + + def getPrincipalPermissions(self): + result = '' + ppm = IPrincipalPermissionMap(self.context) + direct = ppm.getPrincipalsForPermission(self.permissionId) + if direct: + result = '' + self.renderEntry(direct) + '' + acquired = [] + for obj in getParents(self.context): + ppm = IPrincipalPermissionMap(obj, None) + if ppm is not None: + entry = ppm.getPrincipalsForPermission(self.permissionId) + if entry: + acquired.append(self.renderEntry(entry)) + if acquired: + if result: + result += '
' + result += '
'.join(acquired) + return result + + def getPermissions(self): + return sorted(name for name, perm in component.getUtilitiesFor(IPermission)) + + class ManageWorkspaceView(PermissionView): """ View for managing workspace information. """ @@ -49,3 +181,9 @@ class ManageWorkspaceView(PermissionView): @Lazy def permission_macros(self): return permission_template.macros + + @Lazy + def adapted(self): + return adapted(getParent(self.context)) + + diff --git a/security/common.py b/security/common.py index 634878e..a155d50 100644 --- a/security/common.py +++ b/security/common.py @@ -23,9 +23,11 @@ $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 +from zope.app.security.settings import Allow, Deny, Unset from zope.app.securitypolicy.interfaces import IPrincipalRoleManager from zope.app.securitypolicy.interfaces import IRolePermissionManager from zope.cachedescriptors.property import Lazy @@ -39,7 +41,7 @@ from zope.traversing.interfaces import IPhysicallyLocatable from loops.common import adapted from loops.interfaces import ILoopsObject, IConcept from loops.interfaces import IAssignmentEvent, IDeassignmentEvent -from loops.security.interfaces import ISecuritySetter +from loops.security.interfaces import ISecuritySetter, IWorkspaceInformation allRolesExceptOwner = ( @@ -81,7 +83,20 @@ def getCurrentPrincipal(): return None -# functions for setting security properties +# functions for checking and setting security properties + +def overrides(s1, s2): + settings = [Allow, Deny, Unset] + return settings.index(s1) < settings.index(s2) + +def setRolePermission(rpm, p, r, setting): + if setting == Allow: + rpm.grantPermissionToRole(p, r) + elif setting == Deny: + rpm.denyPermissionToRole(p, r) + else: + rpm.unsetPermissionFromRole(p, r) + def assignOwner(obj, principalId): prm = IPrincipalRoleManager(obj) @@ -149,12 +164,16 @@ class WorkspaceInformation(Persistent): children and resources of the context (=parent) object. """ - implements(IPhysicallyLocatable) + implements(IPhysicallyLocatable, IWorkspaceInformation) __name__ = u'workspace_information' + propagatePrincipalRoles = False + propagateRolePermissions = 'workspace' + def __init__(self, parent): self.__parent__ = parent + self.workspaceGroups = PersistentList() def getName(self): return self.__name__ diff --git a/security/configure.zcml b/security/configure.zcml index 62d7709..bd6a6ab 100644 --- a/security/configure.zcml +++ b/security/configure.zcml @@ -9,6 +9,9 @@ + + + + + @@ -33,9 +39,17 @@ name="permissions.html" permission="zope.Security" template="manage_permissionform.pt" - class="loops.security.perm.PermissionView" + class="loops.security.browser.PermissionView" menu="zmi_actions" title="Edit Permissions" /> + + + +
+

Granting Roles and Permissions to Principals

+

+             +           + +

+ +
...
+ +
+ +

Grants for the selected principal

+ + + + + + + +
+ + + + + + + + + + + roles widget + + + + + +
Roles Allow Unset Deny 
+
+ +
+
^ top
+
+ + + + + + + + + + + permission widget + + + + + +
Permissions Allow Unset Deny 
+
+ +
+
^ top
+
+ + +
+
+
+ + diff --git a/security/interfaces.py b/security/interfaces.py index c9f6d0f..99a1392 100644 --- a/security/interfaces.py +++ b/security/interfaces.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2008 Helmut Merz helmutm@cy55.de +# Copyright (c) 2009 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 @@ -40,15 +40,46 @@ class ISecuritySetter(Interface): (e.g. the user that created the object). """ - def setAcquiredRolePermissions(relation, revert=False): + def acquireRolePermissions(): + """ Check (immediate) parents's settings and set role permission + assignments on the context object accordingly. + """ + + def setAcquiredRolePermissions(relation, revert=False, updated=None): """ Grant role permissions on children/resources for the relation given. If the ``revert`` argument is true unset the corresponding settings. + Do not update objects in the ``updated`` collection if present. """ - def setAcquiredPrincipalRoles(relation, revert=False): + def setAcquiredPrincipalRoles(relation, revert=False, updated=None): """ Assign roles on children/resources for the relation given. If the ``revert`` argument is true unset the corresponding settings. + Do not update objects in the ``updated`` collection if present. """ + def propagateRolePermissions(updated=None): + """ Update role permissions on all sub-objects according to the + current setting of the context object. + + Ignore objects in the ``updated`` collection if present. + """ + + def propagatePrincipalRoles(updated=None): + """ Update roles on all sub-objects according to the + current setting of the context object. + + Ignore objects in the ``updated`` collection if present. + """ + + +class IWorkspaceInformation(Interface): + """ Additional information belonging to a concept that controls + security-related stuff for sub-objects. + """ + + propagatePrincipalRoles = Attribute('Should acquired principal roles be ' + 'propagated to children?') + propagateRolePermissions = Attribute('Which role permissions should be ' + 'propagated to children?') diff --git a/security/manage_permissionform.pt b/security/manage_permissionform.pt index 7318d26..718a10d 100644 --- a/security/manage_permissionform.pt +++ b/security/manage_permissionform.pt @@ -3,10 +3,9 @@
+

Assign Permissions to Roles

+ tal:content="status" i18n:translate="" />

Assign Permissions to Roles for Children of this Object


+ tal:content="status" i18n:translate=""/> diff --git a/security/perm.py b/security/perm.py deleted file mode 100644 index 49c8f96..0000000 --- a/security/perm.py +++ /dev/null @@ -1,139 +0,0 @@ -# -# Copyright (c) 2008 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# - -""" -Authentication view. - -$Id$ -""" - -from zope import component -from zope.interface import implements -from zope.cachedescriptors.property import Lazy -from zope.app.security.interfaces import IPermission -from zope.app.securitypolicy.browser.granting import Granting -from zope.app.securitypolicy.browser.rolepermissionview import RolePermissionView -from zope.app.securitypolicy.interfaces import IPrincipalRoleManager, IRolePermissionMap -from zope.app.securitypolicy.interfaces import IPrincipalPermissionManager, \ - IPrincipalPermissionMap -from zope.app.securitypolicy.zopepolicy import SettingAsBoolean -from zope.security.proxy import removeSecurityProxy -from zope.traversing.api import getParents - - -class PermissionView(object): - """ View for permission editing. - """ - - def __init__(self, context, request): - self.context = context - # make sure the real view (delegate) updates our context when - # talking about the context's parent: - self.__parent__ = context - self.request = request - self.delegate = RolePermissionView() - self.delegate.context = self - self.delegate.request = request - self.permissionId = request.get('permission_to_manage') or 'zope.View' - - def pagetip(self): - return self.delegate.pagetip() - - def roles(self): - return self.delegate.roles() - - def permissions(self): - return self.delegate.permissions() - - def availableSettings(self, noacquire=False): - return self.delegate.availableSettings(noacquire) - - def permissionRoles(self): - return self.delegate.permissionRoles() - - def permissionForID(self, pid): - return self.delegate.permissionForID(pid) - - @Lazy - def permission(self): - return self.permissionForID(self.permissionId) - - def roleForID(self, rid): - return self.delegate.roleForID(rid) - - def update(self, testing=None): - return self.delegate.update(testing) - - def getAcquiredPermissionSetting(self, role, perm): - for obj in getParents(self.context): - rpm = IRolePermissionMap(obj, None) - if rpm is not None: - setting = rpm.getSetting(perm, role) - setting = SettingAsBoolean[setting] - if setting is not None: - return setting and '+' or '-' - return '' - - def listUsersForRole(self, rid): - result = '' - direct = IPrincipalRoleManager(self.context).getPrincipalsForRole(rid) - if direct: - result = '' + self.renderEntry(direct) + '' - acquired = [] - for obj in getParents(self.context): - prm = IPrincipalRoleManager(obj, None) - if prm is not None: - entry = prm.getPrincipalsForRole(rid) - if entry: - acquired.append(self.renderEntry(entry)) - if acquired: - if result: - result += '
' - result += '
'.join(acquired) - return result - - def renderEntry(self, entry): - result = [] - for e in entry: - value = SettingAsBoolean[e[1]] - value = (value is False and '-') or (value and '+') or '' - result.append(value + e[0]) - return ', '.join(result) - - def getPrincipalPermissions(self): - result = '' - ppm = IPrincipalPermissionMap(self.context) - direct = ppm.getPrincipalsForPermission(self.permissionId) - if direct: - result = '' + self.renderEntry(direct) + '' - acquired = [] - for obj in getParents(self.context): - ppm = IPrincipalPermissionMap(obj, None) - if ppm is not None: - entry = ppm.getPrincipalsForPermission(self.permissionId) - if entry: - acquired.append(self.renderEntry(entry)) - if acquired: - if result: - result += '
' - result += '
'.join(acquired) - return result - - def getPermissions(self): - return sorted(name for name, perm in component.getUtilitiesFor(IPermission)) - diff --git a/security/setter.py b/security/setter.py index dd07df3..d785fde 100644 --- a/security/setter.py +++ b/security/setter.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2008 Helmut Merz helmutm@cy55.de +# Copyright (c) 2009 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,11 +23,18 @@ methods for setting role permissions and other security-related stuff. $Id$ """ +from zope.app.security.settings import Allow, Deny, Unset +from zope.app.securitypolicy.interfaces import IRolePermissionMap +from zope.app.securitypolicy.interfaces import IRolePermissionManager from zope import component from zope.component import adapts from zope.cachedescriptors.property import Lazy from zope.interface import implements, Interface +from zope.security.proxy import isinstance +from loops.common import adapted, AdapterBase +from loops.security.common import overrides, setRolePermission +from loops.interfaces import IConceptSchema, IBaseResourceSchema, ILoopsAdapter from loops.security.interfaces import ISecuritySetter @@ -45,8 +52,95 @@ class BaseSecuritySetter(object): def setDefaultPrincipalRoles(self): pass - def setAcquiredRolePermissions(self, relation, revert=False): + def acquireRolePermissions(self): pass - def setAcquiredPrincipalRoles(self, relation, revert=False): + def setAcquiredRolePermissions(self, relation, revert=False, updated=None): pass + + def setAcquiredPrincipalRoles(self, relation, revert=False, updated=None): + pass + + def propagateRolePermissions(self, updated=None): + pass + + def propagatePrincipalRoles(self, updated=None): + pass + + +class LoopsObjectSecuritySetter(BaseSecuritySetter): + + @Lazy + def baseObject(self): + obj = self.context + if isinstance(obj, AdapterBase): + obj = obj.context + return obj + + def acquireRolePermissions(self): + obj = self.baseObject + if isinstance(obj, AdapterBase): + obj = obj.context + settings = {} + for p in self.parents: + if p == obj: + continue + secProvider = p + wi = p.workspaceInformation + if wi: + if wi.propagateRolePermissions == 'none': + continue + if wi.propagateRolePermissions == 'workspace': + secProvider = wi + rpm = IRolePermissionMap(secProvider) + for p, r, s in rpm.getRolesAndPermissions(): + current = settings.get((p, r)) + if current is None or overrides(s, current): + settings[(p, r)] = s + rpm = IRolePermissionManager(obj) + for p, r, s in rpm.getRolesAndPermissions(): + # clear previous settings + setRolePermission(rpm, p, r, Unset) + for (p, r), s in settings.items(): + setRolePermission(rpm, p, r, s) + + +class ConceptSecuritySetter(LoopsObjectSecuritySetter): + + adapts(IConceptSchema) + + def setAcquiredRolePermissions(self, relation, revert=False, updated=None): + if updated and relation.second in updated: + return + setter = ISecuritySetter(adapted(relation.second), None) + if setter is not None: + setter.acquireRolePermissions() + setter.propagateRolePermissions(updated) + + def setAcquiredPrincipalRoles(self, relation, revert=False, updated=None): + pass + + def propagateRolePermissions(self, updated=None): + if updated is None: + updated = set() + obj = self.baseObject + updated.add(obj) + for r in obj.getChildRelations(): + self.setAcquiredRolePermissions(r, updated=updated) + + def propagatePrincipalRoles(self, updated=None): + pass + + @Lazy + def parents(self): + return self.baseObject.getParents() + + +class ResourceSecuritySetter(LoopsObjectSecuritySetter): + + adapts(IBaseResourceSchema) + + @Lazy + def parents(self): + return self.baseObject.getConcepts() + diff --git a/tests/setup.py b/tests/setup.py index ec9acc1..f24be15 100644 --- a/tests/setup.py +++ b/tests/setup.py @@ -26,6 +26,7 @@ from zope.dublincore.interfaces import IZopeDublinCore from zope.interface import Interface, implements from zope.publisher.interfaces.browser import IBrowserRequest, IBrowserView from zope.security.checker import Checker, defineChecker +from zope.security.management import setSecurityPolicy from cybertools.browser.controller import Controller from cybertools.catalog.keyword import KeywordIndex @@ -71,9 +72,9 @@ from loops.schema.factory import ResourceSchemaFactory, FileSchemaFactory, \ NoteSchemaFactory from loops.schema.field import RelationSetFieldInstance from loops.security.common import grantAcquiredSecurity, revokeAcquiredSecurity -from zope.security.management import setSecurityPolicy from loops.security.policy import LoopsSecurityPolicy from loops.security.setter import BaseSecuritySetter +from loops.security.setter import ConceptSecuritySetter, ResourceSecuritySetter from loops.setup import SetupManager, addObject from loops.type import LoopsType, ConceptType, ResourceType, TypeConcept from loops.view import Node, NodeAdapter @@ -135,6 +136,8 @@ class TestSite(object): component.provideHandler(grantAcquiredSecurity) component.provideHandler(revokeAcquiredSecurity) component.provideAdapter(BaseSecuritySetter) + component.provideAdapter(ConceptSecuritySetter) + component.provideAdapter(ResourceSecuritySetter) component.provideAdapter(LoopsOptions) component.provideAdapter(QueryOptions) component.provideAdapter(TypeOptions)