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