diff --git a/.gitignore b/.gitignore index 25faf5e..34e27cf 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ pyvenv.cfg /htmlcov /include /lib +/share /local.cfg /parts /src/*.egg-info diff --git a/.travis.yml b/.travis.yml index f36f24e..3819d99 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,13 +8,13 @@ python: - pypy-5.4 env: - ENVIRON=py - - ENVIRON=isort,flake8 + - ENVIRON=isort,flake8,docs matrix: exclude: - - env: ENVIRON=isort,flake8 + - env: ENVIRON=isort,flake8,docs include: - python: "3.6" - env: ENVIRON=isort,flake8 + env: ENVIRON=isort,flake8,docs install: - pip install tox coveralls coverage script: diff --git a/CHANGES.txt b/CHANGES.rst similarity index 98% rename from CHANGES.txt rename to CHANGES.rst index d2d0c12..67ff7ad 100644 --- a/CHANGES.txt +++ b/CHANGES.rst @@ -5,7 +5,7 @@ Changes ------------------ - Mostly complete rewrite based on Python AST module. - [loechel (Alexander Loechel), icemac (Michael Howitz), stephan-hof (Stephan Hof), tlotze (Thomas Lotze)] + [loechel (Alexander Loechel), icemac (Michael Howitz), stephan-hof (Stephan Hofmockel), tlotze (Thomas Lotze)] - switch to pytest diff --git a/docs/RestrictedPython3/index.rst b/docs/RestrictedPython3/index.rst index 259bbf2..21f37ef 100644 --- a/docs/RestrictedPython3/index.rst +++ b/docs/RestrictedPython3/index.rst @@ -8,9 +8,9 @@ Technical foundation of RestrictedPython RestrictedPython is based on the Python 2 only standard library module ``compiler`` (https://docs.python.org/2.7/library/compiler.html). RestrictedPython based on the -* compiler.ast -* compiler.parse -* compiler.pycodegen +* ``compiler.ast`` +* ``compiler.parse`` +* ``compiler.pycodegen`` With Python 2.6 the compiler module with all its sub modules has been declared deprecated with no direct upgrade Path or recommendations for a replacement. @@ -29,11 +29,11 @@ RestrictedPython 3.6.x aims on supporting Python versions: * 2.6 * 2.7 -Even if the README claims that Compatibility Support is form Python 2.3 - 2.7 I found some Code in RestricedPython and related Packages which test if Python 1 is used. +Even if the README claims that Compatibility Support is form Python 2.3 - 2.7 I found some Code in RestrictedPython and related Packages which test if Python 1 is used. Due to this approach to support all Python 2 Versions the code uses only statements that are compatible with all of those versions. -So oldstyle classes and newstyle classes are mixed, +So old style classes and new style classes are mixed, The following language elements are statements and not functions: diff --git a/docs/RestrictedPython4/index.rst b/docs/RestrictedPython4/index.rst index ff7c451..60b5810 100644 --- a/docs/RestrictedPython4/index.rst +++ b/docs/RestrictedPython4/index.rst @@ -12,6 +12,9 @@ Zope and Plone should become Python 3 compatible. One of the core features of Zope 2 and therefore Plone is the possibility to implement and modify Python scripts and templates through the web (TTW) without harming the application or server itself. +As Python is a `Turing complete`_ programming language programmers don't have any limitation and could potentially harm the Application and Server itself. + +RestrictedPython and AccessControl aims on this topic to provide a reduced subset of the Python Programming language, where all functions that could harm the system are permitted by default. Targeted Versions to support ---------------------------- @@ -27,6 +30,7 @@ will be completed): * PyPy2.7 .. _`security support` : https://docs.python.org/devguide/index.html#branchstatus +.. _`Turing complete`: https://en.wikipedia.org/wiki/Turing_completeness We explicitly excluded Python 3.3 and PyPy3 (which is based on the Python 3.3 specification) as the changes in Python 3.4 are significant and the Python 3.3 is nearing the end of its supported lifetime. @@ -43,6 +47,46 @@ The following packages / modules have hard dependencies on RestrictedPython: * Products.PluginIndexes --> * five.pt (wrapping some functions and protection for Chameleon) --> -Additionally the folowing add ons have dependencies on RestrictedPython +Additionally the following add ons have dependencies on RestrictedPython * None + +How RestrictedPython 4+ works internally +---------------------------------------- + +RestrictedPython's core functions are split over several files: + +* __init__.py --> It exports the API directly in the ``RestrictedPython`` namespace. It should be not necessary to import from any other module inside the package. +* compile.py --> It contains the ``compile_restricted`` functions where internally ``_compile_restricted_mode`` is the important one +* transformer.py --> Home of the ``RestrictingNodeTransformer`` + +``RestrictingNodeTransformer`` +.............................. + +The ``RestrictingNodeTransformer`` is one of the core elements of RestrictedPython, it provides the base policy used by itself. + +``RestrictingNodeTransformer`` is a subclass of a ``NodeTransformer`` which has as set of ``visit_`` methods and a ``generic_visit`` method. + +``generic_visit`` is a predefined method of any ``NodeVisitor`` which sequential visit all sub nodes, in RestrictedPython this behavior is overwritten to always call a new internal method ``not_allowed(node)``. +This results in a implicit whitelisting of all allowed AST elements. +Any possible new introduced AST element in Python (new language element) will implicit be blocked and not allowed in RestrictedPython. + +So if new elements should be introduced an explicit ``visit_`` is necessary. + + +``_compile_restricted_mode`` +............................ + +``_compile_restricted_mode`` is an internal method that does the whole mapping against the used policy and compiles provided source code, with respecting the mode. +It is wrapped by the explicit functions: + +* ``compile_restricted_exec`` +* ``compile_restricted_eval`` +* ``compile_restricted_single`` +* ``compile_restricted_function`` + +They are still exposed as those are the nominal used API. + +For advanced usage this function is interesting as it is the point where the policy came into play. +If ``policy`` is ``None`` it just call the Python builtin ``compile`` method. +Else it parse the provided Python source code into an ``ast.AST`` and let it check and transform by the provided policy. diff --git a/docs/api/index.rst b/docs/api/index.rst index c63b995..d0a5d2d 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -1,8 +1,24 @@ -API von RestrictedPython 4.0 -============================ +API of RestrictedPython 4.0 +=========================== +.. code:: Python + compile_restricted(source, filename, mode [, flags [, dont_inherit]]) .. code:: Python - compile_restricted(source, filename, mode [, flags [, dont_inherit]]) + compile_restricted_exec(source, filename, mode [, flags [, dont_inherit [, policy]]]) + +.. code:: Python + + compile_restricted_eval(source, filename, mode [, flags [, dont_inherit [, policy]]]) + + +.. code:: Python + + compile_restricted_single(source, filename, mode [, flags [, dont_inherit [, policy]]]) + + +.. code:: Python + + compile_restricted_function(source, filename, mode [, flags [, dont_inherit [, policy]]]) diff --git a/docs/conf.py b/docs/conf.py index a2c1231..da3ffea 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -150,7 +150,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +# html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied diff --git a/docs/idea.rst b/docs/idea.rst index 06f5ae8..c100b84 100644 --- a/docs/idea.rst +++ b/docs/idea.rst @@ -1,10 +1,10 @@ The Idea behind RestrictedPython ================================ -Python is a `Turing-complete`_ programming language. +Python is a `Turing complete`_ programming language. To offer a Python interface for users in web context is a potential security risk. Web frameworks and Content Management Systems (CMS) want to offer their users as much extensibility as possible through the web (TTW). -This also means to have permissions to add functionallity via a Python Script. +This also means to have permissions to add functionality via a Python Script. There should be additional preventive measures taken to ensure integrity of the application and the server itself, according to information security best practice and unrelated to Restricted Python. @@ -14,10 +14,10 @@ The `Ada Ravenscar profile`_ is another example of such an approach. Defining a secure subset of the language involves restricting the `EBNF`_ elements and explicitly allowing or disallowing language features. Much of the power of a programming language derives from its standard and contributed libraries, so any calling of these methods must also be checked and potentially restricted. -RestricedPython generally disallows calls to any library that is not explicit whitelisted. +RestrictedPython generally disallows calls to any library that is not explicit whitelisted. As Python is a scripting language that is executed by an interpreter. -Any Python code that should be executed have to be explict checked before executing a generated byte code by the interpreter. +Any Python code that should be executed have to be explicit checked before executing a generated byte code by the interpreter. Python itself offers three methods that provide such a workflow: @@ -25,7 +25,7 @@ Python itself offers three methods that provide such a workflow: * ``exec`` / ``exec()`` which executes the byte code in the interpreter * ``eval`` / ``eval()`` which executes a byte code expression -Therefore RestrictedPython offers a repacement for the python builtin function ``compile()`` (Python 2: https://docs.python.org/2/library/functions.html#compile / Python 3 https://docs.python.org/3/library/functions.html#compile). +Therefore RestrictedPython offers a replacement for the python builtin function ``compile()`` (Python 2: https://docs.python.org/2/library/functions.html#compile / Python 3 https://docs.python.org/3/library/functions.html#compile). This method is defined as following: .. code:: Python @@ -40,16 +40,16 @@ There are three valid string values for ``mode``: * ``'eval'`` * ``'single'`` -For RestricedPython this ``compile()`` method is replaced by: +For RestrictedPython this ``compile()`` method is replaced by: .. code:: Python - compile_restriced(source, filename, mode [, flags [, dont_inherit]]) + compile_restricted(source, filename, mode [, flags [, dont_inherit]]) -The primary parameter ``source`` has to be a ASCII or ``unicode`` string. +The primary parameter ``source`` has to be a ASCII or ``unicode`` string (With Python 2.6 an additional option for source was added: ``ast.AST`` for :ref:`Code generation <_sec_code_generation>`). Both methods either returns compiled byte code that the interpreter could execute or raise exceptions if the provided source code is invalid. -As ``compile`` and ``compile_restricted`` just compile the provided source code to bytecode it is not sufficient to sandbox the environment, as all calls to libraries are still available. +As ``compile`` and ``compile_restricted`` just compile the provided source code to byte code it is not sufficient to sandbox the environment, as all calls to libraries are still available. The two methods / Statements: @@ -58,12 +58,12 @@ The two methods / Statements: have two parameters: -* globals -* locals +* ``globals`` +* ``locals`` which are a reference to the Python builtins. -By modifing and restricting the avaliable moules, methods and constants from globals and locals we could limit the possible calls. +By modifying and restricting the available modules, methods and constants from globals and locals we could limit the possible calls. Additionally RestrictedPython offers a way to define a policy which allows developers to protect access to attributes. This works by defining a restricted version of: @@ -76,11 +76,11 @@ This works by defining a restricted version of: Also RestrictedPython provides three predefined, limited versions of Python's own ``__builtins__``: * ``safe_builtins`` (by Guards.py) -* ``limited_builtins`` (by Limits.py), which provides restriced sequence types +* ``limited_builtins`` (by Limits.py), which provides restricted sequence types * ``utilities_builtins`` (by Utilities.py), which provides access for standard modules math, random, string and for sets. Additional there exist guard functions to make attributes of Python objects immutable --> ``full_write_guard`` (write and delete protected) -.. _Turing-complete: https://en.wikipedia.org/wiki/Turing_completeness +.. _`Turing complete`: https://en.wikipedia.org/wiki/Turing_completeness .. _Ada Ravenscar Profile: https://en.wikipedia.org/wiki/Ravenscar_profile .. _EBNF: https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form diff --git a/docs/index.rst b/docs/index.rst index c48a8d9..253d047 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,11 +17,13 @@ Contents idea basics/index - RestrictedPython3/index - RestrictedPython4/index - update/index + usage/index api/index + RestrictedPython3/index + RestrictedPython4/index + upgrade/index + upgrade_dependencies/index Indices and tables ================== diff --git a/docs/notes.rst b/docs/notes.rst index 5843906..438122d 100644 --- a/docs/notes.rst +++ b/docs/notes.rst @@ -1,6 +1,9 @@ How it works ============ +*Caution:* This is old documentation from RestrictedPython 3 and before. +Information should be transferred and this file should be removed. + Every time I see this code, I have to relearn it. These notes will hopefully make this a little easier. :) diff --git a/docs/update/index.rst b/docs/update/index.rst deleted file mode 100644 index a29c137..0000000 --- a/docs/update/index.rst +++ /dev/null @@ -1,65 +0,0 @@ -Concept for a upgrade to Python 3 -================================= - -RestrictedPython is a classic approach of compiler construction to create a limited subset of an existing programming language. - -Defining a programming language requires a regular grammar (Chomsky 3 / EBNF) definition. -This grammar will be implemented in an abstract syntax tree (AST), which will be passed on to a code generator to produce a machine-readable version. - -As Python is a platform independent programming language, this machine readable version is a byte code which will be translated on the fly by an interpreter into machine code. -This machine code then gets executed on the specific CPU architecture, with the standard operating system restrictions. - -The bytecode produced must be compatible with the execution environment that the Python interpreter is running in, so we do not generate the byte code directly from ``compile_restricted`` and the other ``compile_restricted_*`` methods manually, it may not match what the interpreter expects. - -Thankfully, the Python ``compile()`` function introduced the capability to compile ``ast.AST`` code into byte code in Python 2.6, so we can return the platform-independent AST and keep bytecode generation delegated to the interpreter. - -As the ``compiler`` module was deprecated in Python 2.6 and was removed before Python 3.0 was released it has never been avaliable for any Python 3 version. -So we need to move from ``compiler.ast`` to ``ast`` to support newer Python versions. - -``compiler.ast`` --> ``ast`` ----------------------------- - -From the point of view of compiler design, the concepts of the compiler module and the ast module are similar. -The ``compiler`` module predates several major improvements of the Python development like a generally applied style guide. -While ``compiler`` still uses the old `CamelCase`_ Syntax (``visitNode(self, node, walker)``) the ``ast.AST`` did now use the Python common ``visit_Node(self, node)`` syntax. -Also the names of classes have been changed, where ``compiler`` uses ``Walker`` and ``Mutator`` the corresponding elements in ``ast.AST`` are ``NodeVisitor`` and ``NodeTransformator``. - - -ast module (Abstract Syntax Trees) ---------------------------------- - -The ast module consists of four areas: - -* AST (Basis of all Nodes) + all node class implementations -* NodeVisitor and NodeTransformer (tool to consume and modify the AST) -* Helper methods - - * parse - * walk - * dump - -* Constants - - * PyCF_ONLY_AST - - -NodeVisitor & NodeTransformer ------------------------------ - -A NodeVisitor is a class of a node / AST consumer, it reads the data by stepping through the tree without modifying it. -In contrast, a NodeTransformer (which inherits from a NodeVisitor) is allowed to modify the tree and nodes. - - -Links ------ - -* Concept of Immutable Types and Python Example (https://en.wikipedia.org/wiki/Immutable_object#Python) -* Python 3 Standard Library Documentation on AST module ``ast`` (https://docs.python.org/3/library/ast.html) - - * AST Gramar of Python https://docs.python.org/3.5/library/ast.html#abstract-grammar https://docs.python.org/2.7/library/ast.html#abstract-grammar - * NodeVistiors (https://docs.python.org/3.5/library/ast.html#ast.NodeVisitor) - * NodeTransformer (https://docs.python.org/3.5/library/ast.html#ast.NodeTransformer) - * dump (https://docs.python.org/3.5/library/ast.html#ast.dump) - -* Indetail Documentation on the Python AST module ``ast`` (https://greentreesnakes.readthedocs.org/en/latest/) -* Example how to Instrumenting the Python AST (``ast.AST``) (http://www.dalkescientific.com/writings/diary/archive/2010/02/22/instrumenting_the_ast.html) diff --git a/docs/update_notes.rst b/docs/update_notes.rst index b10386a..665c089 100644 --- a/docs/update_notes.rst +++ b/docs/update_notes.rst @@ -1,39 +1,6 @@ Notes on the Update Process to be Python 3 compatible ===================================================== -*Note, due to my English, I sometimes fall back to German on describing my ideas and learnings.* - -Current state -------------- - -Idea of RestrictedPython -........................ - -RestrictedPython offers a replacement for the Python builtin function ``compile()`` (https://docs.python.org/2/library/functions.html#compile) which is defined as: - -.. code:: Python - - compile(source, filename, mode [, flags [, dont_inherit]]) - -The definition of compile has changed over the time, but the important element is the param ``mode``, there are three allowed values for this string param: - -* ``'exec'`` -* ``'eval'`` -* ``'single'`` - -RestrictedPython has it origin in the time of Python 1 and early Python 2. -The optional params ``flags`` and ``dont_inherit`` has been added to Python's ``compile()`` function with Version Python 2.3. -RestrictedPython never added those new parameters to implementation. -The definition of - -.. code:: Python - - compile_restricted(source, filename, mode) - - -The primary param ``source`` has been restriced to be an ASCII string or ``unicode`` string. - - Also RestrictedPython provides a way to define Policies, by redefining restricted versions of ``print``, ``getattr``, ``setattr``, ``import``, etc.. As shortcutes it offers three stripped down versions of Pythons ``__builtins__``: @@ -59,83 +26,6 @@ RestrictedPython based on the With Python 2.6 the compiler module with all its sub modules has been declared deprecated with no direct upgrade Path or recommendations for a replacement. -Version Support of RestrictedPython 3.6.x -......................................... - -RestrictedPython 3.6.x aims on supporting Python versions: - -* 2.0 -* 2.1 -* 2.2 -* 2.3 -* 2.4 -* 2.5 -* 2.6 -* 2.7 - -Even if the README claims that Compatibility Support is form Python 2.3 - 2.7 I found some Code in RestricedPython and related Packages which test if Python 1 is used. - -Due to this approach to support all Python 2 Versions the code uses only statements that are compatible with all of those versions. - -So oldstyle classes and newstyle classes are mixed, - -The following language elements are statements and not functions: - -* exec -* print - - - -Goals for Rewrite ------------------ - -We want to rewrite RestrictedPython as it is one of the core dependencies for the Zope2 Application Server which is the base for the CMS Plone. -Zope2 should become Python3 compatible. - -One of the core features of Zope2 and therefore Plone is the capability to write and modify Code and Templates TTW (through the web). -As Python is a Turing Complete programming language programmers don't have any limitation and could potentially harm the Application and Server itself. - -RestrictedPython and AccessControl aims on this topic to provide a reduced subset of the Python Programming language, where all functions that could harm the system are permitted by default. - -Therefore RestrictedPython provide a way to define Policies and - - - - - -Zope2 Core Packages that has RestrictedPython as dependencies -............................................................. - -The following Packages used in Zope2 for Plone depend on RestricedPython: - -* AccessControl -* zope.untrustedpython -* DocumentTemplate -* Products.PageTemplates -* Products.PythonScripts -* Products.PluginIndexes -* five.pt (wrapping some functions and protection for Chameleon) - -Targeted Versions to support -............................ - -For a RestrictedPython 4.0.0+ Update we aim to support only current Python Versions (under active Security Support): - -* 2.7 -* 3.4 -* 3.5 -* 3.6 - -Targeted API -............ - - -.. code:: Python - - compile(source, filename, mode [, flags [, dont_inherit]]) - compile_restricted(source, filename, mode [, flags [, dont_inherit]]) - - Approach -------- @@ -153,27 +43,3 @@ Produced byte code has to compatible with the execution environment, the Python So we must not generate the byte code that has to be returned from ``compile_restricted`` and the other ``compile_restricted_*`` methods manually, as this might harm the interpreter. We actually don't even need that. The Python ``compile()`` function introduced the capability to compile ``ast.AST`` code into byte code. - -Technical Backgrounds -..................... - -https://docs.python.org/3.5/library/ast.html#abstract-grammar - -NodeVistiors (https://docs.python.org/3.5/library/ast.html#ast.NodeVisitor) - -NodeTransformer (https://docs.python.org/3.5/library/ast.html#ast.NodeTransformer) - -dump (https://docs.python.org/3.5/library/ast.html#ast.dump) - - - - - - -Links ------ - -* Concept of Immutable Types and Python Example (https://en.wikipedia.org/wiki/Immutable_object#Python) -* Python 3 Standard Library Documentation on AST module ``ast`` (https://docs.python.org/3/library/ast.html) -* Indetail Documentation on the Python AST module ``ast`` (https://greentreesnakes.readthedocs.org/en/latest/) -* Example how to Instrumenting the Python AST (``ast.AST``) (http://www.dalkescientific.com/writings/diary/archive/2010/02/22/instrumenting_the_ast.html) diff --git a/docs/update/ast/python2_6.ast b/docs/upgrade/ast/python2_6.ast similarity index 100% rename from docs/update/ast/python2_6.ast rename to docs/upgrade/ast/python2_6.ast diff --git a/docs/update/ast/python2_7.ast b/docs/upgrade/ast/python2_7.ast similarity index 100% rename from docs/update/ast/python2_7.ast rename to docs/upgrade/ast/python2_7.ast diff --git a/docs/update/ast/python3_0.ast b/docs/upgrade/ast/python3_0.ast similarity index 100% rename from docs/update/ast/python3_0.ast rename to docs/upgrade/ast/python3_0.ast diff --git a/docs/update/ast/python3_1.ast b/docs/upgrade/ast/python3_1.ast similarity index 100% rename from docs/update/ast/python3_1.ast rename to docs/upgrade/ast/python3_1.ast diff --git a/docs/update/ast/python3_2.ast b/docs/upgrade/ast/python3_2.ast similarity index 100% rename from docs/update/ast/python3_2.ast rename to docs/upgrade/ast/python3_2.ast diff --git a/docs/update/ast/python3_3.ast b/docs/upgrade/ast/python3_3.ast similarity index 100% rename from docs/update/ast/python3_3.ast rename to docs/upgrade/ast/python3_3.ast diff --git a/docs/update/ast/python3_4.ast b/docs/upgrade/ast/python3_4.ast similarity index 100% rename from docs/update/ast/python3_4.ast rename to docs/upgrade/ast/python3_4.ast diff --git a/docs/update/ast/python3_5.ast b/docs/upgrade/ast/python3_5.ast similarity index 100% rename from docs/update/ast/python3_5.ast rename to docs/upgrade/ast/python3_5.ast diff --git a/docs/update/ast/python3_6.ast b/docs/upgrade/ast/python3_6.ast similarity index 100% rename from docs/update/ast/python3_6.ast rename to docs/upgrade/ast/python3_6.ast diff --git a/docs/upgrade/index.rst b/docs/upgrade/index.rst new file mode 100644 index 0000000..9ce393e --- /dev/null +++ b/docs/upgrade/index.rst @@ -0,0 +1,115 @@ +Concept for a upgrade to Python 3 +================================= + +RestrictedPython is a classic approach of compiler construction to create a limited subset of an existing programming language. + +Defining a programming language requires a regular grammar (`Chomsky 3`_ / `EBNF`_) definition. +This grammar will be implemented in an abstract syntax tree (AST), which will be passed on to a code generator to produce a machine-readable version. + +.. _`_sec_code_generation`: + +Code generation +--------------- + +As Python is a platform independent programming language, this machine readable version is a byte code which will be translated on the fly by an interpreter into machine code. +This machine code then gets executed on the specific CPU architecture, with the standard operating system restrictions. + +The byte code produced must be compatible with the execution environment that the Python interpreter is running in, so we do not generate the byte code directly from ``compile_restricted`` and the other ``compile_restricted_*`` methods manually, it may not match what the interpreter expects. + +Thankfully, the Python ``compile()`` function introduced the capability to compile ``ast.AST`` code into byte code in Python 2.6, so we can return the platform-independent AST and keep byte code generation delegated to the interpreter. + +``compiler.ast`` --> ``ast`` +---------------------------- + +As the ``compiler`` module was deprecated in Python 2.6 and was removed before Python 3.0 was released it has never been available for any Python 3 version. +Instead Python 2.6 / Python 3 introduced the new ``ast`` module, that is more widly supported. +So we need to move from ``compiler.ast`` to ``ast`` to support newer Python versions. + +From the point of view of compiler design, the concepts of the ``compiler`` module and the ``ast`` module are similar. +The ``compiler`` module predates several major improvements of the Python development like a generally applied style guide. +While ``compiler`` still uses the old `CamelCase`_ Syntax (``visitNode(self, node, walker)``) the ``ast.AST`` did now use the Python common ``visit_Node(self, node)`` syntax. +Also the names of classes have been changed, where ``compiler`` uses ``Walker`` and ``Mutator`` the corresponding elements in ``ast.AST`` are ``NodeVisitor`` and ``NodeTransformator``. + + +``ast`` module (Abstract Syntax Trees) +-------------------------------------- + +The ``ast`` module consists of four areas: + +* ``AST`` (Basis of all Nodes) + all node class implementations +* ``NodeVisitor`` and ``NodeTransformer`` (tool to consume and modify the AST) +* Helper methods + + * ``parse`` + * ``walk`` + * ``dump`` + +* Constants + + * ``PyCF_ONLY_AST`` + + +``NodeVisitor`` & ``NodeTransformer`` +..................................... + +A ``NodeVisitor`` is a class of a node / AST consumer, it reads the data by stepping through the tree without modifying it. +In contrast, a ``NodeTransformer`` (which inherits from a ``NodeVisitor``) is allowed to modify the tree and nodes. + +Modifying the AST +----------------- + + + + + + + + +Technical Backgrounds - Links to External Documentation +--------------------------------------------------------- + +* Concept of Immutable Types and Python Example (https://en.wikipedia.org/wiki/Immutable_object#Python) +* Python 3 Standard Library Documentation on AST module ``ast`` (https://docs.python.org/3/library/ast.html) + + * AST Grammar of Python + + * `Python 3.6 AST`_ + * `Python 3.5 AST`_ + * `Python 3.4 AST`_ + * `Python 3.3 AST`_ + * `Python 3.2 AST`_ + * `Python 3.1 AST`_ + * `Python 3.0 AST`_ + * `Python 2.7 AST`_ + * `Python 2.6 AST`_ + + * ``NodeVistiors`` (https://docs.python.org/3.5/library/ast.html#ast.NodeVisitor) + * ``NodeTransformer`` (https://docs.python.org/3.5/library/ast.html#ast.NodeTransformer) + * ``dump`` (https://docs.python.org/3.5/library/ast.html#ast.dump) + +* In detail Documentation on the Python AST module ``ast`` (Green Tree Snakes) (https://greentreesnakes.readthedocs.org/en/latest/) +* Example how to Instrumenting the Python AST (``ast.AST``) (http://www.dalkescientific.com/writings/diary/archive/2010/02/22/instrumenting_the_ast.html) + +.. _`CamelCase`: https://en.wikipedia.org/wiki/Camel_case + +.. _`EBNF`: https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form + +.. _`Chomsky 3`: https://en.wikipedia.org/wiki/Chomsky_hierarchy#Type-3_grammars + +.. _`Python 3.6 AST`: https://docs.python.org/3.6/library/ast.html#abstract-grammar + +.. _`Python 3.5 AST`: https://docs.python.org/3.5/library/ast.html#abstract-grammar + +.. _`Python 3.4 AST`: https://docs.python.org/3.4/library/ast.html#abstract-grammar + +.. _`Python 3.3 AST`: https://docs.python.org/3.3/library/ast.html#abstract-grammar + +.. _`Python 3.2 AST`: https://docs.python.org/3.2/library/ast.html#abstract-grammar + +.. _`Python 3.1 AST`: https://docs.python.org/3.1/library/ast.html#abstract-grammar + +.. _`Python 3.0 AST`: https://docs.python.org/3.0/library/ast.html#abstract-grammar + +.. _`Python 2.7 AST`: https://docs.python.org/2.7/library/ast.html#abstract-grammar + +.. _`Python 2.6 AST`: https://docs.python.org/2.6/library/ast.html#abstract-grammar diff --git a/docs/upgrade_dependencies/index.rst b/docs/upgrade_dependencies/index.rst new file mode 100644 index 0000000..d411a14 --- /dev/null +++ b/docs/upgrade_dependencies/index.rst @@ -0,0 +1,42 @@ +Upgrade dependencies +==================== + +The following packages used in Zope2 and Plone depend on ``RestricedPython``: + +* AccessControl +* zope.untrustedpython +* DocumentTemplate +* Products.PageTemplates +* Products.PythonScripts +* Products.PluginIndexes +* five.pt (wrapping some functions and protection for Chameleon) + +Upgrade path +------------ + +For packages that use RestrictedPython the upgrade path differs on the actual usage. +If it uses pure RestrictedPython without any additional checks it should be just to check the imports. +RestrictedPython did move some of the imports to the base namespace, so you should only import directly from ``RestrictedPython.__init__.py``. + +* compile_restricted methods: + + * ``from RestrictedPython import compile_restricted`` + * ``from RestrictedPython import compile_restricted_eval`` + * ``from RestrictedPython import compile_restricted_exec`` + * ``from RestrictedPython import compile_restricted_function`` + * ``from RestrictedPython import compile_restricted_single`` + +* predefined built-ins: + + * ``from RestrictedPython import safe_builtins`` + * ``from RestrictedPython import limited_builtins`` + * ``from RestrictedPython import utility_builtins`` + +* helper methods: + + * ``from RestrictedPython import PrintCollector`` + +Any import from ``RestrictedPython.RCompile`` indicates that there have been advanced checks implemented. +Those advanced checks where implemented via a ``MutatingWalker``. +Any checks needs to be reimplemented as a subclass of +``RestrictingNodeTransformer``. diff --git a/docs/usage/index.rst b/docs/usage/index.rst new file mode 100644 index 0000000..5079e0d --- /dev/null +++ b/docs/usage/index.rst @@ -0,0 +1,169 @@ +Usage of RestrictedPython +========================= + +API overview +------------ + +RestrictedPython has tree major scopes: + +1. ``compile_restricted`` methods: + + * ``compile_restricted`` + * ``compile_restricted_exec`` + * ``compile_restricted_eval`` + * ``compile_restricted_single`` + * ``compile_restricted_function`` + +2. restricted builtins + + * ``safe_builtins`` + * ``limited_builtins`` + * ``utility_builtins`` + +3. helper modules + + * ``PrintCollector`` + +Basic usage +----------- + +The general workflow to execute Python code that is loaded within a Python program is: + +.. code:: Python + + source_code = """ + def do_something(): + pass + """ + + byte_code = compile(source_code, filename='', mode='exec') + exec(byte_code) + do_something() + +With RestrictedPython that workflow should be as straight forward as possible: + +.. code:: Python + + from RestrictedPython import compile_restricted as compile + + source_code = """ + def do_something(): + pass + """ + + byte_code = compile(source_code, filename='', mode='exec') + exec(byte_code) + do_something() + +With that simple addition: + +.. code:: Python + + from RestrictedPython import compile_restricted as compile + +it uses a predefined policy that checks and modify the source code and checks against a restricted subset of the Python language. +The compiled source code is still executed against the full available set of library modules and methods. + +The Python :py:func:`exec` takes three parameters: + +* ``code`` which is the compiled byte code +* ``globals`` which is global dictionary +* ``locals`` which is the local dictionary + +By limiting the entries in the ``globals`` and ``locals`` dictionaries you +restrict the access to the available library modules and methods. + +Providing defined dictionaries for ``exec()`` should be used in context of RestrictedPython. + +.. code:: Python + + byte_code = + exec(byte_code, { ... }, { ... }) + +Typically there is a defined set of allowed modules, methods and constants used in that context. +RestrictedPython provides three predefined built-ins for that: + +* ``safe_builtins`` +* ``limited_builtins`` +* ``utilities_builtins`` + +So you normally end up using: + +.. code:: Python + + from RestrictedPython import ..._builtins + from RestrictedPython import compile_restricted as compile + + source_code = """""" + + try: + byte_code = compile(source_code, filename='', mode='exec') + + used_builtins = ..._builtins + { } + exec(byte_code, used_buildins, None) + except SyntaxError as e: + ... + +One common advanced usage would be to define an own restricted builtin dictionary. + +.. _sec_usage_frameworks: + +Usage in frameworks and Zope +---------------------------- + +One major issue with using ``compile_restricted`` directly in a framework is, that you have to use try-except statements to handle problems and it might be a bit harder to provide useful information to the user. +RestrictedPython provides four specialized compile_restricted methods: + +* ``compile_restricted_exec`` +* ``compile_restricted_eval`` +* ``compile_restricted_single`` +* ``compile_restricted_function`` + +Those four methods return a tuple with four elements: + +* ``byte_code`` object or ``None`` if ``errors`` is not empty +* ``errors`` a tuple with error messages +* ``warnings`` a list with warnings +* ``used_names`` a set / dictionary with collected used names of library calls + +Those three information "lists" could be used to provide the user with informations about the compiled source code. + +Typical uses cases for the four specialized methods: + +* ``compile_restricted_exec`` --> Python Modules or Scripts that should be used or called by the framework itself or from user calls +* ``compile_restricted_eval`` --> Templates +* ``compile_restricted_single`` +* ``compile_restricted_function`` + +Modifying the builtins is straight forward, it is just a dictionary containing access pointers to available library elements. +Modification is normally removing elements from existing builtins or adding allowed elements by copying from globals. + +For frameworks it could possibly also be useful to change handling of specific Python language elements. +For that use case RestrictedPython provide the possibility to pass an own policy. +A policy is basically a special ``NodeTransformer`` that could be instantiated with three params for ``errors``, ``warnings`` and ``used_names``, it should be a subclass of RestrictingNodeTransformer (that subclassing will maybe later be enforced). + +.. code:: Python + + OwnRestrictingNodeTransformer(errors=[], warnings=[], used_names=[]) + +One special case (defined to unblock ports of Zope Packages to Python 3) is to actually use RestrictedPython in an unrestricted mode, by providing a Null-Policy (aka ``None``). + +All ``compile_restricted*`` methods do have a optional parameter ``policy``, where a specific policy could be provided. + +.. code:: Python + + source_code = """""" + + policy = OwnRestrictingNodeTransformer + + byte_code = compile(source_code, filename='', mode='exec', policy=policy) + exec(byte_code, { ... }, { ... }) + +The Special case "unrestricted RestrictedPython" would be: + +.. code:: Python + + source_code = """""" + + byte_code = compile(source_code, filename='', mode='exec', policy=None) + exec(byte_code, globals(), None) diff --git a/setup.py b/setup.py index 6456ff6..dfe1d49 100644 --- a/setup.py +++ b/setup.py @@ -29,8 +29,8 @@ def read(*rnames): license='ZPL 2.1', description='RestrictedPython provides a restricted execution ' 'environment for Python, e.g. for running untrusted code.', - long_description=(read('src', 'RestrictedPython', 'README.txt') + '\n' + - read('CHANGES.txt')), + long_description=(read('src', 'RestrictedPython', 'README.rst') + '\n' + + read('CHANGES.rst')), classifiers=[ 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', diff --git a/src/RestrictedPython/README.txt b/src/RestrictedPython/README.rst similarity index 100% rename from src/RestrictedPython/README.txt rename to src/RestrictedPython/README.rst diff --git a/src/RestrictedPython/__init__.py b/src/RestrictedPython/__init__.py index 8b9ef76..af8c84b 100644 --- a/src/RestrictedPython/__init__.py +++ b/src/RestrictedPython/__init__.py @@ -12,6 +12,11 @@ ############################################################################## """RestrictedPython package.""" +# This is a file to define public API in the base namespace of the package. +# use: isor:skip to supress all isort related warnings / errors, +# as this file should be logically grouped imports + + # Old API --> Old Import Locations # from RestrictedPython.RCompile import compile_restricted # from RestrictedPython.RCompile import compile_restricted_eval @@ -19,16 +24,24 @@ # from RestrictedPython.RCompile import compile_restricted_function # new API Style -from RestrictedPython.compile import compile_restricted -from RestrictedPython.compile import compile_restricted_eval -from RestrictedPython.compile import compile_restricted_exec -from RestrictedPython.compile import compile_restricted_function -from RestrictedPython.compile import compile_restricted_single -from RestrictedPython.compile import CompileResult -from RestrictedPython.Guards import safe_builtins -from RestrictedPython.Limits import limited_builtins -from RestrictedPython.PrintCollector import PrintCollector -from RestrictedPython.Utilities import utility_builtins +# compile_restricted methods: +from RestrictedPython.compile import compile_restricted # isort:skip +from RestrictedPython.compile import compile_restricted_eval # isort:skip +from RestrictedPython.compile import compile_restricted_exec # isort:skip +from RestrictedPython.compile import compile_restricted_function # isort:skip +from RestrictedPython.compile import compile_restricted_single # isort:skip + +# predefined builtins +from RestrictedPython.Guards import safe_builtins # isort:skip +from RestrictedPython.Limits import limited_builtins # isort:skip +from RestrictedPython.Utilities import utility_builtins # isort:skip + +# Helper Methods +from RestrictedPython.PrintCollector import PrintCollector # isort:skip +from RestrictedPython.compile import CompileResult # isort:skip + +# Policy +from RestrictedPython.transformer import RestrictingNodeTransformer # isort:skip # from RestrictedPython.Eval import RestrictionCapableEval diff --git a/src/RestrictedPython/compile.py b/src/RestrictedPython/compile.py index 5e6fdf2..cd56d1c 100644 --- a/src/RestrictedPython/compile.py +++ b/src/RestrictedPython/compile.py @@ -25,6 +25,11 @@ def _compile_restricted_mode( # Unrestricted Source Checks byte_code = compile(source, filename, mode=mode, flags=flags, dont_inherit=dont_inherit) + # TODO: Should be an elif check if policy is subclass of + # RestrictionNodeTransformer any other object passed in as policy might + # throw an error or is a NodeVisitor subclass that could be initialized + # with three parameters. + # elif issubclass(policy, RestrictingNodeTransformer): else: c_ast = None try: @@ -149,4 +154,5 @@ def compile_restricted( raise TypeError('unknown mode %s', mode) if errors: raise SyntaxError(errors) + # TODO: logging of warnings should be discussed and considered. return byte_code diff --git a/tox.ini b/tox.ini index 33d4da7..b44973a 100644 --- a/tox.ini +++ b/tox.ini @@ -9,6 +9,7 @@ envlist = pypy, coverage-report, isort, + docs skip_missing_interpreters = False [testenv] @@ -57,3 +58,10 @@ commands = basepython = python2.7 deps = flake8 commands = flake8 --doctests src setup.py + +[testenv:docs] +basepython = python2.7 +commands = + sphinx-build -b html -d build/docs/build/doctrees docs build/docs/html +deps = + .[docs]