AppyPrinciplesGetting started
appy.pod Using RestrictedPython

Available since Appy 1.0.13.

By default, when using pod, expressions and statement parts found in pod templates (be it ODT or ODS) are evaluated using the standard Python eval method.

You may consider it as a security problem. Indeed, some organizations using pod let advanced users or third party companies create or modify pod templates.

This is why it is possible to use pod in conjunction with an alternate expression evaluator like RestrictedPython.

Using RestrictedPython with pod

The Renderer's constructor (in appy/pod/renderer.py) has an argument named evaluator. By default, it is set to None. In that case, a standard, eval-based evaluator will be used (from appy/pod/__init__.py).

Alternately, you can place, in this arg, an instance of class Evaluator, from module appy/pod/restricted.py. This class' constructor looks as follows (basic RestrictedPython knowlegde is a prerequisite to understand it).

    def __init__(self, builtins=None, write=None, item=None, unpack=None,
                 iterUnpack=None, attr=None, custom=None):
        # Raise an error if RestrictedPython is not installed
        if not installed:
            raise Exception(RP_N_INS)
        # The replacement for Python __builtins__
        self.builtins = builtins or rp.Guards.safe_builtins
        # Prevent write access to objet attributes
        self.write = write or rp.Guards.full_write_guard
        # For "for" statements and comprehensions
        self.item = item or rp.Eval.default_guarded_getitem
        try:
            self.unpack = unpack or rp.Guards.guarded_unpack_sequence
        except AttributeError:
            self.unpack = unpack # Default may not be available
        try:
            self.iterUnpack = iterUnpack or \
                              rp.Guards.guarded_iter_unpack_sequence
        except AttributeError:
            self.iterUnpack = None
        # Prevent using stdout for printing
        self.safePrint = rp.PrintCollector
        # Protected "getattr". Any other attribute than attr will be
        # initialised once, globally, via updateContext, before any expression
        # is evaluated. attr, on the contrary, will be injected in the
        # context, in run, every time an expression is evaluated.
        self.attr = attr
        # If custom is None, self's attributes as defined hereabove will be
        # injected (by updateContext) in the evaluation context of any
        # evaluated expression, at standard RestrictedPython keys as defined in
        # the v_contextMap. Alternately, if you want to have full control over
        # the RestrictedPython-specific entries you want to inject in the
        # context of any evaluated expression, define them in a custom dict.
        # In this latter case, self's attributes will be ignored: entries from
        # the custom dict will be used instead. This option is useful if the
        # framework you use in conjunction with pod already defines a function
        # that computes such entries.
        self.custom = custom

Basically, constructor's arguments allow to provide alternate, secure elements, that will be injected in the evaluation context of any expression that will be evaluated by pod. Then, every time pod will need to evaluate a Python expression, the evaluator will call RestrictedPython, that will evaluate it in this updated, secured context. Arg builtins, for example, defines a sub-set of standard Python __builtins__. Because, for every element for which a secure replacement may be used, several functions may be proposed (either by RestrictedPython itself or by third-party frameworks), the Evaluator constructor proposes args allowing to provide specific values, fallbacking to default ones if no value is passed. For example, RestrictedPython provides several variants for the __builtins__ sub-set: safe_builtins, limited_builtins or utility_builtins. appy.pod.restricted.Evaluator's default one is safe_builtins.

An exemple: using RestrictedPython from Zope

For those using the Zope application framework, here is an example of how to configure Zope-specific RestrictedPython rules.

By default, an instance of class appy.pod.restricted.Evaluator builds, from its attributes, a dict of all the RestrictedPython-specific entries that will need to be injected in the context of any expression to be evaluated via pod. The problem is that Zope already provides a function that builds such dict: AccessControl.ZopeGuards.get_safe_globals. This is why Evaluator's custom attribute has been foreseen: if not empty, all other Evaluator attributes will be ignored: the custom dict being used instead. Moreover, Zope also provides his own function for replacing the standard getattr function, in AccessControl.ZopeGuards.guarded_getattr. Consequently, here is how to define an Evaluator instance to be used from Zope.

from AccessControl.ZopeGuards import get_safe_globals
from AccessControl.ZopeGuards import guarded_getattr
 
from appy.pod.renderer import Renderer
from appy.pod.restricted import Evaluator
 
rpEvaluator = Evaluator(attr=guarded_getattr, custom=get_safe_globals())
 
# Then, pass the evaluator to the Renderer
renderer = Renderer(..., evaluator=rpEvaluator, ...)