From 9cfe4e1c0d0a7219a4f9c32eb2a2ea7588e6278f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9rome=20Perrin?= <jerome@nexedi.com>
Date: Wed, 1 Apr 2020 09:35:23 +0200
Subject: [PATCH] patches/Restricted: Fix the way we allow classes

make our type checker falseish, so that ZopeGuard security checks don't
short circuit this.
---
 product/ERP5Type/patches/Restricted.py | 60 +++++++++++++++-----------
 1 file changed, 36 insertions(+), 24 deletions(-)

diff --git a/product/ERP5Type/patches/Restricted.py b/product/ERP5Type/patches/Restricted.py
index 7074b64c6f..44b808f81d 100644
--- a/product/ERP5Type/patches/Restricted.py
+++ b/product/ERP5Type/patches/Restricted.py
@@ -81,36 +81,48 @@ def allow_class_attribute(klass, access=1):
   assert(inspect.isclass(klass))
   _safe_class_attribute_dict[klass] = access
 
-def _check_type_access(name, v):
+
+class TypeAccessChecker:
+  """Check Access for class instances (whose type() is `type`).
   """
-    Create a method which checks the access if the context type is <type 'type'>s.
+  def __call__(self, name, v):
+    """
+    Create a callable which checks the access if the context type is <type 'type'>s.
     Since the 'type' can be any types of classes, we support the three ways
     defined in AccessControl/SimpleObjectPolicies.  We implement this
     as "a method which returing a method" because we can not know what is the
     type until it is actually called. So the three ways are simulated the
-    returning method inide this method.
-  """
-  def factory(inst, name):
+    function returned by this method.
     """
-     Check function used with ContainerAssetions checked by cAccessControl.
-    """
-    access = _safe_class_attribute_dict.get(inst, 0)
-    # The next 'dict' only checks the access configuration type
-    if access == 1 or (isinstance(access, dict) and access.get(name, 0) == 1):
-      pass
-    elif isinstance(access, dict) and callable(access.get(name, 0)):
-      guarded_method = access.get(name)
-      return guarded_method(inst, name)
-    elif callable(access):
-      # Only check whether the access configuration raise error or not
-      access(inst, name)
-    else:
-      # fallback to default security
-      aq_acquire(inst, name, aq_validate, getSecurityManager().validate)
-    return v
-  return factory
-
-ContainerAssertions[type] = _check_type_access
+    def factory(inst, name):
+      """
+      Check function used with ContainerAssertions checked by cAccessControl.
+      """
+      access = _safe_class_attribute_dict.get(inst, 0)
+      # The next 'dict' only checks the access configuration type
+      if access == 1 or (isinstance(access, dict) and access.get(name, 0) == 1):
+        pass
+      elif isinstance(access, dict) and callable(access.get(name, 0)):
+        guarded_method = access.get(name)
+        return guarded_method(inst, name)
+      elif callable(access):
+        # Only check whether the access configuration raise error or not
+        access(inst, name)
+      else:
+        # fallback to default security
+        aq_acquire(inst, name, aq_validate, getSecurityManager().validate)
+      return v
+    return factory
+
+  def __nonzero__(self):
+    # If Containers(type(x)) is true, ZopeGuard checks will short circuit,
+    # thinking it's a simple type, but we don't want this for type, because
+    # type(x) is type for classes, being trueish would skip security check on
+    # classes.
+    return False
+
+ContainerAssertions[type] = TypeAccessChecker()
+
 
 class SafeIterItems(SafeIter):
 
-- 
2.30.9