[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/securitypolicy/ - Fixed a bug (actually a missfeature). It wasn't possible

Jim Fulton jim at zope.com
Wed Jul 21 18:52:23 EDT 2004


Log message for revision 26668:
  - Fixed a bug (actually a missfeature). It wasn't possible
    for local settings to override global (zcml) settings.
  
  - Changed the way role denies work.  A role deny simply prevents
    a principal from having a role.  A principal may still
    have access through other roles or through principal grants.
    Role grants or denies never override principal grants or denies
    *even* if the role-based grants or denies are more local.
  
  - Implemented a caching scheme that provides huge performance
    benefits when the authenticated principal is defined in a local auth
    service, rather than a global one (zcml).
  
  - Refactored the way security maps were implemented and used.  
    Especially changed the way annotations were handled.
  
  


Changed:
  U   Zope3/trunk/src/zope/app/securitypolicy/browser/configure.zcml
  U   Zope3/trunk/src/zope/app/securitypolicy/browser/rolepermissionview.py
  U   Zope3/trunk/src/zope/app/securitypolicy/configure.zcml
  A   Zope3/trunk/src/zope/app/securitypolicy/grantinfo.py
  U   Zope3/trunk/src/zope/app/securitypolicy/interfaces.py
  D   Zope3/trunk/src/zope/app/securitypolicy/permissionroles.py
  U   Zope3/trunk/src/zope/app/securitypolicy/principalpermission.py
  U   Zope3/trunk/src/zope/app/securitypolicy/principalrole.py
  U   Zope3/trunk/src/zope/app/securitypolicy/rolepermission.py
  U   Zope3/trunk/src/zope/app/securitypolicy/securitymap.py
  U   Zope3/trunk/src/zope/app/securitypolicy/tests/test_securitymap.py
  U   Zope3/trunk/src/zope/app/securitypolicy/tests/test_zopepolicy.py
  U   Zope3/trunk/src/zope/app/securitypolicy/zopepolicy.py
  A   Zope3/trunk/src/zope/app/securitypolicy/zopepolicy.txt


-=-
Modified: Zope3/trunk/src/zope/app/securitypolicy/browser/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/securitypolicy/browser/configure.zcml	2004-07-21 22:51:35 UTC (rev 26667)
+++ Zope3/trunk/src/zope/app/securitypolicy/browser/configure.zcml	2004-07-21 22:52:23 UTC (rev 26668)
@@ -76,6 +76,17 @@
     template="grant.pt" 
     menu="zmi_actions" title="Grant" />
 
+  <zope:class class=".rolepermissionview.PermissionRoles">
+    <zope:require permission="zope.Security"
+                  attributes="roles rolesInfo id title description" />
+  </zope:class>
+
+  <zope:class class=".rolepermissionview.RolePermissions">
+    <zope:require 
+        permission="zope.Security"
+        attributes="permissions permissionsInfo id title description" />
+  </zope:class>
+
 <!-- Principal Roles -->
 
   <page

Modified: Zope3/trunk/src/zope/app/securitypolicy/browser/rolepermissionview.py
===================================================================
--- Zope3/trunk/src/zope/app/securitypolicy/browser/rolepermissionview.py	2004-07-21 22:51:35 UTC (rev 26667)
+++ Zope3/trunk/src/zope/app/securitypolicy/browser/rolepermissionview.py	2004-07-21 22:52:23 UTC (rev 26668)
@@ -17,14 +17,13 @@
 """
 from datetime import datetime
 
+from zope.interface import implements
+
 from zope.app import zapi
 from zope.app.i18n import ZopeMessageIDFactory as _
 from zope.app.security.settings import Unset, Allow, Deny
 from zope.app.security.interfaces import IPermission
-
 from zope.app.securitypolicy.interfaces import IRole, IRolePermissionManager
-from zope.app.securitypolicy.permissionroles import PermissionRoles
-from zope.app.securitypolicy.rolepermission import RolePermissions
 
 class RolePermissionView(object):
 
@@ -141,3 +140,76 @@
 
         return status
 
+
+class PermissionRoles(object):
+
+    implements(IPermission)
+
+    def __init__(self, permission, context, roles):
+        self._permission = permission
+        self._context    = context
+        self._roles      = roles
+
+    def _getId(self):
+        return self._permission.id
+
+    id = property(_getId)
+
+    def _getTitle(self):
+        return self._permission.title
+
+    title = property(_getTitle)
+
+    def _getDescription(self):
+        return self._permission.description
+
+    description = property(_getDescription)
+
+    def roleSettings(self):
+        """
+        Returns the list of setting names of each role for this permission.
+        """
+        prm = IRolePermissionManager(self._context)
+        proles = prm.getRolesForPermission(self._permission.id)
+        settings = {}
+        for role, setting in proles:
+            settings[role] = setting.getName()
+        nosetting = Unset.getName()
+        return [settings.get(role.id, nosetting) for role in self._roles]
+
+class RolePermissions(object):
+
+    implements(IRole)
+
+    def __init__(self, role, context, permissions):
+        self._role = role
+        self._context = context
+        self._permissions = permissions
+
+    def _getId(self):
+        return self._role.id
+
+    id = property(_getId)
+
+    def _getTitle(self):
+        return self._role.title
+
+    title = property(_getTitle)
+
+    def _getDescription(self):
+        return self._role.description
+
+    description = property(_getDescription)
+
+    def permissionsInfo(self):
+        prm = IRolePermissionManager(self._context)
+        rperms = prm.getPermissionsForRole(self._role.id)
+        settings = {}
+        for permission, setting in rperms:
+            settings[permission] = setting.getName()
+        nosetting = Unset.getName()
+        return [{'id': permission.id,
+                 'title': permission.title,
+                 'setting': settings.get(permission.id, nosetting)}
+                for permission in self._permissions]
+

Modified: Zope3/trunk/src/zope/app/securitypolicy/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/securitypolicy/configure.zcml	2004-07-21 22:51:35 UTC (rev 26667)
+++ Zope3/trunk/src/zope/app/securitypolicy/configure.zcml	2004-07-21 22:52:23 UTC (rev 26668)
@@ -3,58 +3,56 @@
     i18n_domain="zope"
     >
 
-  <class class=".permissionroles.PermissionRoles">
-    <require permission="zope.Security"
-             attributes="roles rolesInfo id title description" />
-  </class>
-
-  <class class=".rolepermission.RolePermissions">
-    <require permission="zope.Security"
-             attributes="permissions permissionsInfo id title description" />
-  </class>
-
   <adapter factory=".rolepermission.AnnotationRolePermissionManager"
            provides=".interfaces.IRolePermissionManager"
            for="zope.app.annotation.interfaces.IAnnotatable"
-           trusted="true" />
+           trusted="true" 
+           />
 
   <class class=".rolepermission.AnnotationRolePermissionManager">
     <require 
         permission="zope.Security"
         attributes="grantPermissionToRole denyPermissionToRole
-                    unsetPermissionFromRole" />
-    <allow 
-        interface=".interfaces.IRolePermissionMap" />
+                    unsetPermissionFromRole" 
+        />
+    <allow interface=".interfaces.IRolePermissionMap" />
   </class>       
 
   <adapter factory=".principalrole.AnnotationPrincipalRoleManager"
            provides=".interfaces.IPrincipalRoleManager"
            for="zope.app.annotation.interfaces.IAnnotatable" 
-           trusted="true" />
+           trusted="true" 
+           />
 
   <class class=".principalrole.AnnotationPrincipalRoleManager">
     <require 
         permission="zope.Security"
         attributes="assignRoleToPrincipal removeRoleFromPrincipal
-                    unsetRoleForPrincipal" />
-    <allow 
-        interface=".interfaces.IPrincipalRoleMap" />
+                    unsetRoleForPrincipal" 
+        />
+    <allow interface=".interfaces.IPrincipalRoleMap" />
   </class>
 
   <adapter factory=".principalpermission.AnnotationPrincipalPermissionManager"
            provides=".interfaces.IPrincipalPermissionManager"
            for="zope.app.annotation.interfaces.IAnnotatable" 
-           trusted="true"/>
+           trusted="true"
+           />
 
   <class class=".principalpermission.AnnotationPrincipalPermissionManager">
     <require 
         permission="zope.Security"
         attributes="grantPermissionToRole denyPermissionToRole
-                    unsetPermissionFromRole" />
-    <allow 
-        interface=".interfaces.IPrincipalPermissionMap" />
+                    unsetPermissionFromRole" 
+        />
+    <allow interface=".interfaces.IPrincipalPermissionMap" />
   </class>
 
+  <adapter factory=".grantinfo.AnnotationGrantInfo"
+           provides=".interfaces.IGrantInfo"
+           for="zope.app.annotation.interfaces.IAnnotatable" 
+           />
+
   <!-- protect Roles and Permissions -->
   <content class=".role.Role">
     <allow interface=".interfaces.IRole" />

Added: Zope3/trunk/src/zope/app/securitypolicy/grantinfo.py
===================================================================
--- Zope3/trunk/src/zope/app/securitypolicy/grantinfo.py	2004-07-21 22:51:35 UTC (rev 26667)
+++ Zope3/trunk/src/zope/app/securitypolicy/grantinfo.py	2004-07-21 22:52:23 UTC (rev 26668)
@@ -0,0 +1,78 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Grant info
+
+$Id$
+"""
+
+from zope.app.securitypolicy.interfaces import IGrantInfo
+
+from zope.app.annotation.interfaces import IAnnotations
+
+from zope.app.securitypolicy.principalpermission \
+     import AnnotationPrincipalPermissionManager
+prinperkey = AnnotationPrincipalPermissionManager.key
+del AnnotationPrincipalPermissionManager
+
+from zope.app.securitypolicy.principalrole \
+     import AnnotationPrincipalRoleManager
+prinrolekey = AnnotationPrincipalRoleManager.key
+del AnnotationPrincipalRoleManager
+
+from zope.app.securitypolicy.rolepermission \
+     import AnnotationRolePermissionManager
+rolepermkey = AnnotationRolePermissionManager.key
+del AnnotationRolePermissionManager
+
+
+class AnnotationGrantInfo(object):
+
+    prinper = prinrole = permrole = {}
+
+    def __init__(self, context):
+        self._context = context
+        annotations = IAnnotations(context, None)
+        if annotations is not None:
+
+            prinper = annotations.get(prinperkey)
+            if prinper is not None:
+                self.prinper = prinper._bycol # by principals
+
+            prinrole = annotations.get(prinrolekey)
+            if prinrole is not None:
+                self.prinrole = prinrole._bycol # by principals
+
+            roleper = annotations.get(rolepermkey)
+            if roleper is not None:
+                self.permrole = roleper._byrow # by permission
+            
+    def __nonzero__(self):
+        return bool(self.prinper or self.prinrole or self.permrole)
+
+    def principalPermissionGrant(self, principal, permission):
+        prinper = self.prinper.get(principal)
+        if prinper:
+            return prinper.get(permission)
+
+    def getRolesForPermission(self, permission):
+        permrole = self.permrole.get(permission)
+        if permrole:
+            return permrole.items()
+        return ()
+
+    def getRolesForPrincipal(self, principal):
+        prinrole = self.prinrole.get(principal)
+        if prinrole:
+            return prinrole.items()
+        return ()


Property changes on: Zope3/trunk/src/zope/app/securitypolicy/grantinfo.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Modified: Zope3/trunk/src/zope/app/securitypolicy/interfaces.py
===================================================================
--- Zope3/trunk/src/zope/app/securitypolicy/interfaces.py	2004-07-21 22:51:35 UTC (rev 26667)
+++ Zope3/trunk/src/zope/app/securitypolicy/interfaces.py	2004-07-21 22:52:23 UTC (rev 26668)
@@ -18,36 +18,6 @@
 from zope.interface import Interface
 from zope.schema import TextLine, Text
 
-class ISecurityMap(Interface):
-    """Security map to hold matrix-like relationships."""
-
-    def addCell(rowentry, colentry, value):
-        """Add a cell"""
-
-    def delCell(rowentry, colentry):
-        """Delete a cell"""
-
-    def queryCell(rowentry, colentry, default=None):
-        """Return the value of a cell by row, entry
-
-        Return `default`, if the cell is not found.
-        """
-
-    def getCell(rowentry, colentry):
-        """Return the value of a cell by row, entry
-
-        Raise an error, if the cell is not found.
-        """
-        
-    def getRow(rowentry):
-        """Return a list of (colentry, value) tuples from a row"""
-
-    def getCol(colentry):
-        """Return a list of (rowentry, value) tuples from a col"""
-
-    def getAllCells():
-        """Return a list of (rowentry, colentry, value)"""
-
 class IRole(Interface):
     """A role object."""
 
@@ -228,3 +198,27 @@
         """Remove the permission (either denied or allowed) from the
         principal.
         """
+
+class IGrantInfo(Interface):
+    """Get grant info needed for checking access
+    """
+
+    def principalPermissionGrant(principal, permission):
+        """Return the principal-permission grant if any
+
+        The return value is one of Allow, Deny, or Unset
+        """
+
+    def getRolesForPermission(permission):
+        """Return the role grants for the permission
+
+        The role grants are an iterable of role, setting tuples, where
+        setting is either Allow or Deny.
+        """
+
+    def getRolesForPrincipal(principal):
+        """Return the role grants for the principal
+
+        The role grants are an iterable of role, setting tuples, where
+        setting is either Allow or Deny.
+        """

Deleted: Zope3/trunk/src/zope/app/securitypolicy/permissionroles.py
===================================================================
--- Zope3/trunk/src/zope/app/securitypolicy/permissionroles.py	2004-07-21 22:51:35 UTC (rev 26667)
+++ Zope3/trunk/src/zope/app/securitypolicy/permissionroles.py	2004-07-21 22:52:23 UTC (rev 26668)
@@ -1,58 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Permission to Roles Map implementation
-
-$Id$
-"""
-from zope.interface import implements
-
-from zope.app.security.interfaces import IPermission
-from zope.app.security.settings import Unset
-from zope.app.securitypolicy.interfaces import IRolePermissionManager
-
-class PermissionRoles(object):
-
-    implements(IPermission)
-
-    def __init__(self, permission, context, roles):
-        self._permission = permission
-        self._context    = context
-        self._roles      = roles
-
-    def _getId(self):
-        return self._permission.id
-
-    id = property(_getId)
-
-    def _getTitle(self):
-        return self._permission.title
-
-    title = property(_getTitle)
-
-    def _getDescription(self):
-        return self._permission.description
-
-    description = property(_getDescription)
-
-    def roleSettings(self):
-        """
-        Returns the list of setting names of each role for this permission.
-        """
-        prm = IRolePermissionManager(self._context)
-        proles = prm.getRolesForPermission(self._permission.id)
-        settings = {}
-        for role, setting in proles:
-            settings[role] = setting.getName()
-        nosetting = Unset.getName()
-        return [settings.get(role.id, nosetting) for role in self._roles]

Modified: Zope3/trunk/src/zope/app/securitypolicy/principalpermission.py
===================================================================
--- Zope3/trunk/src/zope/app/securitypolicy/principalpermission.py	2004-07-21 22:51:35 UTC (rev 26667)
+++ Zope3/trunk/src/zope/app/securitypolicy/principalpermission.py	2004-07-21 22:52:23 UTC (rev 26668)
@@ -17,7 +17,6 @@
 """
 
 from zope.interface import implements
-from zope.app.annotation.interfaces import IAnnotations
 from zope.app.securitypolicy.interfaces import IPrincipalPermissionManager
 
 from zope.app.security.settings import Allow, Deny, Unset
@@ -25,87 +24,37 @@
 from zope.app.security.permission import checkPermission, allPermissions
 
 from zope.app.securitypolicy.securitymap import SecurityMap
+from zope.app.securitypolicy.securitymap import AnnotationSecurityMap
 
-# This is misspelled, but that's OK. It just has to be unique.
-# we'll keep it as is, to prevent breaking on data:
-annotation_key = 'zopel.app.security.AnnotationPrincipalPermissionManager'
 
-class AnnotationPrincipalPermissionManager(object):
+class AnnotationPrincipalPermissionManager(AnnotationSecurityMap):
     """Mappings between principals and permissions."""
 
+    # the annotation key is a holdover from this module's old
+    # location, but cannot change without breaking existing databases
+    # It is also is misspelled, but that's OK. It just has to be unique.
+    # we'll keep it as is, to prevent breaking old data:
+    key = 'zopel.app.security.AnnotationPrincipalPermissionManager'
+
     implements(IPrincipalPermissionManager)
 
-    def __init__(self, context):
-        self._context = context
-
     def grantPermissionToPrincipal(self, permission_id, principal_id):
-        ''' See the interface IPrincipalPermissionManager '''
-        pp = self._getPrincipalPermissions(create=1)
-        pp.addCell(permission_id, principal_id, Allow)
-        self._context._p_changed = 1
+        AnnotationSecurityMap.addCell(self, permission_id, principal_id, Allow)
 
     def denyPermissionToPrincipal(self, permission_id, principal_id):
-        ''' See the interface IPrincipalPermissionManager '''
-        pp = self._getPrincipalPermissions(create=1)
-        pp.addCell(permission_id, principal_id, Deny)
-        self._context._p_changed = 1
+        AnnotationSecurityMap.addCell(self, permission_id, principal_id, Deny)
 
-    def unsetPermissionForPrincipal(self, permission_id, principal_id):
-        ''' See the interface IPrincipalPermissionManager '''
-        pp = self._getPrincipalPermissions()
-        # Only unset if there is a security map, otherwise, we're done
-        if pp:
-            pp.delCell(permission_id, principal_id)
-            self._context._p_changed = 1
+    unsetPermissionForPrincipal = AnnotationSecurityMap.delCell
+    getPrincipalsForPermission = AnnotationSecurityMap.getRow
+    getPermissionsForPrincipal = AnnotationSecurityMap.getCol
 
-    def getPrincipalsForPermission(self, permission_id):
-        ''' See the interface IPrincipalPermissionManager '''
-        pp = self._getPrincipalPermissions()
-        if pp:
-            return pp.getRow(permission_id)
-        return []
+    def getSetting(self, permission_id, principal_id, default=Unset):
+        return AnnotationSecurityMap.queryCell(
+            self, permission_id, principal_id, default)
+       
+    getPrincipalsAndPermissions = AnnotationSecurityMap.getAllCells
 
-    def getPermissionsForPrincipal(self, principal_id):
-        ''' See the interface IPrincipalPermissionManager '''
-        pp = self._getPrincipalPermissions()
-        if pp:
-            return pp.getCol(principal_id)
-        return []
 
-    def getSetting(self, permission_id, principal_id):
-        ''' See the interface IPrincipalPermissionManager '''
-        pp = self._getPrincipalPermissions()
-        if pp:
-            return pp.queryCell(permission_id, principal_id, default=Unset)
-        return []
-
-    def getPrincipalsAndPermissions(self):
-        ''' See the interface IPrincipalPermissionManager '''
-        pp = self._getPrincipalPermissions()
-        if pp:
-            return pp.getAllCells()
-        return []
-
-    # Implementation helpers
-
-    def _getPrincipalPermissions(self, create=0):
-        """ Get the principal permission map stored in the context, optionally
-            creating one if necessary """
-        # need to remove security proxies here, otherwise we enter
-        # an infinite loop, becuase checking security depends on
-        # getting PrincipalPermissions.
-        from zope.proxy import removeAllProxies
-        context = removeAllProxies(self._context)
-        annotations = IAnnotations(context)
-        try:
-            return annotations[annotation_key]
-        except KeyError:
-            if create:
-                rp = annotations[annotation_key] = SecurityMap()
-                return rp
-        return None
-
-
 class PrincipalPermissionManager(SecurityMap):
     """Mappings between principals and permissions."""
 
@@ -151,9 +100,9 @@
         ''' See the interface IPrincipalPermissionManager '''
         return self.getCol(principal_id)
 
-    def getSetting(self, permission_id, principal_id):
+    def getSetting(self, permission_id, principal_id, default=Unset):
         ''' See the interface IPrincipalPermissionManager '''
-        return self.queryCell(permission_id, principal_id, default=Unset)
+        return self.queryCell(permission_id, principal_id, default)
 
     def getPrincipalsAndPermissions(self):
         ''' See the interface IPrincipalPermissionManager '''

Modified: Zope3/trunk/src/zope/app/securitypolicy/principalrole.py
===================================================================
--- Zope3/trunk/src/zope/app/securitypolicy/principalrole.py	2004-07-21 22:51:35 UTC (rev 26667)
+++ Zope3/trunk/src/zope/app/securitypolicy/principalrole.py	2004-07-21 22:52:23 UTC (rev 26668)
@@ -16,95 +16,46 @@
 $Id$
 """
 from zope.interface import implements
-from zope.security.proxy import trustedRemoveSecurityProxy
 
-from zope.app.annotation.interfaces import IAnnotations
 from zope.app.securitypolicy.interfaces import IPrincipalRoleManager
-from zope.app.securitypolicy.interfaces import IPrincipalRoleMap
 
 from zope.app.security.settings import Allow, Deny, Unset
 from zope.app.securitypolicy.securitymap import SecurityMap
-from zope.app.securitypolicy.securitymap import PersistentSecurityMap
+from zope.app.securitypolicy.securitymap import AnnotationSecurityMap
 
 from zope.app.security.principal import checkPrincipal
 from zope.app.securitypolicy.role import checkRole
 
-annotation_key = 'zope.app.security.AnnotationPrincipalRoleManager'
-
-class AnnotationPrincipalRoleManager(object):
+class AnnotationPrincipalRoleManager(AnnotationSecurityMap):
     """Mappings between principals and roles."""
 
+    # the annotation key is a holdover from this module's old
+    # location, but cannot change without breaking existing databases
+    key = 'zope.app.security.AnnotationPrincipalRoleManager'
+
     implements(IPrincipalRoleManager)
 
-    def __init__(self, context):
-        self._context = context
-
     def assignRoleToPrincipal(self, role_id, principal_id):
-        ''' See the interface IPrincipalRoleManager '''
-        pp = self._getPrincipalRoles(create=1)
-        pp.addCell(role_id, principal_id, Allow)
+        AnnotationSecurityMap.addCell(self, role_id, principal_id, Allow)
 
     def removeRoleFromPrincipal(self, role_id, principal_id):
-        ''' See the interface IPrincipalRoleManager '''
-        pp = self._getPrincipalRoles(create=1)
-        pp.addCell(role_id, principal_id, Deny)
+        AnnotationSecurityMap.addCell(self, role_id, principal_id, Deny)
 
-    def unsetRoleForPrincipal(self, role_id, principal_id):
-        ''' See the interface IPrincipalRoleManager '''
-        pp = self._getPrincipalRoles()
-        # Only unset if there is a security map, otherwise, we're done
-        if pp:
-            pp.delCell(role_id, principal_id)
-
-    def getPrincipalsForRole(self, role_id):
-        ''' See the interface IPrincipalRoleManager '''
-        pp = self._getPrincipalRoles()
-        if pp:
-            return pp.getRow(role_id)
-        return []
-
-    def getRolesForPrincipal(self, principal_id):
-        ''' See the interface IPrincipalRoleManager '''
-        pp = self._getPrincipalRoles()
-        if pp:
-            return pp.getCol(principal_id)
-        return []
-
+    unsetRoleForPrincipal = AnnotationSecurityMap.delCell
+    getPrincipalsForRole = AnnotationSecurityMap.getRow
+    getRolesForPrincipal = AnnotationSecurityMap.getCol
+    
     def getSetting(self, role_id, principal_id):
-        ''' See the interface IPrincipalRoleManager '''
-        pp = self._getPrincipalRoles()
-        if pp:
-            return pp.queryCell(role_id, principal_id, default=Unset)
-        return Unset
+        return AnnotationSecurityMap.queryCell(
+            self, role_id, principal_id, default=Unset)
 
-    def getPrincipalsAndRoles(self):
-        ''' See the interface IPrincipalRoleManager '''
-        pp = self._getPrincipalRoles()
-        if pp:
-            return pp.getAllCells()
-        return []
+    getPrincipalsAndRoles = AnnotationSecurityMap.getAllCells
 
-    # Implementation helpers
 
-    def _getPrincipalRoles(self, create=0):
-        """ Get the principal role map stored in the context, optionally
-            creating one if necessary """
-        annotations = IAnnotations(self._context)
-        try:
-            # there's a chance that annotations is security proxied -
-            # remove proxy to avoid authentication failure on role lookup
-            return trustedRemoveSecurityProxy(annotations)[annotation_key]
-        except KeyError:
-            if create:
-                rp = annotations[annotation_key] = PersistentSecurityMap()
-                return rp
-        return None
-
-
 class PrincipalRoleManager(SecurityMap):
     """Mappings between principals and roles."""
 
-    implements(IPrincipalRoleManager, IPrincipalRoleMap)
+    implements(IPrincipalRoleManager)
 
     def assignRoleToPrincipal(self, role_id, principal_id, check=True):
         ''' See the interface IPrincipalRoleManager '''

Modified: Zope3/trunk/src/zope/app/securitypolicy/rolepermission.py
===================================================================
--- Zope3/trunk/src/zope/app/securitypolicy/rolepermission.py	2004-07-21 22:51:35 UTC (rev 26667)
+++ Zope3/trunk/src/zope/app/securitypolicy/rolepermission.py	2004-07-21 22:52:23 UTC (rev 26668)
@@ -18,7 +18,6 @@
 from zope.interface import implements
 
 from zope.app import zapi
-from zope.app.annotation.interfaces import IAnnotations
 
 from zope.app.security.settings import Allow, Deny, Unset
 from zope.app.security.permission import checkPermission, allPermissions
@@ -27,135 +26,36 @@
 from zope.app.securitypolicy.interfaces import IRolePermissionManager
 from zope.app.securitypolicy.interfaces import IRole
 from zope.app.securitypolicy.interfaces import IRolePermissionMap
-from zope.app.securitypolicy.securitymap import PersistentSecurityMap
+from zope.app.securitypolicy.securitymap import AnnotationSecurityMap
 from zope.app.securitypolicy.securitymap import SecurityMap
 
-# the annotation_key is a holdover from this module's old location, but cannot
-# change without breaking existing databases
-annotation_key = 'zope.app.security.AnnotationRolePermissionManager'
 
-class AnnotationRolePermissionManager(object):
+class AnnotationRolePermissionManager(AnnotationSecurityMap):
     """Provide adapter that manages role permission data in an object attribute
     """
 
-    implements(IRolePermissionManager, IRolePermissionMap)
+    # the annotation key is a holdover from this module's old
+    # location, but cannot change without breaking existing databases
+    key = 'zope.app.security.AnnotationRolePermissionManager'
 
-    def __init__(self, context):
-        self._context = context
+    implements(IRolePermissionManager)
 
     def grantPermissionToRole(self, permission_id, role_id):
-        ''' See the interface IRolePermissionManager '''
-        rp = self._getRolePermissions(create=1)
-        rp.addCell(permission_id, role_id, Allow)
-        # probably not needed, as annotations should manage
-        # their own persistence
-        #self._context._p_changed = 1
+        AnnotationSecurityMap.addCell(self, permission_id, role_id, Allow)
 
     def denyPermissionToRole(self, permission_id, role_id):
-        ''' See the interface IRolePermissionManager '''
-        rp = self._getRolePermissions(create=1)
-        rp.addCell(permission_id, role_id, Deny)
-        # probably not needed, as annotations should manage
-        # their own persistence
-        #self._context._p_changed = 1
+        AnnotationSecurityMap.addCell(self, permission_id, role_id, Deny)
 
-    def unsetPermissionFromRole(self, permission_id, role_id):
-        ''' See the interface IRolePermissionManager '''
-        rp = self._getRolePermissions()
-        # Only unset if there is a security map, otherwise, we're done
-        if rp:
-            rp.delCell(permission_id, role_id)
-            # probably not needed, as annotations should manage
-            # their own persistence
-            #self._context._p_changed = 1
+    unsetPermissionFromRole = AnnotationSecurityMap.delCell
+    getRolesForPermission = AnnotationSecurityMap.getRow
+    getPermissionsForRole = AnnotationSecurityMap.getCol
+    getRolesAndPermissions = AnnotationSecurityMap.getAllCells
 
-    def getRolesForPermission(self, permission_id):
-        '''See interface IRolePermissionMap'''
-        rp = self._getRolePermissions()
-        if rp:
-            return rp.getRow(permission_id)
-        else:
-            return []
-
-    def getPermissionsForRole(self, role_id):
-        '''See interface IRolePermissionMap'''
-        rp = self._getRolePermissions()
-        if rp:
-            return rp.getCol(role_id)
-        else:
-            return []
-
-    def getRolesAndPermissions(self):
-        '''See interface IRolePermissionMap'''
-        rp = self._getRolePermissions()
-        if rp:
-            return rp.getAllCells()
-        else:
-            return []
-
     def getSetting(self, permission_id, role_id):
-        '''See interface IRolePermissionMap'''
-        rp = self._getRolePermissions()
-        if rp:
-            return rp.queryCell(permission_id, role_id)
-        else:
-            return Unset
+        return AnnotationSecurityMap.queryCell(
+            self, permission_id, role_id, default=Unset)
 
-    def _getRolePermissions(self, create=0):
-        """Get the role permission map stored in the context, optionally
-           creating one if necessary"""
-        # need to remove security proxies here, otherwise we enter
-        # an infinite loop, becuase checking security depends on
-        # getting RolePermissions.
-        from zope.proxy import removeAllProxies
-        context = removeAllProxies(self._context)
-        annotations = IAnnotations(context)
-        try:
-            return annotations[annotation_key]
-        except KeyError:
-            if create:
-                rp = annotations[annotation_key] = PersistentSecurityMap()
-                return rp
-        return None
 
-class RolePermissions(object):
-
-    implements(IRole)
-
-    def __init__(self, role, context, permissions):
-        self._role = role
-        self._context = context
-        self._permissions = permissions
-
-
-    def _getId(self):
-        return self._role.id
-
-    id = property(_getId)
-
-    def _getTitle(self):
-        return self._role.title
-
-    title = property(_getTitle)
-
-    def _getDescription(self):
-        return self._role.description
-
-    description = property(_getDescription)
-
-    def permissionsInfo(self):
-        prm = IRolePermissionManager(self._context)
-        rperms = prm.getPermissionsForRole(self._role.id)
-        settings = {}
-        for permission, setting in rperms:
-            settings[permission] = setting.getName()
-        nosetting = Unset.getName()
-        return [{'id': permission.id,
-                 'title': permission.title,
-                 'setting': settings.get(permission.id, nosetting)}
-                for permission in self._permissions]
-
-
 class RolePermissionManager(SecurityMap):
     """Mappings between roles and permissions."""
 

Modified: Zope3/trunk/src/zope/app/securitypolicy/securitymap.py
===================================================================
--- Zope3/trunk/src/zope/app/securitypolicy/securitymap.py	2004-07-21 22:51:35 UTC (rev 26667)
+++ Zope3/trunk/src/zope/app/securitypolicy/securitymap.py	2004-07-21 22:52:23 UTC (rev 26668)
@@ -16,14 +16,11 @@
 $Id$
 """
 from persistent import Persistent
-from persistent.dict import PersistentDict
-from zope.interface import implements
-from zope.app.securitypolicy.interfaces import ISecurityMap
+from zope.app.annotation import IAnnotations
+from zope.security.management import queryInteraction
 
 class SecurityMap(object):
 
-    implements(ISecurityMap)
-
     def __init__(self):
         self._clear()
 
@@ -31,42 +28,66 @@
         self._byrow = {}
         self._bycol = {}
 
-    def _empty_mapping(self):
-        return {}
+    def __nonzero__(self):
+        return bool(self._byrow)
 
     def addCell(self, rowentry, colentry, value):
-        """See ISecurityMap"""
         # setdefault may get expensive if an empty mapping is
         # expensive to create, for PersistentDict for instance.
-        row = self._byrow.setdefault(rowentry, self._empty_mapping())
+        row = self._byrow.get(rowentry)
+        if row:
+            if row.get(colentry) is value:
+                return False
+        else:
+            row = self._byrow[rowentry] = {}
+
+        col = self._bycol.get(colentry)
+        if not col:
+            col = self._bycol[colentry] = {}
+            
         row[colentry] = value
-
-        col = self._bycol.setdefault(colentry, self._empty_mapping())
         col[rowentry] = value
-        try:
-            del self._v_cells
-        except AttributeError:
-            pass
 
+        self._invalidated_interaction_cache()
+        
+        return True
+
+    def _invalidated_interaction_cache(self):
+        # Invalidate this threads interaction cache
+        interaction = queryInteraction()
+        if interaction is not None:
+            try:
+                invalidate_cache = interaction.invalidate_cache
+            except AttributeError:
+                pass
+            else:
+                invalidate_cache()
+
     def delCell(self, rowentry, colentry):
-        """See ISecurityMap"""
         row = self._byrow.get(rowentry)
         if row and (colentry in row):
-            del self._byrow[rowentry][colentry]
-            del self._bycol[colentry][rowentry]
-        try:
-            del self._v_cells
-        except AttributeError:
-            pass
+            del row[colentry]
+            if not row:
+                del self._byrow[rowentry]
+            col = self._bycol[colentry]
+            del col[rowentry]
+            if not col:
+                del self._bycol[colentry]
 
+            self._invalidated_interaction_cache()
+
+            return True
+
+        return False
+
     def queryCell(self, rowentry, colentry, default=None):
-        """See ISecurityMap"""
         row = self._byrow.get(rowentry)
-        if row: return row.get(colentry, default)
-        else: return default
+        if row:
+            return row.get(colentry, default)
+        else:
+            return default
 
     def getCell(self, rowentry, colentry):
-        """See ISecurityMap"""
         marker = object()
         cell = self.queryCell(rowentry, colentry, marker)
         if cell is marker:
@@ -74,40 +95,66 @@
         return cell
 
     def getRow(self, rowentry):
-        """See ISecurityMap"""
         row = self._byrow.get(rowentry)
         if row:
             return row.items()
-        else: return []
+        else:
+            return []
 
     def getCol(self, colentry):
-        """See ISecurityMap"""
         col = self._bycol.get(colentry)
         if col:
             return col.items()
-        else: return []
+        else:
+            return []
 
     def getAllCells(self):
-        """See ISecurityMap"""
-        try:
-            return self._v_cells
-        except AttributeError:
-            pass
         res = []
         for r in self._byrow.keys():
             for c in self._byrow[r].items():
                 res.append((r,) + c)
-        self._v_cells = res
         return res
 
-
 class PersistentSecurityMap(SecurityMap, Persistent):
 
-    implements(ISecurityMap)
+    def addCell(self, rowentry, colentry, value):
+        if SecurityMap.addCell(self, rowentry, colentry, value):
+            self._p_changed = 1
 
-    def _clear(self):
-        self._byrow = PersistentDict()
-        self._bycol = PersistentDict()
+    def delCell(self, rowentry, colentry):
+        if SecurityMap.delCell(self, rowentry, colentry):
+            self._p_changed = 1
 
-    def _empty_mapping(self):
-        return PersistentDict()
+class AnnotationSecurityMap(SecurityMap):
+
+    def __init__(self, context):
+        self._context = context
+        annotations = IAnnotations(self._context)
+        map = annotations.get(self.key)
+        if map is None:
+            self._byrow = {}
+            self._bycol = {}
+        else:
+            self._byrow = map._byrow
+            self._bycol = map._bycol
+        self.map = map
+
+    def _changed(self):
+        map = self.map
+        if isinstance(map, PersistentSecurityMap):
+            map._p_changed
+        else:
+            map = PersistentSecurityMap()
+            map._byrow = self._byrow
+            map._bycol = self._bycol
+            annotations = IAnnotations(self._context)
+            annotations[self.key] = map
+
+    def addCell(self, rowentry, colentry, value):
+        if SecurityMap.addCell(self, rowentry, colentry, value):
+            self._changed()
+
+    def delCell(self, rowentry, colentry):
+        if SecurityMap.delCell(self, rowentry, colentry):
+            self._changed()
+        

Modified: Zope3/trunk/src/zope/app/securitypolicy/tests/test_securitymap.py
===================================================================
--- Zope3/trunk/src/zope/app/securitypolicy/tests/test_securitymap.py	2004-07-21 22:51:35 UTC (rev 26667)
+++ Zope3/trunk/src/zope/app/securitypolicy/tests/test_securitymap.py	2004-07-21 22:52:23 UTC (rev 26668)
@@ -11,35 +11,51 @@
 # FOR A PARTICULAR PURPOSE.
 #
 #############################################################################
-"""Test ISecurityMap implementations
+"""Test SecurityMap implementations
 
 $Id$
 """
 import unittest
 from zope.interface.verify import verifyClass
-from zope.app.securitypolicy.interfaces import ISecurityMap
 from zope.app.securitypolicy.securitymap import SecurityMap
 from zope.app.securitypolicy.securitymap import PersistentSecurityMap
+from zope.security.management import setSecurityPolicy, getInteraction
+from zope.security.management import newInteraction, endInteraction
 
+class InteractionStub:
+    invalidated = 0
+    def invalidate_cache(self):
+        self.invalidated += 1
+
+
 class TestSecurityMap(unittest.TestCase):
 
-    def testInterface(self):
-        verifyClass(ISecurityMap, SecurityMap)
+    def setUp(self):
+        self.oldpolicy = setSecurityPolicy(InteractionStub)
+        newInteraction()
 
+    def tearDown(self):
+        endInteraction()
+        setSecurityPolicy(self.oldpolicy)
+
     def _getSecurityMap(self):
         return SecurityMap()
 
     def test_addCell(self):
         map = self._getSecurityMap()
+        self.assertEqual(getInteraction().invalidated, 0)
         map.addCell(0, 0, 'aa')
+        self.assertEqual(getInteraction().invalidated, 1)
         self.assertEqual(map._byrow[0][0], 'aa')
         self.assertEqual(map._bycol[0][0], 'aa')
 
         map.addCell(1, 0, 'ba')
+        self.assertEqual(getInteraction().invalidated, 2)
         self.assertEqual(map._byrow[1][0], 'ba')
         self.assertEqual(map._bycol[0][1], 'ba')
 
         map.addCell(5, 3, 'fd')
+        self.assertEqual(getInteraction().invalidated, 3)
         self.assertEqual(map._byrow[5][3], 'fd')
         self.assertEqual(map._bycol[3][5], 'fd')
 
@@ -56,18 +72,20 @@
         
     def test_delCell(self):
         map = self._getSecurityMap()
-        map._byrow[0] = map._empty_mapping()
-        map._bycol[1] = map._empty_mapping()
+        self.assertEqual(getInteraction().invalidated, 0)
+        map._byrow[0] = {}
+        map._bycol[1] = {}
         map._byrow[0][1] = 'aa'
         map._bycol[1][0] = 'aa'
         map.delCell(0, 1)
-        self.assertEqual(len(map._byrow.get(0)), 0) 
-        self.assertEqual(len(map._bycol.get(1)), 0) 
+        self.assertEqual(getInteraction().invalidated, 1)
+        self.assertEqual(map._byrow.get(0), None) 
+        self.assertEqual(map._bycol.get(1), None) 
 
     def test_queryCell(self):
         map = self._getSecurityMap()
-        map._byrow[0] = map._empty_mapping()
-        map._bycol[1] = map._empty_mapping()
+        map._byrow[0] = {}
+        map._bycol[1] = {}
         map._byrow[0][1] = 'aa'
         map._bycol[1][0] = 'aa'
 
@@ -78,8 +96,8 @@
 
     def test_getCell(self):
         map = self._getSecurityMap()
-        map._byrow[0] = map._empty_mapping()
-        map._bycol[1] = map._empty_mapping()
+        map._byrow[0] = {}
+        map._bycol[1] = {}
         map._byrow[0][1] = 'aa'
         map._bycol[1][0] = 'aa'
 
@@ -88,15 +106,15 @@
 
     def test_getRow(self):
         map = self._getSecurityMap()
-        map._byrow[0] = map._empty_mapping()
+        map._byrow[0] = {}
         map._byrow[0][1] = 'ab'
         map._byrow[0][2] = 'ac'
-        map._byrow[1] = map._empty_mapping()
+        map._byrow[1] = {}
         map._byrow[1][1] = 'bb'
-        map._bycol[1] = map._empty_mapping()
+        map._bycol[1] = {}
         map._bycol[1][0] = 'ab'
         map._bycol[1][1] = 'bb'
-        map._bycol[2] = map._empty_mapping()
+        map._bycol[2] = {}
         map._bycol[2][0] = 'ac'
 
         self.assertEqual(map.getRow(0), [(1, 'ab'), (2, 'ac')])
@@ -105,15 +123,15 @@
 
     def test_getCol(self):
         map = self._getSecurityMap()
-        map._byrow[0] = map._empty_mapping()
+        map._byrow[0] = {}
         map._byrow[0][1] = 'ab'
         map._byrow[0][2] = 'ac'
-        map._byrow[1] = map._empty_mapping()
+        map._byrow[1] = {}
         map._byrow[1][1] = 'bb'
-        map._bycol[1] = map._empty_mapping()
+        map._bycol[1] = {}
         map._bycol[1][0] = 'ab'
         map._bycol[1][1] = 'bb'
-        map._bycol[2] = map._empty_mapping()
+        map._bycol[2] = {}
         map._bycol[2][0] = 'ac'
 
         self.assertEqual(map.getCol(1), [(0, 'ab'), (1, 'bb')])
@@ -122,15 +140,15 @@
 
     def test_getAllCells(self):
         map = self._getSecurityMap()
-        map._byrow[0] = map._empty_mapping()
+        map._byrow[0] = {}
         map._byrow[0][1] = 'ab'
         map._byrow[0][2] = 'ac'
-        map._byrow[1] = map._empty_mapping()
+        map._byrow[1] = {}
         map._byrow[1][1] = 'bb'
-        map._bycol[1] = map._empty_mapping()
+        map._bycol[1] = {}
         map._bycol[1][0] = 'ab'
         map._bycol[1][1] = 'bb'
-        map._bycol[2] = map._empty_mapping()
+        map._bycol[2] = {}
         map._bycol[2][0] = 'ac'
 
         self.assertEqual(map.getCol(1), [(0, 'ab'), (1, 'bb')])
@@ -140,9 +158,6 @@
 
 class TestPersistentSecurityMap(TestSecurityMap):
 
-    def testInterface(self):
-        verifyClass(ISecurityMap, PersistentSecurityMap)
-
     def _getSecurityMap(self):
         return PersistentSecurityMap()
 

Modified: Zope3/trunk/src/zope/app/securitypolicy/tests/test_zopepolicy.py
===================================================================
--- Zope3/trunk/src/zope/app/securitypolicy/tests/test_zopepolicy.py	2004-07-21 22:51:35 UTC (rev 26667)
+++ Zope3/trunk/src/zope/app/securitypolicy/tests/test_zopepolicy.py	2004-07-21 22:52:23 UTC (rev 26668)
@@ -11,360 +11,61 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
-"""Tests the standard zope policy.
+"""Tests the zope policy.
 
 $Id$
 """
-import unittest
-from zope.interface import implements
-from zope.interface.verify import verifyObject
 
-from zope.app import zapi
+import unittest
+from zope.testing.doctestunit import DocFileSuite
+from zope.app.tests import placelesssetup, ztapi
+from zope.app.annotation.interfaces import IAnnotatable
+from zope.app.annotation.interfaces import IAttributeAnnotatable
+from zope.app.annotation.interfaces import IAnnotations
 from zope.app.annotation.attribute import AttributeAnnotations
-from zope.app.annotation.interfaces import IAttributeAnnotatable, IAnnotations
-from zope.app.security.principalregistry import principalRegistry, PrincipalBase
-from zope.app.security.interfaces import IPermission, IAuthenticationService
-from zope.app.security.permission import Permission
-from zope.app.servicenames import Authentication
-from zope.app.site.tests.placefulsetup import PlacefulSetup
-from zope.app.tests import ztapi
-
-from zope.app.securitypolicy.interfaces import IRole
-from zope.app.securitypolicy.interfaces import IRolePermissionManager
+from zope.app.securitypolicy.interfaces import IGrantInfo
 from zope.app.securitypolicy.interfaces import IPrincipalRoleManager
-
-from zope.app.securitypolicy.role import Role
-from zope.app.securitypolicy.zopepolicy import permissionsOfPrincipal
-from zope.app.securitypolicy.principalpermission \
-     import principalPermissionManager
-from zope.app.securitypolicy.rolepermission import rolePermissionManager
-from zope.app.securitypolicy.principalrole import principalRoleManager
-from zope.app.securitypolicy.principalpermission \
-    import AnnotationPrincipalPermissionManager
 from zope.app.securitypolicy.interfaces import IPrincipalPermissionManager
+from zope.app.securitypolicy.interfaces import IRolePermissionManager
+from zope.app.securitypolicy.principalpermission \
+     import AnnotationPrincipalPermissionManager
 from zope.app.securitypolicy.principalrole \
      import AnnotationPrincipalRoleManager
 from zope.app.securitypolicy.rolepermission \
-    import AnnotationRolePermissionManager
+     import AnnotationRolePermissionManager
+from zope.app.securitypolicy.grantinfo \
+     import AnnotationGrantInfo
+from zope.security.management import endInteraction
 
+def setUp():
+    placelesssetup.setUp()
+    endInteraction()
+    ztapi.provideAdapter(
+        IAttributeAnnotatable, IAnnotations,
+        AttributeAnnotations)
+    ztapi.provideAdapter(
+        IAnnotatable, IPrincipalPermissionManager,
+        AnnotationPrincipalPermissionManager)
+    ztapi.provideAdapter(
+        IAnnotatable, IPrincipalRoleManager,
+        AnnotationPrincipalRoleManager)
+    ztapi.provideAdapter(
+        IAnnotatable, IRolePermissionManager,
+        AnnotationRolePermissionManager)
+    ztapi.provideAdapter(
+        IAnnotatable, IGrantInfo,
+        AnnotationGrantInfo)
 
-class RequestStub(object):
-    def __init__(self, principal, interaction=None):
-        self.principal = principal
-        self.interaction = interaction
+def tearDown():
+    placelesssetup.tearDown()
+    
 
-class Interaction(object):
-    def __init__(self, user):
-        self.participations = [RequestStub(user, self)]
-
-class Unprotected(object):
-    pass
-
-class Principal(PrincipalBase):
-    pass
-
-
-def defineRole(id, title=None, description=None):
-    role = Role(id, title, description)
-    ztapi.provideUtility(IRole, role, name=role.id)
-    return role
-
-def definePermission(id, title=None, description=None):
-    perm = Permission(id, title, description)
-    ztapi.provideUtility(IPermission, perm, name=perm.id)
-    return perm
-
-
-class Test(PlacefulSetup, unittest.TestCase):
-
-    def setUp(self):
-        PlacefulSetup.setUp(self)
-        services = zapi.getGlobalServices()
-
-        services.defineService(Authentication, IAuthenticationService)
-        services.provideService(Authentication, principalRegistry)
-
-        ztapi.provideAdapter(
-            IAttributeAnnotatable, IAnnotations,
-            AttributeAnnotations)
-
-        # set up some principals
-        self.jim = principalRegistry.definePrincipal(
-            'jim', 'Jim', 'Jim Fulton',
-            'jim', '123')
-
-        self.tim = principalRegistry.definePrincipal(
-            'tim', 'Tim', 'Tim Peters',
-            'tim', '456')
-
-        self.unknown = principalRegistry.defineDefaultPrincipal('unknown',
-                    'Unknown', 'Nothing is known about this principal')
-
-        # set up some permissions
-        self.read = definePermission('read', 'Read', 'Read something').id
-
-        self.write = definePermission('write', 'Write', 'Write something').id
-
-        self.create = definePermission('create', 'Create',
-                                       'Create something').id
-
-        self.update = definePermission('update', 'Update',
-                                       'Update something').id
-
-        # ... and some roles...
-        defineRole("zope.Anonymous", "Everybody",
-                   "All users have this role implicitly")
-
-        self.peon = defineRole('Peon', 'Site Peon').id
-
-        self.manager = defineRole('Manager', 'Site Manager').id
-
-        self.arole = defineRole('Another', 'Another Role').id
-
-        # grant and deny some permissions to a principal
-        principalPermissionManager.grantPermissionToPrincipal(
-            self.create, self.jim.id)
-        principalPermissionManager.denyPermissionToPrincipal(
-            self.update, self.jim.id)
-
-        # grant and deny some permissions to the roles
-        rolePermissionManager.grantPermissionToRole(self.read, self.peon)
-
-        rolePermissionManager.grantPermissionToRole(self.read, self.manager)
-        rolePermissionManager.grantPermissionToRole(self.write, self.manager)
-
-        # ... and assign roles to principals
-        principalRoleManager.assignRoleToPrincipal(self.peon, self.jim.id)
-        principalRoleManager.assignRoleToPrincipal(self.manager, self.tim.id)
-
-        self.interaction = self._makeInteraction()
-
-
-    def _makeInteraction(self):
-        from zope.app.securitypolicy.zopepolicy import ZopeSecurityPolicy
-        return ZopeSecurityPolicy()
-
-
-    def __assertPermissions(self, user, expected, object=None):
-        permissions = list(permissionsOfPrincipal(user, object))
-        permissions.sort()
-        self.assertEqual(permissions, expected)
-
-    def testImport(self):
-        from zope.app.securitypolicy.zopepolicy import ZopeSecurityPolicy
-
-    def testInterfaces(self):
-        from zope.security.interfaces import ISecurityPolicy
-        from zope.app.securitypolicy.zopepolicy import ZopeSecurityPolicy
-        verifyObject(ISecurityPolicy, ZopeSecurityPolicy)
-
-    def testCreateInteraction(self):
-        from zope.security.interfaces import IInteraction
-        from zope.app.securitypolicy.zopepolicy import ZopeSecurityPolicy
-        i1 = ZopeSecurityPolicy()
-        verifyObject(IInteraction, i1)
-        self.assertEquals(list(i1.participations), [])
-
-        user = object()
-        rq = RequestStub(user)
-        i2 = ZopeSecurityPolicy(rq)
-        verifyObject(IInteraction, i2)
-        self.assertEquals(list(i2.participations), [rq])
-
-    def testGlobalCheckPermission(self):
-        r = RequestStub(self.jim)
-        self.interaction.add(r)
-        self.failUnless(self.interaction.checkPermission(self.read, None))
-        self.interaction.remove(r)
-
-        r = RequestStub(self.tim)
-        self.interaction.add(r)
-        self.failUnless(self.interaction.checkPermission(self.read, None))
-        self.failUnless(self.interaction.checkPermission(self.write, None))
-        self.interaction.remove(r)
-
-        r = RequestStub(self.unknown)
-        self.interaction.add(r)
-        self.failIf(self.interaction.checkPermission(self.read, None))
-        self.failIf(self.interaction.checkPermission(self.write, None))
-
-        self.failIf(self.interaction.checkPermission(self.read, None))
-
-        self.__assertPermissions(self.jim, ['create', 'read'])
-        self.__assertPermissions(self.tim, ['read', 'write'])
-        self.__assertPermissions(self.unknown, [])
-
-        rolePermissionManager.grantPermissionToRole(
-            self.read, 'zope.Anonymous')
-
-        self.failUnless(self.interaction.checkPermission(self.read, None))
-        self.interaction.remove(r)
-
-        self.__assertPermissions(self.unknown, ['read'])
-
-        principalPermissionManager.grantPermissionToPrincipal(
-            self.write, self.jim.id)
-        r = RequestStub(self.jim)
-        self.interaction.add(r)
-        self.failUnless(self.interaction.checkPermission(self.write, None))
-
-        self.__assertPermissions(self.jim, ['create', 'read', 'write'])
-
-    def testPlaylessPrincipalRole(self):
-        r = RequestStub(self.jim)
-        self.interaction.add(r)
-        self.failIf(self.interaction.checkPermission(self.write, None))
-        principalRoleManager.assignRoleToPrincipal(
-            self.manager, self.jim.id)
-        self.failUnless(self.interaction.checkPermission(self.write, None))
-        principalRoleManager.removeRoleFromPrincipal(
-            self.manager, self.jim.id)
-        self.failIf(self.interaction.checkPermission(self.write, None))
-
-    def testPlayfulPrincipalRole(self):
-        ztapi.provideAdapter(
-            ITest,
-            IPrincipalRoleManager, AnnotationPrincipalRoleManager)
-
-        ob1 = TestClass()
-        ob2 = TestClass(); ob2.__parent__ = ob1
-        ob3 = TestClass(); ob3.__parent__ = ob2
-
-        r = RequestStub(self.jim)
-        self.interaction.add(r)
-        self.failIf(self.interaction.checkPermission(self.write, ob3))
-        AnnotationPrincipalRoleManager(ob3).assignRoleToPrincipal(
-            self.manager, self.jim.id)
-        self.failUnless(self.interaction.checkPermission(self.write, ob3))
-        AnnotationPrincipalRoleManager(ob3).removeRoleFromPrincipal(
-            self.manager, self.jim.id)
-        self.failIf(self.interaction.checkPermission(self.write, ob3))
-
-    def testPlayfulRolePermissions(self):
-
-        ARPM = AnnotationRolePermissionManager
-        ztapi.provideAdapter(ITest,
-                            IRolePermissionManager, ARPM)
-        test = definePermission('test', 'Test', '')
-        test = test.id
-
-        ob1 = TestClass()
-        ob2 = TestClass(); ob2.__parent__ = ob1
-        ob3 = TestClass(); ob3.__parent__ = ob2
-
-        r = RequestStub(self.tim)
-        self.interaction.add(r)
-        self.failIf(self.interaction.checkPermission(test, ob3))
-        self.__assertPermissions(self.tim, ['read', 'write'], ob3)
-
-        ARPM(ob2).grantPermissionToRole(test, self.manager)
-        self.failUnless(self.interaction.checkPermission(test, ob3))
-        self.__assertPermissions(self.tim, ['read', 'test', 'write'], ob3)
-        self.interaction.remove(r)
-
-        r = RequestStub(self.jim)
-        self.interaction.add(r)
-        self.failIf(self.interaction.checkPermission(test, ob3))
-        self.__assertPermissions(self.jim, ['create', 'read'], ob3)
-
-
-        ARPM(ob3).grantPermissionToRole(test, self.peon)
-        self.failUnless(self.interaction.checkPermission(test, ob3))
-        self.__assertPermissions(self.jim, ['create', 'read', 'test'], ob3)
-
-
-
-        principalPermissionManager.denyPermissionToPrincipal(
-            test, self.jim.id)
-        self.failIf(self.interaction.checkPermission(test, ob3))
-        self.__assertPermissions(self.jim, ['create', 'read'], ob3)
-        self.interaction.remove(r)
-
-        principalPermissionManager.unsetPermissionForPrincipal(
-            test, self.jim.id)
-
-        # Make sure multiple conflicting role permissions resolve correctly
-        ARPM(ob2).grantPermissionToRole(test, 'zope.Anonymous')
-        ARPM(ob2).grantPermissionToRole(test, self.arole)
-        ARPM(ob3).denyPermissionToRole(test, self.peon)
-
-        new = principalRegistry.definePrincipal('new', 'Newbie',
-                                                'Newbie User', 'new', '098')
-        principalRoleManager.assignRoleToPrincipal(self.arole, new.id)
-        r = RequestStub(new)
-        self.interaction.add(r)
-        self.failUnless(self.interaction.checkPermission(test, ob3))
-        self.__assertPermissions(new, ['test'], ob3)
-
-        principalRoleManager.assignRoleToPrincipal(self.peon, new.id)
-        self.failIf(self.interaction.checkPermission(test, ob3))
-        self.__assertPermissions(new, ['read'], ob3)
-
-    def testPlayfulPrinciplePermissions(self):
-        APPM = AnnotationPrincipalPermissionManager
-        ztapi.provideAdapter(ITest,
-                       IPrincipalPermissionManager, APPM)
-
-        ob1 = TestClass()
-        ob2 = TestClass(); ob2.__parent__ = ob1
-        ob3 = TestClass(); ob3.__parent__ = ob2
-
-        test = definePermission('test', 'Test', '').id
-
-        r = RequestStub(self.tim)
-        self.interaction.add(r)
-        self.failIf(self.interaction.checkPermission(test, ob3))
-
-        self.__assertPermissions(self.tim, ['read', 'write'], ob3)
-
-        APPM(ob2).grantPermissionToPrincipal(test, self.tim.id)
-        self.failUnless(self.interaction.checkPermission(test, ob3))
-        self.__assertPermissions(self.tim, ['read', 'test', 'write'], ob3)
-
-        APPM(ob3).denyPermissionToPrincipal(test, self.tim.id)
-        self.failIf(self.interaction.checkPermission(test, ob3))
-        self.__assertPermissions(self.tim, ['read', 'write'], ob3)
-        self.interaction.remove(r)
-
-        r = RequestStub(self.jim)
-        self.interaction.add(r)
-        APPM(ob1).denyPermissionToPrincipal(test, self.jim.id)
-        APPM(ob3).grantPermissionToPrincipal(test, self.jim.id)
-        self.failUnless(self.interaction.checkPermission(test, ob3))
-        self.__assertPermissions(self.jim, ['create', 'read', 'test'], ob3)
-
-
-        APPM(ob3).unsetPermissionForPrincipal(test, self.jim.id)
-        self.failIf(self.interaction.checkPermission(test, ob3))
-        self.__assertPermissions(self.jim, ['create', 'read'], ob3)
-        self.interaction.remove(r)
-
-        # make sure placeless principal permissions override placeful ones
-        r = RequestStub(self.tim)
-        self.interaction.add(r)
-        APPM(ob3).grantPermissionToPrincipal(test, self.tim.id)
-        principalPermissionManager.denyPermissionToPrincipal(
-            test, self.tim.id)
-        self.failIf(self.interaction.checkPermission(test, ob3))
-
-        self.__assertPermissions(self.tim, ['read', 'write'], ob3)
-
-
-class ITest(IAttributeAnnotatable):
-    pass
-
-class TestClass(object):
-    implements(ITest)
-
-    __parent__ = None
-
-    def __init__(self):
-        self._roles       = { 'test' : {} }
-        self._permissions = { 'Manager' : {} , 'Peon' : {} }
-
 def test_suite():
-    loader=unittest.TestLoader()
-    return loader.loadTestsFromTestCase(Test)
+    return unittest.TestSuite((
+        DocFileSuite('zopepolicy.txt',
+                     package='zope.app.securitypolicy',
+                     setUp=setUp, tearDown=tearDown),
+        ))
 
-if __name__=='__main__':
-    unittest.TextTestRunner().run(test_suite())
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Modified: Zope3/trunk/src/zope/app/securitypolicy/zopepolicy.py
===================================================================
--- Zope3/trunk/src/zope/app/securitypolicy/zopepolicy.py	2004-07-21 22:51:35 UTC (rev 26667)
+++ Zope3/trunk/src/zope/app/securitypolicy/zopepolicy.py	2004-07-21 22:52:23 UTC (rev 26668)
@@ -15,246 +15,268 @@
 
 $Id$
 """
+
+import zope.interface
+
 from zope.security.management import system_user
-import zope.security.simplepolicies
+from zope.security.simplepolicies import ParanoidSecurityPolicy
 from zope.security.interfaces import ISecurityPolicy
+from zope.security.proxy import getProxiedObject
 
-from zope.app.location import LocationIterator
 from zope.app.security.settings import Allow, Deny
-from zope.app.securitypolicy.interfaces import \
-     IRolePermissionMap, IPrincipalPermissionMap, IPrincipalRoleMap
+
 from zope.app.securitypolicy.principalpermission \
      import principalPermissionManager
+globalPrincipalPermissionSetting = principalPermissionManager.getSetting
+
 from zope.app.securitypolicy.rolepermission import rolePermissionManager
+globalRolesForPermission = rolePermissionManager.getRolesForPermission
+
 from zope.app.securitypolicy.principalrole import principalRoleManager
+globalRolesForPrincipal = principalRoleManager.getRolesForPrincipal
 
-getPermissionsForPrincipal = \
-                principalPermissionManager.getPermissionsForPrincipal
-getPermissionsForRole = rolePermissionManager.getPermissionsForRole
-getRolesForPrincipal = principalRoleManager.getRolesForPrincipal
+from zope.app.securitypolicy.interfaces import IRolePermissionMap
+from zope.app.securitypolicy.interfaces import IPrincipalPermissionMap
+from zope.app.securitypolicy.interfaces import IPrincipalRoleMap
+from zope.app.securitypolicy.interfaces import IGrantInfo
 
-globalContext = object()
+class CacheEntry:
+    pass
+    
+class ZopeSecurityPolicy(ParanoidSecurityPolicy):
+    zope.interface.classProvides(ISecurityPolicy)
 
+    def __init__(self, *args, **kw):
+        ParanoidSecurityPolicy.__init__(self, *args, **kw)
+        self._cache = {}
 
-class ZopeSecurityPolicy(zope.security.simplepolicies.ParanoidSecurityPolicy):
-    zope.interface.classProvides(ISecurityPolicy)
+    def invalidate_cache(self):
+        self._cache = {}
 
-    def checkPermission(self, permission, object):
-        # XXX We aren't really handling multiple principals yet
-        assert len(self.participations) == 1 # XXX
-        user = self.participations[0].principal
+    def cache(self, parent):
+        cache = self._cache.get(id(parent))
+        if cache:
+            cache = cache[0]
+        else:
+            cache = CacheEntry()
+            self._cache[id(parent)] = cache, parent
+        return cache
+    
+    def cached_decision(self, parent, principal, permission):
+        cache = self.cache(parent)
+        try:
+            cache_decision = cache.decision
+        except AttributeError:
+            cache_decision = cache.decision = {}
 
-        # mapping from principal to set of roles
-        if user is system_user:
-            return True
+        cache_decision_prin = cache_decision.get(principal)
+        if not cache_decision_prin:
+            cache_decision_prin = cache_decision[principal] = {}
+            
+        try:
+            return cache_decision_prin[permission]
+        except KeyError:
+            pass
+            
+        decision = self.cached_prinper(parent, principal, permission)
+        if decision is not None:
+            cache_decision_prin[permission] = decision
+            return decision
 
-        roledict = {'zope.Anonymous': Allow}
-        principals = {user.id : roledict}
+        roles = self.cached_roles(parent, permission)
+        if roles:
+            for role in self.cached_principal_roles(parent, principal):
+                if role in roles:
+                    cache_decision_prin[permission] = decision = True
+                    return decision
 
-        role_permissions = {}
-        remove = {}
+        cache_decision_prin[permission] = decision = False
+        return decision
+        
+    def cached_prinper(self, parent, principal, permission):
+        cache = self.cache(parent)
+        try:
+            cache_prin = cache.prin
+        except AttributeError:
+            cache_prin = cache.prin = {}
 
-        # Look for placeless grants first.
+        cache_prin_per = cache_prin.get(principal)
+        if not cache_prin_per:
+            cache_prin_per = cache_prin[principal] = {}
 
-        # get placeless principal permissions
-        for principal in principals:
-            for principal_permission, setting in (
-                getPermissionsForPrincipal(principal)):
-                if principal_permission == permission:
-                    if setting is Deny:
-                        return False
-                    assert setting is Allow
-                    remove[principal] = True
+        try:
+            return cache_prin_per[permission]
+        except KeyError:
+            pass
 
-        # Clean out removed principals
-        if remove:
-            for principal in remove:
-                del principals[principal]
-            if principals:
-                # not done yet
-                remove.clear()
-            else:
-                # we've eliminated all the principals
-                return True
+        if parent is None:
+            prinper = globalPrincipalPermissionSetting(
+                permission, principal, None)
+            if prinper is not None:
+                prinper = prinper is Allow
+            cache_prin_per[permission] = prinper
+            return prinper
 
-        # get placeless principal roles
-        for principal in principals:
-            roles = principals[principal]
-            for role, setting in getRolesForPrincipal(principal):
-                assert setting in (Allow, Deny)
-                if role not in roles:
-                    roles[role] = setting
+        prinper = IPrincipalPermissionMap(parent, None)
+        if prinper is not None:
+            prinper = prinper.getSetting(permission, principal, None)
+            if prinper is not None:
+                prinper = prinper is Allow
+                cache_prin_per[permission] = prinper
+                return prinper
 
-        for perm, role, setting in (
-            rolePermissionManager.getRolesAndPermissions()):
-            assert setting in (Allow, Deny)
-            if role not in role_permissions:
-                role_permissions[role] = {perm: setting}
-            else:
-                if perm not in role_permissions[role]:
-                    role_permissions[role][perm] = setting
+        parent = getProxiedObject(getattr(parent, '__parent__', None))
+        prinper = self.cached_prinper(parent, principal, permission)
+        cache_prin_per[permission] = prinper
+        return prinper
+        
+    def cached_roles(self, parent, permission):
+        cache = self.cache(parent)
+        try:
+            cache_roles = cache.roles
+        except AttributeError:
+            cache_roles = cache.roles = {}
+        try:
+            return cache_roles[permission]
+        except KeyError:
+            pass
+        
+        if parent is None:
+            roles = dict(
+                [(role, 1)
+                 for (role, setting) in globalRolesForPermission(permission)
+                 if setting is Allow
+                 ]
+               )
+            cache_roles[permission] = roles
+            return roles
 
-        # Get principal permissions based on roles
-        for principal in principals:
-            roles = principals[principal]
-            for role, role_setting in roles.items():
-                if role_setting is Deny:
-                    return False
-                if role in role_permissions:
-                    if permission in role_permissions[role]:
-                        setting = role_permissions[role][permission]
-                        if setting is Deny:
-                            return False
-                        remove[principal] = True
+        roles = self.cached_roles(
+            getProxiedObject(getattr(parent, '__parent__', None)),
+            permission)
+        roleper = IRolePermissionMap(parent, None)
+        if roleper:
+            roles = roles.copy()
+            for role, setting in roleper.getRolesForPermission(permission):
+                if setting is Allow:
+                    roles[role] = 1
+                elif role in roles:
+                    del roles[role]
 
+        cache_roles[permission] = roles
+        return roles
 
-        # Clean out removed principals
-        if remove:
-            for principal in remove:
-                del principals[principal]
-            if principals:
-                # not done yet
-                remove.clear()
-            else:
-                # we've eliminated all the principals
-                return True
+    def cached_principal_roles(self, parent, principal):
+        cache = self.cache(parent)
+        try:
+            cache_principal_roles = cache.principal_roles
+        except AttributeError:
+            cache_principal_roles = cache.principal_roles = {}
+        try:
+            return cache_principal_roles[principal]
+        except KeyError:
+            pass
 
-        # Look for placeful grants
-        for place in LocationIterator(object):
+        if parent is None:
+            roles = dict(
+                [(role, 1)
+                 for (role, setting) in globalRolesForPrincipal(principal)
+                 if setting is Allow
+                 ]
+                 )
+            roles['zope.Anonymous'] = 1 # Everybody has Anonymous
+            cache_principal_roles[principal] = roles
+            return roles
+            
+        roles = self.cached_principal_roles(
+            getProxiedObject(getattr(parent, '__parent__', None)),
+            principal)
+        prinrole = IPrincipalRoleMap(parent, None)
+        if prinrole:
+            roles = roles.copy()
+            for role, setting in prinrole.getRolesForPrincipal(principal):
+                if setting is Allow:
+                    roles[role] = 1
+                elif role in roles:
+                    del roles[role]
 
-            # Copy specific principal permissions
-            prinper = IPrincipalPermissionMap(place, None)
-            if prinper is not None:
-                for principal in principals:
-                    for principal_permission, setting in (
-                        prinper.getPermissionsForPrincipal(principal)):
-                        if principal_permission == permission:
-                            if setting is Deny:
-                                return False
+        cache_principal_roles[principal] = roles
+        return roles
+        
 
-                            assert setting is Allow
-                            remove[principal] = True
+    def checkPermission(self, permission, object):
+        principals = {}
+        for participation in self.participations:
+            principal = participation.principal
+            if principal is system_user:
+                continue # always allow system_user
+            principals[principal.id] = 1
 
-            # Clean out removed principals
-            if remove:
-                for principal in remove:
-                    del principals[principal]
-                if principals:
-                    # not done yet
-                    remove.clear()
-                else:
-                    # we've eliminated all the principals
-                    return True
+        if not principals:
+            return True
 
-            # Collect principal roles
-            prinrole = IPrincipalRoleMap(place, None)
-            if prinrole is not None:
-                for principal in principals:
-                    roles = principals[principal]
-                    for role, setting in (
-                        prinrole.getRolesForPrincipal(principal)):
-                        assert setting in (Allow, Deny)
-                        if role not in roles:
-                            roles[role] = setting
+        object = getProxiedObject(object)
+        parent = getProxiedObject(getattr(object, '__parent__', None))
 
-            # Collect role permissions
-            roleper = IRolePermissionMap(place, None)
-            if roleper is not None:
-                for perm, role, setting in roleper.getRolesAndPermissions():
-                    assert setting in (Allow, Deny)
-                    if role not in role_permissions:
-                        role_permissions[role] = {perm: setting}
-                    else:
-                        if perm not in role_permissions[role]:
-                            role_permissions[role][perm] = setting
-
-            # Get principal permissions based on roles
+        grant_info = IGrantInfo(object, None)
+        if not grant_info:
+            # No local grants; just use cached decision for parent
             for principal in principals:
-                roles = principals[principal]
-                for role, role_setting in roles.items():
-                    if role_setting is Deny:
-                        return False
-                    if role in role_permissions:
-                        if permission in role_permissions[role]:
-                            setting = role_permissions[role][permission]
-                            if setting is Deny:
-                                return False
-                            remove[principal] = True
+                if not self.cached_decision(parent, principal, permission):
+                    return False
+            return True
 
-            # Clean out removed principals
-            if remove:
-                for principal in remove:
+        # We need to combine local and parent info
+            
+        # First, look for principal grants
+        for principal in principals.keys():
+            setting = grant_info.principalPermissionGrant(
+                principal, permission)
+            if setting is Deny:
+                return False
+            elif setting is Allow: # setting could be None
+                del principals[principal]
+                if not principals:
+                    return True
+                continue
+
+            decision = self.cached_prinper(parent, principal, permission)
+            if decision is not None:
+                if decision:
                     del principals[principal]
-                if principals:
-                    # not done yet
-                    remove.clear()
+                    if not principals:
+                        return True
                 else:
-                    # we've eliminated all the principals
-                    return True
+                    return decision # False
 
-        return False # deny by default
+        roles = self.cached_roles(parent, permission)
+        local_roles = grant_info.getRolesForPermission(permission)
+        if local_roles:
+            roles = roles.copy()
+            for role, setting in local_roles:
+                if setting is Allow:
+                    roles[role] = 1
+                elif role in roles:
+                    del roles[role]
 
+        for principal in principals.keys():
+            proles = self.cached_principal_roles(parent, principal).copy()
+            for role, setting in grant_info.getRolesForPrincipal(principal):
+                if setting is Allow:
+                    if role in roles:
+                        del principals[principal]
+                        if not principals:
+                            return True
+                        break
+                elif role in proles:
+                    del proles[role]
+            else:
+                for role in proles:
+                    if role in roles:
+                        del principals[principal]
+                        if not principals:
+                            return True
+                        break                        
+                
+        return False
 
-def permissionsOfPrincipal(principal, object):
-    permissions = {}
-
-    roles = {'zope.Anonymous': Allow}
-    principalid = principal.id
-
-    # Make two passes.
-
-    # First, collect what we know about the principal:
-
-
-    # get placeless principal permissions
-    for permission, setting in getPermissionsForPrincipal(principalid):
-        if permission not in permissions:
-            permissions[permission] = setting
-
-    # get placeless principal roles
-    for role, setting in getRolesForPrincipal(principalid):
-        if role not in roles:
-            roles[role] = setting
-
-    # get placeful principal permissions and roles
-    for place in LocationIterator(object):
-
-        # Copy specific principal permissions
-        prinper = IPrincipalPermissionMap(place, None)
-        if prinper is not None:
-            for permission, setting in prinper.getPermissionsForPrincipal(
-                principalid):
-                if permission not in permissions:
-                    permissions[permission] = setting
-
-        # Collect principal roles
-        prinrole = IPrincipalRoleMap(place, None)
-        if prinrole is not None:
-            for role, setting in prinrole.getRolesForPrincipal(principalid):
-                if role not in roles:
-                    roles[role] = setting
-
-    # Second, update permissions using principal
-
-    for perm, role, setting in (
-        rolePermissionManager.getRolesAndPermissions()):
-        if role in roles and perm not in permissions:
-            permissions[perm] = setting
-
-    for place in LocationIterator(object):
-
-        # Collect role permissions
-        roleper = IRolePermissionMap(place, None)
-        if roleper is not None:
-            for perm, role, setting in roleper.getRolesAndPermissions():
-                if role in roles and perm not in permissions:
-                    permissions[perm] = setting
-
-
-
-    result = [permission
-              for permission in permissions
-              if permissions[permission] is Allow]
-
-    return result
-

Added: Zope3/trunk/src/zope/app/securitypolicy/zopepolicy.txt
===================================================================
--- Zope3/trunk/src/zope/app/securitypolicy/zopepolicy.txt	2004-07-21 22:51:35 UTC (rev 26667)
+++ Zope3/trunk/src/zope/app/securitypolicy/zopepolicy.txt	2004-07-21 22:52:23 UTC (rev 26668)
@@ -0,0 +1,480 @@
+Classic Zope Security Policy
+============================
+
+This package implements a role-based security policy similar to the
+policy found in Zope 2.  The security policy is responsible for
+deciding whether an interaction has a permission on an object.  This
+security policy does this using grant and denial information.  Managers
+can grant or deny:
+
+  - roles to principals,
+
+  - permissions to principals, and 
+
+  - permissions to roles
+
+Grants and denials are stored as annotations on objects.  To store
+grants and denials, objects must be annotatable:
+
+  >>> import zope.interface
+  >>> from zope.app.annotation.interfaces import IAttributeAnnotatable
+  >>> class Ob:
+  ...     zope.interface.implements(IAttributeAnnotatable)
+  >>> ob = Ob()
+
+We use objects to represent principals.  These objects implement an
+interface named `IPrincipal`, but the security policy only uses the `id`
+attribute: 
+
+  >>> class Principal:
+  ...     pass
+  >>> principal = Principal()
+  >>> principal.id = 'bob'
+ 
+Roles and permissions are also represented by objects, however, for
+the purposes of the scurity policy, only string `ids` are used.
+
+The security policy provides a factory for creating interactions:
+
+  >>> import zope.app.securitypolicy.zopepolicy
+  >>> interaction = zope.app.securitypolicy.zopepolicy.ZopeSecurityPolicy()
+
+An interaction represents a specific interaction between some
+principals (normally users) and the system.  Normally, we are only
+concerned with the interaction of one principal with the system, although
+we can have interactions of multiple principals.  Multiple-principal
+interactions normally occur when untrusted users store code on a
+system for later execution.  When untrusted code is executing, the
+authors of the code participate in the interaction.  An
+interaction has a permission on an object only if all of the
+principals participataing in the interaction have access to the object.
+
+The `checkPermission` method on interactions is used to test whether
+an interaction has a permission for an object.  An interaction without
+participants always has every permission:
+
+  >>> interaction.checkPermission('P1', ob)
+  True
+
+In this example, 'P1' is a permission id.
+
+Normally, interactions have participants:
+
+  >>> class Participation:
+  ...     interaction = None
+  >>> participation = Participation()
+  >>> participation.principal = principal
+  >>> interaction.add(participation)
+
+If we have participants, then we don't have a permission unless there
+are grants:
+
+  >>> interaction.checkPermission('P1', ob)
+  False
+
+We make grants and denials on objects by adaping them to various
+granting interfaces:
+
+  >>> from zope.app.securitypolicy import interfaces
+  >>> roleper  = interfaces.IRolePermissionManager(ob)
+  >>> prinrole = interfaces.IPrincipalRoleManager(ob)
+  >>> prinper  = interfaces.IPrincipalPermissionManager(ob)
+
+The computations involved in checking permissions can be
+significant. To reduce the computational cost, caching is used
+extensively. We could invalidate the cache as we make grants, but the
+adapters for making grants will automatically invalidate the cache of
+the current interaction.  They use the security-management apis to do
+this. To take advantage of the cache invalidation, we'll need to let
+the security-management system manage out interactions.  First, we'll
+set our security policy as the default:
+
+  >>> import zope.security.management
+  >>> oldpolicy = zope.security.management.setSecurityPolicy(
+  ...      zope.app.securitypolicy.zopepolicy.ZopeSecurityPolicy)
+
+and then we'll create a new interaction:
+
+  >>> participation = Participation()
+  >>> participation.principal = principal
+  >>> zope.security.management.newInteraction(participation)
+  >>> interaction = zope.security.management.getInteraction()
+
+We normally provide access by granting permissions to roles for an object:
+
+  >>> roleper.grantPermissionToRole('P1', 'R1')
+
+and then granting roles to principals for an object (local roles):
+
+  >>> prinrole.assignRoleToPrincipal('R1', 'bob')
+
+The combination of these grants, which we call a role-based grant,
+provides the permission:
+
+  >>> interaction.checkPermission('P1', ob)
+  True
+
+We can also provide a permission directly:
+
+  >>> prinper.grantPermissionToPrincipal('P2', 'bob')
+  >>> interaction.checkPermission('P2', ob)
+  True
+
+Permission grants or denials override role-based grant or denials.  So
+if we deny P1:
+
+  >>> prinper.denyPermissionToPrincipal('P1', 'bob')
+
+we cause the interaction to lack the permission, despite the role
+grants:
+
+  >>> interaction.checkPermission('P1', ob)
+  False
+
+Similarly, even if we have a role-based denial of P2:
+
+  >>> roleper.denyPermissionToRole('P2', 'R1')
+
+we still have access, because of the permission-based grant:
+
+  >>> interaction.checkPermission('P2', ob)
+  True
+
+A role-based denial doesn't actually deny a permission; rather it
+prevents the granting of a permission. So, if we have both grants and
+denials based on roles, we have access:
+
+  >>> roleper.grantPermissionToRole('P3', 'R1')
+  >>> roleper.grantPermissionToRole('P3', 'R2')
+  >>> roleper.denyPermissionToRole('P3', 'R3')
+  >>> prinrole.removeRoleFromPrincipal('R2', 'bob')
+  >>> prinrole.assignRoleToPrincipal('R3', 'bob')
+
+  >>> interaction.checkPermission('P3', ob)
+  True
+
+Global grants
+-------------
+
+Grants made to an object are said to be "local".  We can also make
+global grants:
+
+  >>> from zope.app.securitypolicy.rolepermission \
+  ...     import rolePermissionManager as roleperG  
+  >>> from zope.app.securitypolicy.principalpermission \
+  ...     import principalPermissionManager as prinperG
+  >>> from zope.app.securitypolicy.principalrole \
+  ...     import principalRoleManager as prinroleG
+
+And the same rules apply to global grants and denials.
+
+  >>> roleperG.grantPermissionToRole('P1G', 'R1G', False)
+
+In these tests, we aren't bothering to define any roles, permissions,
+or principals, so we pass an extra argument that tells the granting
+routines not to check the validity of the values.
+
+  >>> prinroleG.assignRoleToPrincipal('R1G', 'bob', False)
+  >>> interaction.checkPermission('P1G', ob)
+  True
+
+  >>> prinperG.grantPermissionToPrincipal('P2G', 'bob', False)
+  >>> interaction.checkPermission('P2G', ob)
+  True
+
+  >>> prinperG.denyPermissionToPrincipal('P1G', 'bob', False)
+  >>> interaction.checkPermission('P1G', ob)
+  False
+
+  >>> roleperG.denyPermissionToRole('P2G', 'R1G', False)
+  >>> interaction.checkPermission('P2G', ob)
+  True
+
+  >>> roleperG.grantPermissionToRole('P3G', 'R1G', False)
+  >>> roleperG.grantPermissionToRole('P3G', 'R2G', False)
+  >>> roleperG.denyPermissionToRole('P3G', 'R3G', False)
+  >>> prinroleG.removeRoleFromPrincipal('R2G', 'bob', False)
+  >>> prinroleG.assignRoleToPrincipal('R3G', 'bob', False)
+  >>> interaction.checkPermission('P3G', ob)
+  True
+
+Local verses global grants
+--------------------------
+
+We, of course, acquire global grants by default:
+
+  >>> interaction.checkPermission('P1G', ob)
+  False
+  >>> interaction.checkPermission('P2G', ob)
+  True
+  >>> interaction.checkPermission('P3G', ob)
+  True
+
+Local role-based grants do not override global prinicipal-specific denials:
+
+  >>> roleper.grantPermissionToRole('P1G', 'R1G')
+  >>> prinrole.assignRoleToPrincipal('R1G', 'bob')
+  >>> interaction.checkPermission('P1G', ob)
+  False
+  
+And local role-based denials don't override global
+principal-grants:
+
+  >>> roleper.denyPermissionToRole('P2G', 'R1G')
+  >>> interaction.checkPermission('P2G', ob)
+  True
+
+A local role-based deny can cancel a global role-based grant:
+
+  >>> roleper.denyPermissionToRole('P3G', 'R1G')
+  >>> interaction.checkPermission('P3G', ob)
+  False
+
+and a local role-based grant can override a global role-based denial:
+
+  >>> roleperG.denyPermissionToRole('P4G', 'R1G', False)
+  >>> prinroleG.assignRoleToPrincipal('R1G', "bob", False)
+  >>> interaction.checkPermission('P4G', ob)
+  False
+  >>> roleper.grantPermissionToRole('P4G', 'R1G')
+  >>> interaction.checkPermission('P4G', ob)
+  True
+  >>> prinroleG.removeRoleFromPrincipal('R1G', "bob", False)
+  >>> interaction.checkPermission('P4G', ob)
+  True
+
+Of course, a local permission-based grant or denial overrides any
+global setting and overrides local role-based grants or denials:
+
+  >>> prinper.grantPermissionToPrincipal('P3G', 'bob')
+  >>> interaction.checkPermission('P3G', ob)
+  True
+
+  >>> prinper.denyPermissionToPrincipal('P2G', 'bob')
+  >>> interaction.checkPermission('P2G', ob)
+  False
+
+Sublocations
+------------
+
+We can have sub-locations. A sublocation of a location is an object
+whos `__parent__` attribute is the location:
+
+  >>> ob2 = Ob()
+  >>> ob2.__parent__ = ob
+
+By default, sublocations acquire grants from higher locations:
+
+  >>> interaction.checkPermission('P1', ob2)
+  False
+  >>> interaction.checkPermission('P2', ob2)
+  True
+  >>> interaction.checkPermission('P3', ob2)
+  True
+  >>> interaction.checkPermission('P1G', ob2)
+  False
+  >>> interaction.checkPermission('P2G', ob2)
+  False
+  >>> interaction.checkPermission('P3G', ob2)
+  True
+  >>> interaction.checkPermission('P4G', ob2)
+  True
+
+Sublocation role-based grants do not override their parent
+prinicipal-specific denials:
+
+  >>> roleper2  = interfaces.IRolePermissionManager(ob2)
+  >>> prinrole2 = interfaces.IPrincipalRoleManager(ob2)
+  >>> prinper2  = interfaces.IPrincipalPermissionManager(ob2)
+
+  >>> roleper2.grantPermissionToRole('P1', 'R1')
+  >>> prinrole2.assignRoleToPrincipal('R1', 'bob')
+  >>> interaction.checkPermission('P1', ob2)
+  False
+  
+And local role-based denials don't override their parents
+principal-grant:
+
+  >>> roleper2.denyPermissionToRole('P2', 'R1')
+  >>> interaction.checkPermission('P2', ob2)
+  True
+
+A local role-based deny can cancel a parent role-based grant:
+
+  >>> roleper2.denyPermissionToRole('P3', 'R1')
+  >>> interaction.checkPermission('P3', ob2)
+  False
+
+and a local role-based grant can override a parent role-based denial:
+
+  >>> roleper.denyPermissionToRole('P4', 'R1')
+  >>> prinrole.assignRoleToPrincipal('R1', 'bob')
+  >>> interaction.checkPermission('P4', ob2)
+  False
+  >>> roleper2.grantPermissionToRole('P4', 'R1')
+  >>> interaction.checkPermission('P4', ob2)
+  True
+  >>> prinrole.removeRoleFromPrincipal('R1', 'bob')
+  >>> interaction.checkPermission('P4', ob2)
+  True
+  
+
+Of course, a local permission-based grant or denial overrides any
+global setting and overrides local role-based grants or denials:
+
+  >>> prinper.grantPermissionToPrincipal('P3', 'bob')
+  >>> interaction.checkPermission('P3', ob2)
+  True
+
+  >>> prinper.denyPermissionToPrincipal('P2', 'bob')
+  >>> interaction.checkPermission('P2', ob2)
+  False
+
+If an object is not annotatable, but does have a parent, it will get
+it's grants from it's parent:
+
+  >>> class C:
+  ...     pass
+  >>> ob3 = C()
+  >>> ob3.__parent__ = ob
+
+  >>> interaction.checkPermission('P1', ob3)
+  False
+  >>> interaction.checkPermission('P2', ob3)
+  False
+  >>> interaction.checkPermission('P3', ob3)
+  True
+  >>> interaction.checkPermission('P1G', ob3)
+  False
+  >>> interaction.checkPermission('P2G', ob3)
+  False
+  >>> interaction.checkPermission('P3G', ob3)
+  True
+  >>> interaction.checkPermission('P4G', ob3)
+  True
+
+The same results will be had if there are multiple non-annotatable
+objects:
+
+  >>> ob3.__parent__ = C()
+  >>> ob3.__parent__.__parent__ = ob
+
+  >>> interaction.checkPermission('P1', ob3)
+  False
+  >>> interaction.checkPermission('P2', ob3)
+  False
+  >>> interaction.checkPermission('P3', ob3)
+  True
+  >>> interaction.checkPermission('P1G', ob3)
+  False
+  >>> interaction.checkPermission('P2G', ob3)
+  False
+  >>> interaction.checkPermission('P3G', ob3)
+  True
+  >>> interaction.checkPermission('P4G', ob3)
+  True
+
+and if an object doesn't have a parent:
+
+  >>> del ob3.__parent__
+
+it will have whatever grants were made globally:
+
+  >>> interaction.checkPermission('P1', ob3)
+  False
+  >>> interaction.checkPermission('P2', ob3)
+  False
+  >>> interaction.checkPermission('P3', ob3)
+  False
+  >>> interaction.checkPermission('P1G', ob3)
+  False
+  >>> interaction.checkPermission('P2G', ob3)
+  True
+  >>> interaction.checkPermission('P3G', ob3)
+  False
+  >>> interaction.checkPermission('P4G', ob3)
+  False
+
+  >>> prinroleG.assignRoleToPrincipal('R1G', "bob", False)
+  >>> interaction.checkPermission('P3G', ob3)
+  True
+
+We'll get the same result if we have a non-annotatble parent without a
+parent:
+
+  >>> ob3.__parent__ = C()
+
+  >>> interaction.checkPermission('P1', ob3)
+  False
+  >>> interaction.checkPermission('P2', ob3)
+  False
+  >>> interaction.checkPermission('P3', ob3)
+  False
+  >>> interaction.checkPermission('P1G', ob3)
+  False
+  >>> interaction.checkPermission('P2G', ob3)
+  True
+  >>> interaction.checkPermission('P3G', ob3)
+  True
+  >>> interaction.checkPermission('P4G', ob3)
+  False
+
+The Anonymous role
+------------------
+
+The security policy defines a special role named "zope.Anonymous".  All
+principals have this role and the role cannot be taken away. 
+
+  >>> roleperG.grantPermissionToRole('P5', 'zope.Anonymous', False)
+  >>> interaction.checkPermission('P5', ob2)
+  True
+
+Proxies
+-------
+
+Objects may be proxied:
+
+  >>> from zope.security.checker import ProxyFactory
+  >>> ob = ProxyFactory(ob)
+  >>> interaction.checkPermission('P1', ob)
+  False
+  >>> interaction.checkPermission('P2', ob)
+  False
+  >>> interaction.checkPermission('P3', ob)
+  True
+  >>> interaction.checkPermission('P1G', ob)
+  False
+  >>> interaction.checkPermission('P2G', ob)
+  False
+  >>> interaction.checkPermission('P3G', ob)
+  True
+  >>> interaction.checkPermission('P4G', ob)
+  True
+
+as may their parents:
+
+  >>> ob3 = C()
+  >>> ob3.__parent__ = ob
+
+  >>> interaction.checkPermission('P1', ob3)
+  False
+  >>> interaction.checkPermission('P2', ob3)
+  False
+  >>> interaction.checkPermission('P3', ob3)
+  True
+  >>> interaction.checkPermission('P1G', ob3)
+  False
+  >>> interaction.checkPermission('P2G', ob3)
+  False
+  >>> interaction.checkPermission('P3G', ob3)
+  True
+  >>> interaction.checkPermission('P4G', ob3)
+  True
+
+Cleanup
+-------
+
+We clean up the changes we made:
+
+  >>> zope.security.management.endInteraction()
+  >>> ignore = zope.security.management.setSecurityPolicy(oldpolicy)


Property changes on: Zope3/trunk/src/zope/app/securitypolicy/zopepolicy.txt
___________________________________________________________________
Name: svn:eol-style
   + native



More information about the Zope3-Checkins mailing list