================================== Support for Restricted Python Code ================================== This package provides a way to compile untrusted Python code so that it can be executed safely. This form of restricted Python assumes that security proxies will be used to protect assets. Given this, the only thing that actually needs to be done differently by the generated code is to: - Ensure that all attribute lookups go through a safe version of the getattr() function that's been provided in the built-in functions used in the execution environment. - Prevent exec statements. (Later, we could possibly make exec safe.) - Print statements always go to an output that is provided as a global, rather than having an implicit sys.output. - Prevent try/except and raise statements. This is mainly because they don't work properly in the presense of security proxies. Try/except statements will be made to work in the future. No other special treatment is needed to support safe expression evaluation. The implementation makes use of the `RestrictedPython` package, originally written for Zope 2. There is a new AST re-writer in `zope.security.untrustedpython.rcompile` which performs the tree-transformation, and a top-level `compile()` function in `zope.security.untrustedpython.rcompile`; the later is what client applications are expected to use. The signature of the `compile()` function is very similar to that of Python's built-in `compile()` function:: compile(source, filename, mode) Using it is equally simple:: >>> from zope.security.untrustedpython.rcompile import compile >>> code = compile("21 * 2", "", "eval") >>> eval(code) 42 What's interesting about the restricted code is that all attribute lookups go through the `getattr()` function. This is generally provided as a built-in function in the restricted environment:: >>> def mygetattr(object, name, default="Yahoo!"): ... marker = [] ... print "Looking up", name ... if getattr(object, name, marker) is marker: ... return default ... else: ... return "Yeehaw!" >>> import __builtin__ >>> builtins = __builtin__.__dict__.copy() >>> builtins["getattr"] = mygetattr >>> def reval(source): ... code = compile(source, "README.txt", "eval") ... globals = {"__builtins__": builtins} ... return eval(code, globals, {}) >>> reval("(42).__class__") Looking up __class__ 'Yeehaw!' >>> reval("(42).not_really_there") Looking up not_really_there 'Yahoo!' >>> reval("(42).foo.not_really_there") Looking up foo Looking up not_really_there 'Yahoo!' This allows a `getattr()` to be used that ensures the result of evaluation is a security proxy. To compile code with statements, use exec or single: >>> exec compile("x = 1", "", "exec") >>> x 1 Trying to compile exec, raise or try/except sattements gives syntax errors: >>> compile("exec 'x = 2'", "", "exec") Traceback (most recent call last): ... SyntaxError: Line 1: exec statements are not supported >>> compile("raise KeyError('x')", "", "exec") Traceback (most recent call last): ... SyntaxError: Line 1: raise statements are not supported >>> compile("try: pass\nexcept: pass", "", "exec") Traceback (most recent call last): ... SyntaxError: Line 1: try/except statements are not supported Printing to an explicit writable is allowed: >>> import StringIO >>> f = StringIO.StringIO() >>> code = compile("print >> f, 'hi',\nprint >> f, 'world'", '', 'exec') >>> exec code in {'f': f} >>> f.getvalue() 'hi world\n' But if no output is specified, then output will be send to `untrusted_output`: >>> code = compile("print 'hi',\nprint 'world'", '', 'exec') >>> exec code in {} Traceback (most recent call last): ... NameError: name 'untrusted_output' is not defined >>> f = StringIO.StringIO() >>> exec code in {'untrusted_output': f}