From de2001ff772cde5fa338d77a20b33163a3b04f5c Mon Sep 17 00:00:00 2001
From: Julien Muchembled <jm@nexedi.com>
Date: Wed, 23 Feb 2011 16:24:43 +0000
Subject: [PATCH] Teach restricted Python about new language features

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@43622 20353a03-c40f-0410-a6d1-a30d3c3de9de
---
 product/ERP5Type/JSON.py               |  16 +--
 product/ERP5Type/JSONEncoder.py        |   2 +-
 product/ERP5Type/Utils.py              |  21 +--
 product/ERP5Type/ZopePatch.py          |   1 +
 product/ERP5Type/collections.py        |  15 +--
 product/ERP5Type/patches/Restricted.py | 180 +++++++++++++++++++++++++
 product/ERP5Type/patches/python.py     |  25 +++-
 7 files changed, 226 insertions(+), 34 deletions(-)
 create mode 100644 product/ERP5Type/patches/Restricted.py

diff --git a/product/ERP5Type/JSON.py b/product/ERP5Type/JSON.py
index c144d654cf..507769f17d 100644
--- a/product/ERP5Type/JSON.py
+++ b/product/ERP5Type/JSON.py
@@ -29,15 +29,9 @@
 
 """A wrapper module for simplejson or json."""
 
-try:
-  from simplejson import dumps, loads
-except ImportError:
-  try:
-    from json import dumps, loads
-  except ImportError:
-    def dumps(*args, **kw):
-      raise RuntimeError('You must install simplejson to use Products.ERP5Type.JSON.dumps.')
-    def loads(*args, **kw):
-      raise RuntimeError('You must install simplejson to use Products.ERP5Type.JSON.loads.')
-
+from Products.ERP5Type.Utils import deprecated
+import json
 
+deprecated = deprecated("%r is deprecated; use 'json' instead." % __name__)
+dumps = deprecated(json.dumps)
+loads = deprecated(json.loads)
diff --git a/product/ERP5Type/JSONEncoder.py b/product/ERP5Type/JSONEncoder.py
index 33f1ec92f0..8bb9e75c38 100644
--- a/product/ERP5Type/JSONEncoder.py
+++ b/product/ERP5Type/JSONEncoder.py
@@ -400,6 +400,6 @@ __all__ = ['JSONEncoder']
 
 def encodeInJson(o):
   from warnings import warn
-  warn('Products.ERP5Type.JSONEncoder.encodeInJson is deprecated; use Products.ERP5Type.JSON.dumps instead.',
+  warn('Products.ERP5Type.JSONEncoder.encodeInJson is deprecated; use json.dumps instead.',
        DeprecationWarning)
   return JSONEncoder().encode(o) 
diff --git a/product/ERP5Type/Utils.py b/product/ERP5Type/Utils.py
index 2e3a0d32d9..afb8ff8bff 100644
--- a/product/ERP5Type/Utils.py
+++ b/product/ERP5Type/Utils.py
@@ -216,14 +216,19 @@ def _showwarning(message, category, filename, lineno, file=None, line=None):
     file.write(warnings.formatwarning(message, category, filename, lineno))
 warnings.showwarning = _showwarning
 
-@simple_decorator
-def deprecated(wrapped):
-  message = "Use of '%s' function (%s, line %s) is deprecated." % (
-    wrapped.__name__, wrapped.__module__, wrapped.func_code.co_firstlineno)
-  def wrapper(*args, **kw):
-    warnings.warn(message, DeprecationWarning, 2)
-    return wrapped(*args, **kw)
-  return wrapper
+def deprecated(message=''):
+  @simple_decorator
+  def _deprecated(wrapped):
+    m = message or "Use of '%s' function (%s, line %s) is deprecated." % (
+      wrapped.__name__, wrapped.__module__, wrapped.func_code.co_firstlineno)
+    def deprecated(*args, **kw):
+      warnings.warn(m, DeprecationWarning, 2)
+      return wrapped(*args, **kw)
+    return deprecated
+  if callable(message):
+    m, message = message, ''
+    return _deprecated(m)
+  return _deprecated
 
 #####################################################
 # Useful methods
diff --git a/product/ERP5Type/ZopePatch.py b/product/ERP5Type/ZopePatch.py
index 7883c64845..9ccfa0a8a2 100644
--- a/product/ERP5Type/ZopePatch.py
+++ b/product/ERP5Type/ZopePatch.py
@@ -21,6 +21,7 @@
 ##############################################################################
 
 # Load all monkey patches
+from Products.ERP5Type.patches import Restricted
 from Products.ERP5Type.patches import m2crypto
 from Products.ERP5Type.patches import ObjectManager
 from Products.ERP5Type.patches import PropertyManager
diff --git a/product/ERP5Type/collections.py b/product/ERP5Type/collections.py
index 30458d3f1c..bc7c5ce791 100644
--- a/product/ERP5Type/collections.py
+++ b/product/ERP5Type/collections.py
@@ -26,17 +26,6 @@
 #
 ##############################################################################
 
-import collections
-try:
-  from collections import OrderedDict
-except ImportError:
-  try:
-    from ordereddict import OrderedDict
-    collections.OrderedDict = OrderedDict
-  except ImportError:
-    OrderedDict = None
+# XXX deprecated; use 'collections' instead, even on Python < 2.7
 
-if OrderedDict is not None and \
-    getattr(OrderedDict, '__allow_access_to_unprotected_subobjects__',
-            None) is None:
-  OrderedDict.__allow_access_to_unprotected_subobjects__ = 1
+from collections import OrderedDict
diff --git a/product/ERP5Type/patches/Restricted.py b/product/ERP5Type/patches/Restricted.py
new file mode 100644
index 0000000000..76e32d2fb1
--- /dev/null
+++ b/product/ERP5Type/patches/Restricted.py
@@ -0,0 +1,180 @@
+#############################################################################
+#
+# 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
+#
+##############################################################################
+
+import sys
+
+from RestrictedPython.RestrictionMutator import RestrictionMutator
+
+# Unsafe attributes on protected objects are already disallowed at execution
+# and we don't want to maintain a duplicated list of exceptions.
+RestrictionMutator.checkName = RestrictionMutator.checkAttrName = \
+    lambda *args, **kw: None
+
+
+from Acquisition import aq_acquire
+from AccessControl import getSecurityManager
+from AccessControl.ZopeGuards import (safe_builtins, _marker, Unauthorized,
+    aq_validate, guard, guarded_getattr, guarded_iter, SafeIter, NullIter,
+    ContainerAssertions, GuardedDictType, _dict_white_list)
+
+# TODO: add buffer/bytearray
+
+def add_builtins(**kw):
+    assert not set(safe_builtins).intersection(kw)
+    safe_builtins.update(kw)
+
+del safe_builtins['dict']
+del safe_builtins['list']
+add_builtins(Ellipsis=Ellipsis, NotImplemented=NotImplemented,
+             dict=dict, list=list, set=set, frozenset=frozenset)
+
+add_builtins(classmethod=classmethod, object=object, property=property,
+             slice=slice, staticmethod=staticmethod, super=super, type=type)
+
+if sys.version_info >= (2, 6):
+    add_builtins(bin=bin, format=format)
+
+def guarded_next(iterator, default=_marker):
+    """next(iterator[, default])
+
+    Return the next item from the iterator. If default is given
+    and the iterator is exhausted, it is returned instead of
+    raising StopIteration.
+    """
+    try:
+        iternext = guarded_getattr(iterator, 'next').__call__
+        # this way an AttributeError while executing next() isn't hidden
+        # (2.6 does this too)
+    except AttributeError:
+        raise TypeError("%s object is not an iterator"
+                        % type(iterator).__name__)
+    try:
+        return iternext()
+    except StopIteration:
+        if default is _marker:
+            raise
+        return default
+add_builtins(next=guarded_next)
+
+def _check_type_access(name, v):
+  def factory(inst, name):
+    if not (name == 'fromkeys' and type(inst) is dict):
+      # fallback to default security
+      aq_acquire(inst, name, aq_validate, getSecurityManager().validate)
+    return v
+  return factory
+
+ContainerAssertions[type] = _check_type_access
+
+class SafeIterItems(SafeIter):
+
+    def next(self):
+        ob = self._next()
+        c = self.container
+        guard(c, ob[0])
+        guard(c, ob[1])
+        return ob
+
+def get_iteritems(c, name):
+    return lambda: SafeIterItems(c.iteritems(), c)
+_dict_white_list['iteritems'] = get_iteritems
+
+if sys.version_info < (2, 5):
+    # these are backported in Products.ERP5Type.patches.python
+    def guarded_any(seq):
+        return any(guarded_iter(seq))
+    safe_builtins['any'] = guarded_any
+
+    def guarded_all(seq):
+        return all(guarded_iter(seq))
+    safe_builtins['all'] = guarded_all
+
+def guarded_sorted(seq, cmp=None, key=None, reverse=False):
+    if not isinstance(seq, SafeIter):
+        for i, x in enumerate(seq):
+            guard(seq, x, i)
+    return sorted(seq, cmp=cmp, key=key, reverse=reverse)
+safe_builtins['sorted'] = guarded_sorted
+
+def guarded_reversed(seq):
+    return SafeIter(reversed(seq))
+safe_builtins['reversed'] = guarded_reversed
+
+def get_set_pop(s, name):
+    def guarded_pop():
+        v = s.pop()
+        try:
+            guard(s, v)
+        except Unauthorized:
+            s.add(v)
+            raise
+        return v
+    return guarded_pop
+
+_set_white_get = {
+    'add': 1, 'clear': 1, 'copy': 1, 'difference': 1, 'difference_update': 1,
+    'discard': 1, 'intersection': 1, 'intersection_update': 1, 'isdisjoint': 1,
+    'issubset': 1, 'issuperset': 1, 'pop': get_set_pop, 'remove': 1,
+    'symmetric_difference': 1, 'symmetric_difference_update': 1, 'union': 1,
+    'update': 1}.get
+
+def _check_set_access(name, value):
+    # Check whether value is a set method
+    self = getattr(value, '__self__', None)
+    if self is None: # item
+        return 1
+    # Disallow spoofing
+    if type(self) is not set:
+        return 0
+    if getattr(value, '__name__', None) != name:
+        return 0
+    return _set_white_get(name, 0)
+
+ContainerAssertions[set] = _check_set_access
+
+ContainerAssertions[frozenset] = 1
+
+from collections import OrderedDict
+OrderedDict.__allow_access_to_unprotected_subobjects__ = 1
+
+from AccessControl import allow_module, allow_class, allow_type
+from AccessControl import ModuleSecurityInfo
+
+# given as example in Products.PythonScripts.module_access_examples
+allow_module('base64')
+allow_module('binascii')
+allow_module('bisect')
+allow_module('colorsys')
+allow_module('crypt')
+##
+
+allow_module('pprint')
+ModuleSecurityInfo('json').declarePublic('dumps', 'loads')
+
+import re
+allow_module('fnmatch')
+allow_module('re')
+allow_type(type(re.compile('')))
+allow_type(type(re.match('x','x')))
+
+import cStringIO
+f = cStringIO.StringIO()
+allow_module('cStringIO')
+allow_module('StringIO')
+allow_type(type(f))
+
+ModuleSecurityInfo('cgi').declarePublic('escape', 'parse_header')
+allow_module('difflib')
+allow_module('hashlib')
+allow_module('time')
+allow_module('urlparse')
diff --git a/product/ERP5Type/patches/python.py b/product/ERP5Type/patches/python.py
index 1f920f38c4..63a60587ad 100644
--- a/product/ERP5Type/patches/python.py
+++ b/product/ERP5Type/patches/python.py
@@ -26,7 +26,7 @@
 #
 ##############################################################################
 
-import sys
+import sys, types
 
 if sys.version_info < (2, 5):
   import __builtin__, imp
@@ -85,3 +85,26 @@ if sys.version_info < (2, 5):
       object = _ordered_dict(object)
     return orig_safe_repr(object, context, maxlevels, level)
   _pprint._safe_repr = _safe_repr
+
+
+if sys.version_info < (2, 6):
+
+  try:
+    import simplejson as json
+  except ImportError, missing_simplejson:
+    class dummy(types.ModuleType):
+      def __getattr__(self, name):
+        raise missing_simplejson
+    json = dummy('dummy_json')
+  sys.modules['json'] = json
+
+
+if sys.version_info < (2, 7):
+
+  try:
+    from ordereddict import OrderedDict
+  except ImportError, missing_ordereddict:
+    def OrderedDict(*args, **kw):
+      raise missing_ordereddict
+  import collections
+  collections.OrderedDict = ordereddict.OrderedDict
-- 
2.30.9