[Zope-Checkins] CVS: Zope/lib/python/AccessControl - SimpleObjectPolicies.py:1.12.6.1 ZopeGuards.py:1.12.4.3 ZopeSecurityPolicy.py:1.20.4.2 __init__.py:1.15.6.1 cAccessControl.c:1.17.6.2

Tres Seaver tseaver at zope.com
Thu Jan 8 15:12:38 EST 2004


Update of /cvs-repository/Zope/lib/python/AccessControl
In directory cvs.zope.org:/tmp/cvs-serv29583/lib/python/AccessControl

Modified Files:
      Tag: Zope-2_6-branch
	SimpleObjectPolicies.py ZopeGuards.py ZopeSecurityPolicy.py 
	__init__.py cAccessControl.c 
Log Message:


  - Enforce new restrictions on untrusted code, identified during
    the December 2003 security audit.  These issues affect sites
    that allow untrusted users to write Python Scripts, Page Templates,
    and DTML:

    o Iteration over sequences could in some cases fail to check access 
      to an object obtained from the sequence. Subsequent checks (such 
      as for attributes access) of such an object would still be 
      performed, but it should not have been possible to obtain the 
      object in the first place.

    o List and dictionary instance methods such as the get method of 
      dictionary objects were not security aware and could return an 
      object without checking access to that object. Subsequent checks 
      (such as for attributes access) of such an object would still be 
      performed, but it should not have been possible to obtain the 
      object in the first place.

    o Use of 'import as. in Python scripts could potentially rebind 
      names in ways that could be used to avoid appropriate security 
      checks.

    o A number of newer built-ins (min, max, enumerate, iter, sum)
      were either unavailable in untrusted code or did not perform
      adequate security checking.

    o Unpacking via function calls, variable assignment, exception 
      variables and other contexts did not perform adequate security 
      checks, potentially allowing access to objects that should have 
      been protected.

    o DTMLMethods with proxy rights could incorrectly transfer those 
      rights via acquisition when traversing to a parent object.



=== Zope/lib/python/AccessControl/SimpleObjectPolicies.py 1.12 => 1.12.6.1 ===
--- Zope/lib/python/AccessControl/SimpleObjectPolicies.py:1.12	Wed Aug 14 17:29:07 2002
+++ Zope/lib/python/AccessControl/SimpleObjectPolicies.py	Thu Jan  8 15:12:07 2004
@@ -10,10 +10,43 @@
 # FOR A PARTICULAR PURPOSE
 #
 ##############################################################################
-__doc__='''Collect rules for access to objects that don\'t have roles.
+"""Collect rules for access to objects that don\'t have roles.
 
-$Id$'''
-__version__='$Revision$'[11:-2]
+An assertion can be:
+
+  - A dict
+
+  - A callable
+
+  - Something with a truth value
+
+If the assertion is a callable, then it will be called with
+a name being accessed and the name used.  Its return value is ignored,
+but in may veto an access by raising an exception.
+
+N.B.:  ExtensionClass instances *always* look callable;  hence, unless you
+       really designed your EC-derived instance to be called to perform
+       checked access to a container's method, *don't* use it as a
+       container assertion.
+
+If the assertion is a dictionary, then the keys are attribute names.
+The values may be callables or objects with boolean values. If a value
+is callable, it will be called with the object we are accessing an
+attribute of and the attribute name. It should return an attribute
+value. Callables are often used to returned guarded versions of
+methods.  Otherwise, accesses are allowed if values in this dictionary
+are true and disallowed if the values are false or if an item for an
+attribute name is not present.
+
+If the assertion is not a dict and is not callable, then access to
+unprotected attributes is allowed if the assertion is true, and
+disallowed otherwise.
+
+XXX This descrition doesn't actually match what's done in ZopeGuards
+or in ZopeSecurityPolicy. :(
+
+$Id$
+"""
 
 _noroles=[] # this is imported from various places
 
@@ -24,8 +57,6 @@
 
 ContainerAssertions={
     type(()): 1,
-    type([]): 1,
-    type({}): 1,
     type(''): 1,
     type(u''): 1,
     }


=== Zope/lib/python/AccessControl/ZopeGuards.py 1.12.4.2 => 1.12.4.3 ===
--- Zope/lib/python/AccessControl/ZopeGuards.py:1.12.4.2	Tue Dec 17 15:39:13 2002
+++ Zope/lib/python/AccessControl/ZopeGuards.py	Thu Jan  8 15:12:07 2004
@@ -10,15 +10,19 @@
 # FOR A PARTICULAR PURPOSE
 #
 ##############################################################################
-
+from __future__ import nested_scopes
+  
 __version__='$Revision$'[11:-2]
+  
+import sys
+from types import DictType, ListType, StringType, SliceType
 
-from RestrictedPython.Guards import safe_builtins, _full_read_guard, \
-     full_write_guard
+import RestrictedPython
+from RestrictedPython.Guards import safe_builtins, full_write_guard
 from RestrictedPython.Utilities import utility_builtins
 from SecurityManagement import getSecurityManager
 from SecurityInfo import secureModule
-from SimpleObjectPolicies import Containers
+from SimpleObjectPolicies import Containers, ContainerAssertions
 from zExceptions import Unauthorized
 
 _marker = []  # Create a new marker object.
@@ -49,9 +53,29 @@
                 if default is not _marker:
                     return default
                 raise
-            if Containers(type(inst)):
-                # Simple type.  Short circuit.
+
+            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
+                
             validate = getSecurityManager().validate
             # Filter out the objects we can't access.
             if hasattr(inst, 'aq_acquire'):
@@ -71,7 +95,6 @@
     return 1
 safe_builtins['hasattr'] = guarded_hasattr
 
-SliceType = type(slice(0))
 def guarded_getitem(object, index):
     if type(index) is SliceType:
         if index.step is not None:
@@ -91,16 +114,126 @@
     if Containers(type(object)) and Containers(type(v)):
         # Simple type.  Short circuit.
         return v
-    if getSecurityManager().validate(object, object, index, v):
+    if getSecurityManager().validate(object, object, None, v):
         return v
     raise Unauthorized, 'unauthorized access to element %s' % `i`
 
+def get_dict_get(d, name):
+    def guarded_get(key, default=None):
+        try:
+            return guarded_getitem(d, key)
+        except KeyError:
+            return default
+    return guarded_get
+
+def get_dict_pop(d, name):
+    def guarded_pop(key, default=_marker):
+        try:
+            v = guarded_getitem(d, key)
+        except KeyError:
+            if default is not _marker:
+                return default
+            raise
+        else:
+            del d[key]
+            return v        
+    return guarded_pop
+
+def get_iter(c, name):
+    iter = getattr(c, name)
+    def guarded_iter():
+        return SafeIter(iter(), c)
+    return guarded_iter
+
+def get_list_pop(lst, name):
+    def guarded_pop(index=-1):
+        # XXX This is not thread safe, but we don't expect
+        # XXX thread interactions between python scripts <wink>
+        v = guarded_getitem(lst, index)
+        del lst[index]
+        return v        
+    return guarded_pop
+
+ContainerAssertions[DictType] = {
+    'clear':1, 'copy':1, 'fromkeys':1, 'get':get_dict_get, 'has_key':1,
+    'items':1, 'iteritems':1, 'keys':1, 
+    'iterkeys': get_iter,  'itervalues':get_iter,
+    'pop':get_dict_pop, 'popitem':1, 'setdefault':1, 'update':1, 'values':1}
+
+ContainerAssertions[ListType] = {
+    'append':1, 'count':1, 'extend':1, 'index':1, 'insert':1, 
+    'pop':get_list_pop, 'remove':1, 'reverse':1, 'sort':1}
+
+# This implementation of a "safe" iterator uses a global guard()
+# function to implement the actual guard.  This check is defined as a
+# global so that it can delay imports of some module to avoid circular
+# dependencies while also making it possible to use a faster
+# implementation once the imports are done (by avoiding the import
+# machinery on subsequent calls).  Use of a method on the SafeIter
+# class is avoided to ensure the best performance of the resulting
+# function.
+
+
+if sys.version_info < (2, 2):
+
+    class SafeIter:
+        def __init__(self, sequence, container=None):
+            self.sequence = sequence
+            if container is None:
+                container = sequence
+            self.container = container
+            self.next_index = 0
+
+        def __getitem__(self, index):
+            ob = self.sequence[self.next_index]
+            self.next_index += 1
+            guard(self.container, ob, self.next_index - 1)
+            return ob
+            
+        def __len__(self):
+            return len(self.sequence)
+
+    def _error(index):
+        raise Unauthorized, 'unauthorized access to element %s' % `index`
+
+else:
+    class SafeIter(object):
+        #__slots__ = '_next', 'container'
+
+        def __init__(self, ob, container=None):
+            self._next = iter(ob).next
+            if container is None:
+                container = ob
+            self.container = container
+
+        def __iter__(self):
+            return self
+
+        def next(self):
+            ob = self._next()
+            guard(self.container, ob)
+            return ob
+
+    def _error(index):
+        raise Unauthorized, 'unauthorized access to element'
+
+    safe_builtins['iter'] = SafeIter
 
-full_read_guard = _full_read_guard(guarded_getattr, guarded_getitem)
+
+def guard(container, value, index=None):
+    if Containers(type(container)) and Containers(type(value)):
+        # Simple type.  Short circuit.
+        return
+    if getSecurityManager().validate(container, container, index, value):
+        return
+    _error(index)
+
+
+# More replacement built-ins.
 
 
 def guarded_filter(f, seq, skip_unauthorized=0):
-    if type(seq) is type(''):
+    if type(seq) is StringType:
         return filter(f, seq)
     if f is None:
         def f(x): return x
@@ -115,6 +248,27 @@
     return result
 safe_builtins['filter'] = guarded_filter
 
+def guarded_reduce(f, seq, initial=_marker):
+    if initial is _marker:
+        return reduce(f, SafeIter(seq))
+    else:
+        return reduce(f, SafeIter(seq), initial)
+safe_builtins['reduce'] = guarded_reduce
+
+def guarded_max(item, *items):
+    if items:
+        item = [item]
+        item.extend(items)
+    return max(SafeIter(item))
+safe_builtins['max'] = guarded_max
+
+def guarded_min(item, *items):
+    if items:
+        item = [item]
+        item.extend(items)
+    return min(SafeIter(item))
+safe_builtins['min'] = guarded_min
+
 def guarded_map(f, *seqs):
     safe_seqs = []
     for seqno in range(len(seqs)):
@@ -123,7 +277,6 @@
     return map(f, *safe_seqs)
 safe_builtins['map'] = guarded_map
 
-import sys
 def guarded_import(mname, globals={}, locals={}, fromlist=None):
     mnameparts = mname.split('.')
     firstmname = mnameparts[0]
@@ -151,6 +304,29 @@
     raise ImportError, 'import of "%s" is unauthorized' % mname
 safe_builtins['__import__'] = guarded_import
 
+if sys.version_info >= (2, 2):
+    class GuardedListType:
+        def __call__(self, *args, **kwargs):
+            return list(*args)
+
+        if sys.version_info >= (2, 4):
+            def sorted(self, iterable, cmp=None, key=None, reverse=False):
+                return list.sorted(iterable, cmp=None, key=None, reverse=False)
+    safe_builtins['list'] = GuardedListType()
+
+    class GuardedDictType:
+        def __call__(self, *args, **kwargs):
+            return dict(*args, **kwargs)
+
+        if sys.version_info >= (2, 3):
+            # dict.fromkeys() was introduced in 2.3.
+            def fromkeys(self, S, v=None):
+                return dict.fromkeys(S,v)
+    safe_builtins['dict'] = GuardedDictType()
+else:
+    # In python 2.1 or before, list is not a type (there was no dict)
+    safe_builtins['list'] = list
+    
 def load_module(module, mname, mnameparts, validate, globals, locals):
     modules = sys.modules
     while mnameparts:
@@ -170,3 +346,66 @@
             return
         module = nextmodule
     return module
+
+# This version of apply is used by restricted Python, which transforms
+# extended call syntax into a call of _apply_(), after tucking the callable
+# into the first element of args.  For example,
+#     f(3, eggs=1, spam=False)
+# is changed to
+#     _apply_(f, 3, eggs=1, spam=False)
+def guarded_apply(func, *args, **kws):
+    return builtin_guarded_apply(func, args, kws)
+
+# This version is the safe_builtins apply() replacement, so needs to match the
+# signature of __builtin__.apply.
+def builtin_guarded_apply(func, args=(), kws={}):
+    # Check the args elements.  args may be an arbitrary iterable, and
+    # iterating over it may consume it, so we also need to save away
+    # the arguments in a new list to pass on to the real apply().
+    i, arglist = 0, []
+    for elt in args:
+        guard(args, elt, i)
+        arglist.append(elt)
+        i += 1
+    # Check kws similarly.  Checking the keys may not be strictly necessary,
+    # but better safe than sorry.  A new argument dict is created just in
+    # case kws is a hostile user-defined instance that may do horrid things
+    # as a side-effect of calling items().
+    argdict = {}
+    for k, v in kws.items():
+        guard(kws, k)
+        guard(kws, v, k)
+        argdict[k] = v
+    return func(*arglist, **argdict)
+
+safe_builtins['apply'] = builtin_guarded_apply
+
+
+# AccessControl clients generally need to set up a safe globals dict for
+# use by restricted code.  The get_safe_globals() function returns such
+# a dict, containing '__builtins__' mapped to our safe bulitins, and
+# bindings for all the special functions inserted into Python code by
+# RestrictionMutator transformations.  A client may wish to add more
+# bindings to this dict.  It's generally safe to do so, as
+# get_safe_globals returns a (shallow) copy of a canonical safe globals
+# dict.
+# Exception:  For obscure technical reasons, clients have to import
+# guarded_getattr from this module (ZopeGuards) and plug it into the
+# dict themselves, with key '_getattr_'.
+
+_safe_globals = {'__builtins__': safe_builtins,
+                 '_apply_':      guarded_apply,
+                 '_getitem_':    guarded_getitem,
+                 '_getiter_':    SafeIter,
+                 '_print_':      RestrictedPython.PrintCollector,
+                 '_write_':      full_write_guard,
+                 # The correct implementation of _getattr_, aka
+                 # guarded_getattr, isn't known until
+                 # AccessControl.Implementation figures that out, then
+                 # stuffs it into *this* module's globals bound to
+                 # 'guarded_getattr'.  We can't know what that is at
+                 ## '_getattr_':   guarded_getattr,
+                }
+
+get_safe_globals = _safe_globals.copy
+


=== Zope/lib/python/AccessControl/ZopeSecurityPolicy.py 1.20.4.1 => 1.20.4.2 ===
--- Zope/lib/python/AccessControl/ZopeSecurityPolicy.py:1.20.4.1	Wed Nov 19 07:23:23 2003
+++ Zope/lib/python/AccessControl/ZopeSecurityPolicy.py	Thu Jan  8 15:12:07 2004
@@ -140,7 +140,11 @@
                     tp=type(p)
                     if tp is not IntType:
                         if tp is DictType:
-                            p=p.get(name, None)
+                            if (isinstance(name, StringType) or
+                                isinstance(name, UnicodeType)):
+                                p=p.get(name, None)
+                            else:
+                                p = 1
                         else:
                             p=p(name, value)
 
@@ -184,6 +188,23 @@
                 # Proxy roles, which are a lot safer now.
                 proxy_roles=getattr(eo, '_proxy_roles', None)
                 if proxy_roles:
+                    # Verify that the owner actually can state the proxy role
+                    # in the context of the accessed item; users in subfolders
+                    # should not be able to use proxy roles to access items 
+                    # above their subfolder!
+                    owner = eo.getOwner()
+                    # Sigh; the default userfolder doesn't return users wrapped
+                    if owner and not hasattr(owner, 'aq_parent'):
+                        udb=eo.getOwner(1)[0]
+                        root=container.getPhysicalRoot()
+                        udb=root.unrestrictedTraverse(udb)
+                        owner=owner.__of__(udb)
+                        
+                    if owner is not None:
+                        if not owner._check_context(container):
+                            # container is higher up than the owner, deny access
+                            raise Unauthorized(name, value)
+
                     for r in proxy_roles:
                         if r in roles: return 1
 


=== Zope/lib/python/AccessControl/__init__.py 1.15 => 1.15.6.1 ===
--- Zope/lib/python/AccessControl/__init__.py:1.15	Wed Aug 14 17:29:07 2002
+++ Zope/lib/python/AccessControl/__init__.py	Thu Jan  8 15:12:07 2004
@@ -23,6 +23,6 @@
 from SecurityInfo import ACCESS_NONE
 from SecurityInfo import secureModule, allow_module, allow_class
 from SimpleObjectPolicies import allow_type
-from ZopeGuards import full_read_guard, full_write_guard, safe_builtins
+from ZopeGuards import full_write_guard, safe_builtins
 
 ModuleSecurityInfo('AccessControl').declarePublic('getSecurityManager')


=== Zope/lib/python/AccessControl/cAccessControl.c 1.17.6.1 => 1.17.6.2 ===
--- Zope/lib/python/AccessControl/cAccessControl.c:1.17.6.1	Thu Sep 11 11:55:57 2003
+++ Zope/lib/python/AccessControl/cAccessControl.c	Thu Jan  8 15:12:07 2004
@@ -637,6 +637,7 @@
 */
 
 static PyObject *Containers = NULL;
+static PyObject *ContainerAssertions = NULL;
 static PyObject *Unauthorized = NULL;
 static PyObject *LOG = NULL;
 static PyObject *PROBLEM = NULL;
@@ -655,9 +656,14 @@
 static PyObject *_proxy_roles_str = NULL;
 static PyObject *allowed_str = NULL;
 static PyObject *getOwner_str = NULL;
+static PyObject *getPhysicalRoot_str = NULL;
 static PyObject *checkPermission_str = NULL;
 static PyObject *getSecurityManager = NULL;
+static PyObject *unrestrictedTraverse_str = NULL;
 static PyObject *aq_validate = NULL;
+static PyObject *aq_parent_str = NULL;
+static PyObject *_check_context_str = NULL;
+
 static int ownerous = 1;
 static int authenticated = 1;
 
@@ -692,6 +698,15 @@
           return -1;
 	UNLESS (allowed_str = PyString_FromString("allowed")) return -1;
 	UNLESS (getOwner_str = PyString_FromString("getOwner")) return -1;
+	UNLESS (getPhysicalRoot_str = PyString_FromString("getPhysicalRoot")) 
+	  return -1;
+	UNLESS (aq_parent_str = PyString_FromString("aq_parent")) return -1;
+	UNLESS (_check_context_str = PyString_FromString("_check_context")) 
+	  return -1;
+	UNLESS (unrestrictedTraverse_str = PyString_FromString(
+					   "unrestrictedTraverse")) 
+	  return -1;
+
 	UNLESS (checkPermission_str = PyString_FromString("checkPermission")) 
           return -1;
         UNLESS (__allow_access_to_unprotected_subobjects__ = 
@@ -743,8 +758,18 @@
 	PyObject *rval = NULL;
 	PyObject *stack = NULL;
 	PyObject *user = NULL;
+
+
+	PyObject *method = NULL;
+	PyObject *tmp = NULL;
+	PyObject *udb = NULL;
+	PyObject *root = NULL;
+	PyObject *item = NULL;
+
 	char *sname;
 
+	int i, l, contains;
+	PyObject *r;
 
 	/*| def validate(self, accessed, container, name, value, context
 	**|	roles=_noroles ...
@@ -891,24 +916,38 @@
 		**|    tp = type(p)
 		**|    if tp is not IntType:
 		**|       if tp is DictType:
-		**|          p = p.get(name, None)
+                **|         if (isinstance(name, StringType) or
+                **|             isinstance(name, UnicodeType)):
+                **|             p=p.get(name, None)
+                **|         else:
+                **|             p = 1
 		**|       else:
 		**|          p = p(name, value)
 		*/
 
-		if (p) {
-			if (! PyInt_Check(p)) {
-				if (PyDict_Check(p)) {
-                                        ASSIGN(p, PyObject_GetItem(p, name));
-                                        if (p == NULL)
-                                          PyErr_Clear();
-				} else {
-                                  ASSIGN(p, callfunction2(p, name, value));
-                                  if (p == NULL)
-                                    goto err;
-				}
-			}
-		}
+		if (p) 
+                  {
+                    if (! PyInt_Check(p)) 
+                      {
+                        if (PyDict_Check(p)) 
+                          {
+                            if (PyString_Check(name) || PyUnicode_Check(name))
+                              {
+                                ASSIGN(p, PyObject_GetItem(p, name));
+                                if (p == NULL)
+                                  PyErr_Clear();
+                              }
+                            else
+                              p = PyInt_FromLong(1);
+                          } 
+                        else 
+                          {
+                            ASSIGN(p, callfunction2(p, name, value));
+                            if (p == NULL)
+                              goto err;
+                          }
+                      }
+                  }
 
 		/*| if not p:
 		**|    if containerbase is accessedbase:
@@ -1056,6 +1095,24 @@
 	/*|    # Proxy roles, which are a lot safer now
 	**|    proxy_roles = getattr(eo, "_proxy_roles", None)
 	**|    if proxy_roles:
+	**|        # Verify that the owner actually can state the proxy role
+	**|        # in the context of the accessed item; users in subfolders
+	**|        # should not be able to use proxy roles to access items 
+	**|        # above their subfolder!
+	**|        owner = eo.getOwner()
+	**|        # Sigh; the default userfolder doesn't return users wrapped
+	**|        if owner and not hasattr(owner, 'aq_parent'):
+	**|            udb=eo.getOwner(1)[0]
+	**|            root=container.getPhysicalRoot()
+	**|            udb=root.unrestrictedTraverse(udb)
+	**|            owner=owner.__of__(udb)
+	**|                        
+	**|        if owner is not None:
+	**|            if not owner._check_context(container):
+	**|                # container is higher up than the owner, deny
+	**|                # access
+	**|                raise Unauthorized(name, value)
+	**|
 	**|       for r in proxy_roles:
 	**|          if r in roles: return 1
 	**|
@@ -1074,8 +1131,82 @@
                   }
                 else if (PyObject_IsTrue(proxy_roles)) 
                   {
-                    int i, l, contains=0;
-                    PyObject *r;
+
+		    /* patch!! --------------------------------  */
+
+		    method = PyObject_GetAttr(eo, getOwner_str);
+		    if (method == NULL) {
+		      goto err;
+		    }
+
+		    owner = PyObject_CallObject(method, NULL);
+		    Py_DECREF(method);
+
+		    if (owner == NULL) {
+		      goto err;
+		    }
+
+
+		    if (PyObject_IsTrue(owner)) {
+		      if (!PyObject_HasAttr(owner, aq_parent_str)) {
+			item = PyInt_FromLong(1);
+		        tmp = callmethod1(eo, getOwner_str, item);
+			Py_XDECREF(item);
+			udb = PySequence_GetItem(tmp, 0);
+			Py_XDECREF(tmp);
+			if (udb == NULL) {
+			  Py_DECREF(owner);
+			  goto err;
+			}
+
+			method = PyObject_GetAttr(container, 
+						  getPhysicalRoot_str);
+			if (method == NULL) {
+			  Py_DECREF(owner);
+			  Py_DECREF(udb);
+			}
+			root = PyObject_CallObject(method, NULL);
+			Py_DECREF(method);
+			
+			ASSIGN(udb, callmethod1(root, unrestrictedTraverse_str,
+						udb)); 
+			if (udb == NULL) {
+			  Py_DECREF(owner);
+			  Py_DECREF(udb);
+			}
+
+			ASSIGN(owner, callmethod1(owner, __of__, udb));
+			Py_DECREF(udb);
+
+
+		      }
+		    }
+
+
+		    if (owner != Py_None) {
+		      PyObject *tmp = callmethod1(owner, 
+						  _check_context_str,
+						  container
+						  );
+		      if (tmp == NULL) {
+			Py_DECREF(owner);
+			goto err;
+		      }
+
+		      if (!PyObject_IsTrue(tmp)) {
+			Py_DECREF(owner);
+			Py_DECREF(tmp);
+			unauthErr(name, value);
+			goto err;
+		      }
+		      Py_DECREF(owner);
+		    }
+		    		    
+		    /* ------------------------------------------- */
+
+
+
+                    contains = 0;
                     if (PyTuple_Check(proxy_roles)) 
                       {
                         l=PyTuple_GET_SIZE(proxy_roles);
@@ -1956,16 +2087,74 @@
         }
 
       /*
-        if Containers(type(inst)):
-            # Simple type.  Short circuit.
-            return v
+            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
       */
-      t=callfunction1(Containers, OBJECT(inst->ob_type));
-      if (t==NULL) goto err;
-      i=PyObject_IsTrue(t);
-      if (i < 0) goto err;
-      Py_DECREF(t);
-      if (i) return v;
+      t = PyDict_GetItem(ContainerAssertions, OBJECT(inst->ob_type));
+      if (t != NULL)
+        {
+          if (PyDict_Check(t))
+            {
+              PyObject *attrv;
+              
+              attrv = PyDict_GetItem(t, name);
+              if (attrv != NULL)
+                {
+                  i=PyObject_IsTrue(attrv);
+                  if (i < 0) goto err;
+                  if (i) 
+                    {
+                      if (PyCallable_Check(attrv))
+                        {
+                          Py_DECREF(v);
+                          v = callfunction2(attrv, inst, name);
+                          return v;
+                        }
+                      return v;
+                    }
+                }
+              Py_DECREF(v);
+              goto unauth;
+            }
+          
+          i = PyObject_IsTrue(t);
+          if (i < 0) goto err;
+          if (i)
+            {
+              if (t->ob_type->tp_call)
+                {
+                  PyObject *ignored;
+                  ignored = callfunction2(t, name, v);
+                  if (ignored == NULL)
+                    {
+                      /* veto */
+                      Py_DECREF(v);
+                      return NULL;
+                    }
+                  Py_DECREF(ignored);
+                }
+              return v;
+            }
+        }
 
       /*
         # Filter out the objects we can't access.
@@ -1996,6 +2185,7 @@
       return NULL;
     }
 
+ unauth:
   /* raise Unauthorized, name */
   PyErr_SetObject(Unauthorized, name);
   return NULL;
@@ -2133,6 +2323,7 @@
 
 	IMPORT(module, "AccessControl.SimpleObjectPolicies");
 	GETATTR(module, Containers);
+	GETATTR(module, ContainerAssertions);
 	Py_DECREF(module);
 	module = NULL;
 




More information about the Zope-Checkins mailing list