Skip to content

Commit

Permalink
allow (some) builtins as first element in a path expression (#866)
Browse files Browse the repository at this point in the history
* allow (some) builtins as first element in a path expression

* make isort happy

Co-authored-by: Jens Vagelpohl <jens@netz.ooo>
  • Loading branch information
d-maurer and dataflake committed Jul 6, 2020
1 parent 2c03c78 commit 400947d
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 3 deletions.
7 changes: 7 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ https://zope.readthedocs.io/en/2.13/CHANGES.html
4.4.5 (unreleased)
------------------

- Allow (some) builtins as first element of a (TALES) path expression:
in an untrusted context, the builtins from
``AccessControl.safe_builtins`` are allowed;
in a trusted context, all Python builtins are allowed in addition
(and take precedence)
(`zope.tales#23 <https://github.com/zopefoundation/zope.tales/issues/23>`_).

- Add ``tal:switch`` test

- Support the ``attrs`` predefined template variable again (as
Expand Down
4 changes: 3 additions & 1 deletion docs/zopebook/AdvZPT.rst
Original file line number Diff line number Diff line change
Expand Up @@ -750,7 +750,8 @@ Path Expressions
Path expressions refer to objects with a path that resembles a
URL path. A path describes a traversal from object to
object. All paths begin with a known object (such as a built-in
variable, a repeat variable, or a user defined variable) and
variable, a built-in (such as ``True``),
a repeat variable, or a user defined variable) and
depart from there to the desired object. Here are some example
paths expressions::

Expand All @@ -760,6 +761,7 @@ paths expressions::
container/master.html/macros/header
request/form/address
root/standard_look_and_feel.html
True

With path expressions you can traverse from an object to its
sub-objects including properties and methods. You can also use
Expand Down
12 changes: 11 additions & 1 deletion docs/zopebook/ZPT.rst
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,8 @@ be useful. We'll come back to that later in this chapter::
<p tal:content="request/URL"></p>
<p tal:content="user/getUserName"></p>

Every *path expression* starts with a variable name. The available
Every *path expression* starts either with a variable name or with
a (supported) builtin. The available
variable names refer either to objects like *context*, *request* or
*user* that are bound to every *Page Template* by default or variables
defined within the *Page Template* using TAL. Note that *here* is an
Expand All @@ -216,6 +217,15 @@ Otherwise, you add a slash ('/') and the name of a sub-object or
attribute. You may need to work your way through several
sub-objects to get to the value you're looking for.

Which builtins are supported as first element of a path
expression depends on the context. In an untrusted context,
only "safe builtins" (as specified by ``AccessControl.safe_builtins``)
are supported; in a trusted context, beside those all
Python builtins are supported in addition (and take precedence over
the former). Note that many builtins are callable. For those,
you will refer to them usually via the ``nocall`` variant of
a path expression.

Python Expressions
%%%%%%%%%%%%%%%%%%

Expand Down
35 changes: 35 additions & 0 deletions src/Products/PageTemplates/Expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from six import text_type

import OFS.interfaces
from AccessControl import safe_builtins
from Acquisition import aq_base
from MultiMapping import MultiMapping
from Products.PageTemplates import ZRPythonExpr
Expand All @@ -39,6 +40,7 @@
from zope.tales.expressions import NotExpr
from zope.tales.expressions import PathExpr
from zope.tales.expressions import StringExpr
from zope.tales.expressions import SubPathExpr
from zope.tales.expressions import Undefs
from zope.tales.pythonexpr import PythonExpr
from zope.tales.tales import Context
Expand Down Expand Up @@ -125,9 +127,41 @@ def render(ob, ns):
return ob


class _CombinedMapping(object):
"""Minimal auxiliary class to combine several mappings.
Earlier mappings take precedence.
"""
def __init__(self, *ms):
self.mappings = ms

def get(self, key, default):
for m in self.mappings:
value = m.get(key, self)
if value is not self:
return value
return default


class UntrustedSubPathExpr(SubPathExpr):
ALLOWED_BUILTINS = safe_builtins


class TrustedSubPathExpr(SubPathExpr):
# we allow both Python's builtins (we are trusted)
# as well as ``safe_builtins`` (because it may contain extensions)
# Python's builtins take precedence, because those of
# ``safe_builtins`` may have special restrictions for
# the use in an untrusted context
ALLOWED_BUILTINS = _CombinedMapping(
__builtins__,
safe_builtins)


class ZopePathExpr(PathExpr):

_TRAVERSER = staticmethod(boboAwareZopeTraverse)
SUBEXPR_FACTORY = UntrustedSubPathExpr

def __init__(self, name, expr, engine):
if not expr.strip():
Expand Down Expand Up @@ -174,6 +208,7 @@ def _exists(self, econtext):

class TrustedZopePathExpr(ZopePathExpr):
_TRAVERSER = staticmethod(trustedBoboAwareZopeTraverse)
SUBEXPR_FACTORY = TrustedSubPathExpr


class SafeMapping(MultiMapping):
Expand Down
24 changes: 24 additions & 0 deletions src/Products/PageTemplates/tests/testExpressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from six import text_type

from AccessControl import safe_builtins
from zope.component.testing import PlacelessSetup


Expand Down Expand Up @@ -199,6 +200,12 @@ def test_mixed(self):
IUnicodeEncodingConflictResolver)
self.assertEqual(ec.evaluate(expr), u'äüö')

def test_builtin_in_path_expr(self):
ec = self._makeContext()
self.assertIs(ec.evaluate('True'), True)
self.assertIs(ec.evaluate('False'), False)
self.assertIs(ec.evaluate('nocall: test'), safe_builtins["test"])


class UntrustedEngineTests(EngineTestsBase, unittest.TestCase):

Expand All @@ -208,6 +215,15 @@ def _makeEngine(self):

# XXX: add tests that show security checks being enforced

def test_open_in_path_expr(self):
ec = self._makeContext()
with self.assertRaises(KeyError):
ec.evaluate("nocall:open")

def test_list_in_path_expr(self):
ec = self._makeContext()
self.assertIs(ec.evaluate('nocall: list'), safe_builtins["list"])


class TrustedEngineTests(EngineTestsBase, unittest.TestCase):

Expand All @@ -217,6 +233,14 @@ def _makeEngine(self):

# XXX: add tests that show security checks *not* being enforced

def test_open_in_path_expr(self):
ec = self._makeContext()
self.assertIs(ec.evaluate("nocall:open"), open)

def test_list_in_path_expr(self):
ec = self._makeContext()
self.assertIs(ec.evaluate('nocall: list'), list)


class UnicodeEncodingConflictResolverTests(PlacelessSetup, unittest.TestCase):

Expand Down
2 changes: 1 addition & 1 deletion src/Products/PageTemplates/unicodeconflictresolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

from six import text_type

import ZPublisher
import ZPublisher.HTTPRequest
from Acquisition import aq_get
from Products.PageTemplates.interfaces import IUnicodeEncodingConflictResolver
from zope.i18n.interfaces import IUserPreferredCharsets
Expand Down

0 comments on commit 400947d

Please sign in to comment.