[Zope-Checkins] SVN: Zope/branches/tseaver-five-integration-security/ Check in my acquisition / security noodling for Jim to play with.

Tres Seaver tseaver at zope.com
Mon Mar 28 12:16:28 EST 2005


Log message for revision 29696:
  Check in my acquisition / security noodling for Jim to play with.
  

Changed:
  A   Zope/branches/tseaver-five-integration-security/
  U   Zope/branches/tseaver-five-integration-security/lib/python/AccessControl/ImplPython.py
  U   Zope/branches/tseaver-five-integration-security/lib/python/AccessControl/cAccessControl.c
  A   Zope/branches/tseaver-five-integration-security/lib/python/AccessControl/tests/testAcquisition.py
  U   Zope/branches/tseaver-five-integration-security/lib/python/Shared/DC/Scripts/Bindings.py

-=-
Copied: Zope/branches/tseaver-five-integration-security (from rev 29692, Zope/branches/five-integration)

Modified: Zope/branches/tseaver-five-integration-security/lib/python/AccessControl/ImplPython.py
===================================================================
--- Zope/branches/five-integration/lib/python/AccessControl/ImplPython.py	2005-03-27 18:39:17 UTC (rev 29692)
+++ Zope/branches/tseaver-five-integration-security/lib/python/AccessControl/ImplPython.py	2005-03-28 17:16:28 UTC (rev 29696)
@@ -17,6 +17,9 @@
 import string
 
 from Acquisition import aq_base
+from Acquisition import aq_parent
+from Acquisition import aq_inner
+from Acquisition import aq_acquire
 from ExtensionClass import Base
 from zLOG import LOG, PROBLEM
 
@@ -522,38 +525,57 @@
     Raises Unauthorized if the attribute is found but the user is
     not allowed to access the attribute.
     """
-    if name[:1] != '_':
-        # Try to get the attribute normally so that unusual
-        # exceptions are caught early.
-        try: v = getattr(inst, name)
-        except AttributeError:
-            if default is not _marker:
-                return default
-            raise
+    if name[:1] == '_':
+        raise Unauthorized, name
 
-        assertion = Containers(type(inst))
-        if isinstance(assertion, dict):
-            # We got a table that lets us reason about individual
-            # attrs
-            assertion = assertion.get(name)
-            if assertion:
-                # There's an entry, but it may be a function.
-                if callable(assertion):
-                    return assertion(inst, name)
+    # Try to get the attribute normally so that unusual
+    # exceptions are caught early.
+    try:
+        v = getattr(inst, name)
+    except AttributeError:
+        if default is not _marker:
+            return default
+        raise
 
-                # Nope, it's boolean
-                return v
-            raise Unauthorized, name
+    return _verify_attribute_access(inst, name, v)
 
-        elif assertion:
-            # So the entry in the outer table is not a dict
-            # It's allowed to be a vetoing function:
+def _verify_attribute_access(inst, name, v):
+
+    try:
+        container = v.im_self
+    except AttributeError:
+        container = aq_parent(aq_inner(v)) or inst
+
+    assertion = Containers(type(container))
+
+    if isinstance(assertion, dict):
+        # We got a table that lets us reason about individual
+        # attrs
+        assertion = assertion.get(name)
+        if assertion:
+            # There's an entry, but it may be a function.
             if callable(assertion):
-                assertion(name, v)
-            # No veto, so we can return
+                return assertion(inst, name)
+
+            # Nope, it's boolean
             return v
+        raise Unauthorized, name
 
-        validate = SecurityManagement.getSecurityManager().validate
-        if validate(inst, inst, name, v):
-            return v
-    raise Unauthorized, name
+    if assertion:
+        if callable(assertion):
+            factory = assertion(name, v)
+            if callable(factory):
+                return factory(inst, name)
+            assert factory == 1
+        else:
+            assert assertion == 1
+        return v
+
+
+    # See if we can get the value doing a filtered acquire.
+    # aq_acquire will either return the same value as held by
+    # v or it will return an Unauthorized raised by validate.
+    validate = SecurityManagement.getSecurityManager().validate
+    aq_acquire(inst, name, aq_validate, validate)
+    
+    return v

Modified: Zope/branches/tseaver-five-integration-security/lib/python/AccessControl/cAccessControl.c
===================================================================
--- Zope/branches/five-integration/lib/python/AccessControl/cAccessControl.c	2005-03-27 18:39:17 UTC (rev 29692)
+++ Zope/branches/tseaver-five-integration-security/lib/python/AccessControl/cAccessControl.c	2005-03-28 17:16:28 UTC (rev 29696)
@@ -2080,8 +2080,13 @@
   int i;
 
   /* if name[:1] != '_': */
-  if ( (PyString_Check(name) || PyUnicode_Check(name)) && 
-       PyString_AsString(name)[0] != '_')
+  if (PyString_Check(name) || PyUnicode_Check(name)) {
+    char *name_s = PyString_AsString(name);
+
+    if (name_s == NULL)
+        return NULL;
+
+    if (name_s[0] != '_')
     {
 
       /*
@@ -2107,31 +2112,27 @@
         }
 
       /*
-            assertion = Containers(type(inst))
-            if type(assertion) is DictType:
-                # We got a table that lets us reason about individual
-                # attrs
-                assertion = assertion.get(name)
-                if assertion:
-                    # There's an entry, but it may be a function.
-                    if callable(assertion):
-                        return assertion(inst, name)
 
-                    # Nope, it's boolean
-                    return v
-                raise Unauthorized, name
-            
-            elif assertion:
-                # So the entry in the outer table is not a dict 
-                # It's allowed to be a vetoing function:
-                if callable(assertion):
-                    assertion(name, v)
-                # No veto, so we can return
-                return v
+        assertion = Containers(type(inst))
       */
       t = PyDict_GetItem(ContainerAssertions, OBJECT(inst->ob_type));
       if (t != NULL)
         {
+
+      /*
+        if isinstance(assertion, dict):
+            # We got a table that lets us reason about individual
+            # attrs
+            assertion = assertion.get(name)
+            if assertion:
+                # There's an entry, but it may be a function.
+                if callable(assertion):
+                    return assertion(inst, name)
+
+                # Nope, it's boolean
+                return v
+            raise Unauthorized, name
+      */
           if (PyDict_Check(t))
             {
               PyObject *attrv;
@@ -2155,43 +2156,59 @@
               Py_DECREF(v);
               goto unauth;
             }
-          
-          i = PyObject_IsTrue(t);
-          if (i < 0) goto err;
-          if (i)
+
+      /*
+        if assertion:
+            if callable(assertion):
+                factory = assertion(name, v)
+                if callable(factory):
+                    return factory(inst, name)
+                assert factory == 1
+            assert callable == 1
+            return v
+
+      */
+          if (PyCallable_Check(t))
             {
-              if (t->ob_type->tp_call)
+              PyObject *factory;
+
+              factory = callfunction2(t, name, v);
+              if (factory == NULL) 
+                goto err;
+
+              if (PyCallable_Check(factory))
                 {
-                  PyObject *ignored;
-                  ignored = callfunction2(t, name, v);
-                  if (ignored == NULL)
-                    {
-                      /* veto */
-                      Py_DECREF(v);
-                      return NULL;
-                    }
-                  Py_DECREF(ignored);
+                  Py_DECREF(v);
+                  v = callfunction2(factory, inst, name);
                 }
-              return v;
+              Py_DECREF(factory);
             }
+          return v;
         }
 
-      /*
-        if validate(inst, inst, name, v):
-            return v
-       */
-      validate=callfunction4(validate, inst, inst, name, v);
-      if (validate==NULL) goto err;
-      i=PyObject_IsTrue(validate);
-      Py_DECREF(validate);
-      if (i < 0) goto err;
-      if (i > 0) return v;
-      
+      /* 
+        # See if we can get the value doing a filtered acquire.
+        # aq_acquire will either return the same value as held by
+        # v or it will return an Unauthorized raised by validate.
+        validate = SecurityManagement.getSecurityManager().validate
+        aq_acquire(inst, name, aq_validate, validate)
+        
+        return v
+      */
+
+      t = aq_Acquire(inst, name, aq_validate, validate, 1, NULL, 0);
+      if (t == NULL)
+        return NULL;
+      Py_DECREF(t);
+
+      return v;
+            
       unauthErr(name, v);
     err:
       Py_DECREF(v);
       return NULL;
     }
+  }
 
  unauth:
   /* raise Unauthorized, name */

Added: Zope/branches/tseaver-five-integration-security/lib/python/AccessControl/tests/testAcquisition.py
===================================================================
--- Zope/branches/five-integration/lib/python/AccessControl/tests/testAcquisition.py	2005-03-27 18:39:17 UTC (rev 29692)
+++ Zope/branches/tseaver-five-integration-security/lib/python/AccessControl/tests/testAcquisition.py	2005-03-28 17:16:28 UTC (rev 29696)
@@ -0,0 +1,252 @@
+##############################################################################
+#
+# Copyright (c) 2001 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
+#
+##############################################################################
+"""Tests demonstrating consequences of guarded_getattr fix from 2004/08/07
+
+   http://mail.zope.org/pipermail/zope-checkins/2004-August/028152.html
+   http://zope.org/Collectors/CMF/259
+
+"""
+
+import unittest
+
+from Testing.makerequest import makerequest
+
+import Zope2
+Zope2.startup()
+
+from OFS.SimpleItem import SimpleItem
+from Globals import InitializeClass
+from AccessControl import ClassSecurityInfo
+from AccessControl.SecurityManagement import newSecurityManager
+from AccessControl.SecurityManagement import noSecurityManager
+from AccessControl.Permissions import view, view_management_screens
+from AccessControl.ImplPython import guarded_getattr as guarded_getattr_py
+from AccessControl.ImplC import guarded_getattr as guarded_getattr_c
+from Products.SiteErrorLog.SiteErrorLog import SiteErrorLog
+
+
+class AllowedItem(SimpleItem):
+    id = 'allowed'
+    security = ClassSecurityInfo()
+    security.setDefaultAccess('allow')
+
+InitializeClass(AllowedItem)
+
+class DeniedItem(SimpleItem):
+    id = 'denied'
+    security = ClassSecurityInfo()
+    security.setDefaultAccess('deny')
+
+InitializeClass(DeniedItem)
+
+class ProtectedItem(SimpleItem):
+    id = 'protected'
+    security = ClassSecurityInfo()
+    security.declareObjectProtected(view_management_screens)
+
+InitializeClass(ProtectedItem)
+
+class ProtectedSiteErrorLog(SiteErrorLog):
+    '''This differs from the base by declaring security
+       for the object itself.
+    '''
+    id = 'error_log2'
+    security = ClassSecurityInfo()
+    security.declareObjectProtected(view)
+
+InitializeClass(ProtectedSiteErrorLog)
+
+
+class TestGetAttr(unittest.TestCase):
+
+    def setUp(self):
+        import transaction
+        self.guarded_getattr = guarded_getattr_py
+        transaction.manager.begin()
+        self.app = makerequest(Zope2.app())
+        try:
+
+            # Set up a manager user
+            self.uf = self.app.acl_users
+            self.uf._doAddUser('manager', 'secret', ['Manager'], [])
+            self.login('manager')
+
+            # Set up objects in the root that we want to aquire
+            self.app.manage_addFolder('plain_folder')
+            self.app._setObject('error_log2', ProtectedSiteErrorLog())
+
+            # We also want to be able to acquire simple attributes
+            self.app.manage_addProperty(id='simple_type', type='string', value='a string')
+
+            # Set up a subfolder and the objects we want to acquire from
+            self.app.manage_addFolder('subfolder')
+            self.folder = self.app.subfolder
+            self.folder._setObject('allowed', AllowedItem())
+            self.folder._setObject('denied', DeniedItem())
+            self.folder._setObject('protected', ProtectedItem())
+
+        except:
+            self.tearDown()
+            raise
+
+    def tearDown(self):
+        import transaction
+        noSecurityManager()
+        transaction.manager.get().abort()
+        self.app._p_jar.close()
+
+    def login(self, name):
+        user = self.uf.getUserById(name)
+        user = user.__of__(self.uf)
+        newSecurityManager(None, user)
+
+    # Acquire plain folder
+
+    def testFolderAllowed(self):
+        o = self.guarded_getattr(self.folder.allowed, 'plain_folder')
+        self.assertEqual(o, self.app.plain_folder)
+
+    def testFolderDenied(self):
+        o = self.guarded_getattr(self.folder.denied, 'plain_folder')
+        self.assertEqual(o, self.app.plain_folder)
+
+    def testFolderProtected(self):
+        o = self.guarded_getattr(self.folder.protected, 'plain_folder')
+        self.assertEqual(o, self.app.plain_folder)
+
+    # Acquire user folder
+
+    def testAclUsersAllowed(self):
+        o = self.guarded_getattr(self.folder.allowed, 'acl_users')
+        self.assertEqual(o, self.app.acl_users)
+
+    def testAclUsersDenied(self):
+        # XXX: Fails in 2.7.3
+        o = self.guarded_getattr(self.folder.denied, 'acl_users')
+        self.assertEqual(o, self.app.acl_users)
+
+    def testAclUsersProtected(self):
+        # XXX: Fails in 2.7.3 for Anonymous
+        o = self.guarded_getattr(self.folder.protected, 'acl_users')
+        self.assertEqual(o, self.app.acl_users)
+
+    # Acquire browser id manager
+
+    def testBrowserIdManagerAllowed(self):
+        o = self.guarded_getattr(self.folder.allowed, 'browser_id_manager')
+        self.assertEqual(o, self.app.browser_id_manager)
+
+    def testBrowserIdManagerDenied(self):
+        o = self.guarded_getattr(self.folder.denied, 'browser_id_manager')
+        self.assertEqual(o, self.app.browser_id_manager)
+
+    def testBrowserIdManagerProtected(self):
+        o = self.guarded_getattr(self.folder.protected, 'browser_id_manager')
+        self.assertEqual(o, self.app.browser_id_manager)
+
+    # Acquire error log
+
+    def testErrorLogAllowed(self):
+        o = self.guarded_getattr(self.folder.allowed, 'error_log')
+        self.assertEqual(o, self.app.error_log)
+
+    def testErrorLogDenied(self):
+        # XXX: Fails in 2.7.3
+        o = self.guarded_getattr(self.folder.denied, 'error_log')
+        self.assertEqual(o, self.app.error_log)
+
+    def testErrorLogProtected(self):
+        # XXX: Fails in 2.7.3 for Anonymous
+        o = self.guarded_getattr(self.folder.protected, 'error_log')
+        self.assertEqual(o, self.app.error_log)
+
+    # Now watch this: error log with object security declaration works fine!
+
+    def testProtectedErrorLogAllowed(self):
+        o = self.guarded_getattr(self.folder.allowed, 'error_log2')
+        self.assertEqual(o, self.app.error_log2)
+
+    def testProtectedErrorLogDenied(self):
+        o = self.guarded_getattr(self.folder.denied, 'error_log2')
+        self.assertEqual(o, self.app.error_log2)
+
+    def testProtectedErrorLogProtected(self):
+        o = self.guarded_getattr(self.folder.protected, 'error_log2')
+        self.assertEqual(o, self.app.error_log2)
+
+    # This appears to mean that any potential acquiree must make sure
+    # to declareObjectProtected(SomePermission).
+
+    # From the ZDG:
+    # We've seen how to make  assertions on methods - but in the case of
+    # someObject we are not trying to access any particular method, but
+    # rather the object itself (to pass it to some_method). Because the
+    # security machinery will try to validate access to someObject, we
+    # need a way to let the security machinery know how to handle access
+    # to the object itself in addition to protecting its methods.
+
+    # IOW, acquiring an object in restricted Python now amounts to
+    # "passing it to some_method".
+
+
+    # Also test Richard Jones' use-case of acquiring a string:
+
+    def testSimpleTypeAllowed(self):
+        o = self.guarded_getattr(self.folder.allowed, 'simple_type')
+        self.assertEqual(o, 'a string')
+
+    def testSimpleTypeDenied(self):
+        # XXX: Fails in 2.7.3
+        o = self.guarded_getattr(self.folder.denied, 'simple_type')
+        self.assertEqual(o, 'a string')
+
+    def testSimpleTypeProtected(self):
+        # XXX: Fails in 2.7.3 for Anonymous
+        o = self.guarded_getattr(self.folder.protected, 'simple_type')
+        self.assertEqual(o, 'a string')
+
+
+class TestGetAttrAnonymous(TestGetAttr):
+
+    # Run all tests again as Anonymous User
+
+    def setUp(self):
+        TestGetAttr.setUp(self)
+        # Log out
+        noSecurityManager()
+
+
+class TestGetAttr_c(TestGetAttr):
+
+    def setUp(self):
+        TestGetAttr.setUp(self)
+        self.guarded_getattr = guarded_getattr_c
+
+class TestGetAttrAnonymous_c(TestGetAttrAnonymous):
+
+    def setUp(self):
+        TestGetAttrAnonymous.setUp(self)
+        self.guarded_getattr = guarded_getattr_c
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(TestGetAttr))
+    suite.addTest(unittest.makeSuite(TestGetAttrAnonymous))
+    suite.addTest(unittest.makeSuite(TestGetAttr_c))
+    suite.addTest(unittest.makeSuite(TestGetAttrAnonymous_c))
+    return suite
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')
+

Modified: Zope/branches/tseaver-five-integration-security/lib/python/Shared/DC/Scripts/Bindings.py
===================================================================
--- Zope/branches/five-integration/lib/python/Shared/DC/Scripts/Bindings.py	2005-03-27 18:39:17 UTC (rev 29692)
+++ Zope/branches/tseaver-five-integration-security/lib/python/Shared/DC/Scripts/Bindings.py	2005-03-28 17:16:28 UTC (rev 29696)
@@ -15,6 +15,7 @@
 
 import Globals
 from AccessControl import getSecurityManager
+from AccessControl.PermissionRole import _what_not_even_god_should_do
 from AccessControl.ZopeGuards import guarded_getattr
 from Persistence import Persistent
 from string import join, strip
@@ -167,15 +168,19 @@
         self._wrapped = wrapped
 
     __allow_access_to_unprotected_subobjects__ = 1
+    __roles__ = _what_not_even_god_should_do
 
+    def __repr__(self):
+        return '<UnauthorizedBinding: %s>' % self._name
+
     def __getattr__(self, name, default=None):
-
         # Make *extra* sure that the wrapper isn't used to access
-        # __call__, __str__, __repr__, etc.
+        # __call__, etc.
         if name.startswith('__'):
             self.__you_lose()
 
         return guarded_getattr(self._wrapped, name, default)
+        #return getattr(self._wrapped, name, default)
 
     def __you_lose(self):
         name = self.__dict__['_name']



More information about the Zope-Checkins mailing list