summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Howitz <icemac@gmx.net>2023-02-07 11:36:18 +0100
committerGitHub <noreply@github.com>2023-02-07 11:36:18 +0100
commitf7b94db50380db535bad8bd7783f72470eda257c (patch)
tree4153ef2c4a2ff4ac709338d55baa51a967ebd4c0
parent8f8ed8f7b603792b48401a64b51e900e86b88a82 (diff)
downloadzope-pagetemplate-f7b94db50380db535bad8bd7783f72470eda257c.tar.gz
Add support for ``zope.untrustedpython`` on Python 3. (#29)
With it, Python expressions are now protected by RestrictedPython.
-rw-r--r--.meta.toml3
-rw-r--r--CHANGES.rst7
-rw-r--r--setup.py2
-rw-r--r--src/zope/pagetemplate/engine.py27
-rw-r--r--src/zope/pagetemplate/tests/test_engine.py30
-rw-r--r--tox.ini2
6 files changed, 44 insertions, 27 deletions
diff --git a/.meta.toml b/.meta.toml
index f0ea76b..6392b54 100644
--- a/.meta.toml
+++ b/.meta.toml
@@ -14,6 +14,9 @@ with-macos = false
[tox]
use-flake8 = true
+testenv-additional-extras = [
+ "untrusted",
+ ]
[coverage]
fail-under = 97
diff --git a/CHANGES.rst b/CHANGES.rst
index 937553f..1c56372 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -2,8 +2,11 @@
Changes
=========
-5.0 (unreleased)
-================
+5.0.0 (unreleased)
+==================
+
+- Add support for ``zope.untrustedpython`` on Python 3. With it, Python
+ expressions are now protected. It is activated using the ``untrusted`` extra.
- Add support for Python 3.11.
diff --git a/setup.py b/setup.py
index 4b2fb6d..fd2ddba 100644
--- a/setup.py
+++ b/setup.py
@@ -34,6 +34,7 @@ TESTS_REQUIRE = [
'zope.security',
'zope.testing',
'zope.testrunner',
+ 'zope.untrustedpython >= 5.0.dev0',
]
@@ -75,6 +76,7 @@ setup(name='zope.pagetemplate',
extras_require={
'test': TESTS_REQUIRE,
'untrusted': [
+ 'zope.untrustedpython >= 5.0.dev0',
],
'docs': [
'Sphinx',
diff --git a/src/zope/pagetemplate/engine.py b/src/zope/pagetemplate/engine.py
index 662dbcd..9b61498 100644
--- a/src/zope/pagetemplate/engine.py
+++ b/src/zope/pagetemplate/engine.py
@@ -33,13 +33,17 @@ from zope.traversing.interfaces import TraversalError
from zope import component
-try: # pragma: no cover
- # Until https://github.com/zopefoundation/zope.untrustedpython/issues/2
- # is fixed Python 3 does not support special handling for untrusted code:
+try:
+ # The ``untrusted`` extra is needed to have zope.untrustedpython:
from zope.untrustedpython import rcompile
from zope.untrustedpython.builtins import SafeBuiltins
+
+ def guarded_getitem(ob, index):
+ """getitem access which gets guarded in the next line."""
+ return ob[index]
+ guarded_getitem = ProxyFactory(guarded_getitem)
HAVE_UNTRUSTED = True
-except ImportError:
+except ImportError: # pragma: no cover
HAVE_UNTRUSTED = False
# PyPy doesn't support assigning to '__builtins__', even when using eval()
@@ -128,6 +132,8 @@ class ZopePythonExpr(PythonExpr):
def __call__(self, econtext):
__traceback_info__ = self.text
vars = self._bind_used_names(econtext, SafeBuiltins)
+ vars['_getattr_'] = SafeBuiltins.getattr
+ vars['_getitem_'] = guarded_getitem
return eval(self._code, vars)
def _compile(self, text, filename):
@@ -363,14 +369,15 @@ class ZopeEngine(ZopeBaseEngine):
wrapped in security proxies if the 'untrusted' extra is installed::
>>> r = context.evaluate('python: {12: object()}.values')
- >>> str(type(r).__name__) in (
- ... ('_Proxy',) if HAVE_UNTRUSTED else
- ... ('builtin_function_or_method', 'method', 'instancemethod'))
+ >>> str(type(r).__name__) if HAVE_UNTRUSTED else '_Proxy'
+ '_Proxy'
+ >>> ((str(type(r).__name__) in ('method', 'instancemethod'))
+ ... if not HAVE_UNTRUSTED else True)
True
- >>> r = context.evaluate('python: {12: object()}[12].__class__')
- >>> str(type(r).__name__) == '_Proxy' or not HAVE_UNTRUSTED
- True
+ >>> r = context.evaluate('python: {12: (1, 2, 3)}[12]')
+ >>> str(type(r).__name__) if HAVE_UNTRUSTED else '_Proxy'
+ '_Proxy'
General path expressions provide objects that are wrapped in
security proxies as well::
diff --git a/src/zope/pagetemplate/tests/test_engine.py b/src/zope/pagetemplate/tests/test_engine.py
index 946cfba..714d4d2 100644
--- a/src/zope/pagetemplate/tests/test_engine.py
+++ b/src/zope/pagetemplate/tests/test_engine.py
@@ -51,11 +51,7 @@ class DummyEngine:
return {}
def getCompilerError(self):
- # This is only here to get meaningful errors if RestrictedPython denies
- # execution of some code.
- def get_error(text): # pragma: no cover
- raise RuntimeError(text) # pragma: no cover
- return get_error # pragma: no cover
+ return SyntaxError # pragma: no cover
class DummyContext:
@@ -73,21 +69,25 @@ class ZopePythonExprTests(unittest.TestCase):
expr = ZopePythonExpr('python', 'max(a,b)', DummyEngine())
self.assertEqual(expr(DummyContext(a=1, b=2)), 2)
- def test_allowed_module_name(self):
+ def test_import_not_possible(self):
from zope.pagetemplate.engine import ZopePythonExpr
- expr = ZopePythonExpr('python', '__import__("sys").__name__',
- DummyEngine())
- self.assertEqual(expr(DummyContext()), 'sys')
+ with self.assertRaises(SyntaxError) as err:
+ ZopePythonExpr('python', 'import sys', DummyEngine())
+ if zope.pagetemplate.engine.HAVE_UNTRUSTED:
+ self.assertIn(
+ 'SyntaxError: invalid syntax at statement', str(err.exception))
+ else:
+ self.assertEqual(
+ 'invalid syntax (<string>, line 1)', str(err.exception))
@unittest.skipUnless(zope.pagetemplate.engine.HAVE_UNTRUSTED,
"Needs untrusted")
- def test_forbidden_module_name(self):
- from zope.security.interfaces import Forbidden
-
+ def test___import___not_allowed(self):
from zope.pagetemplate.engine import ZopePythonExpr
- expr = ZopePythonExpr('python', '__import__("sys").exit',
- DummyEngine())
- self.assertRaises(Forbidden, expr, DummyContext())
+ with self.assertRaises(SyntaxError) as err:
+ ZopePythonExpr('python', '__import__("sys")', DummyEngine())
+ self.assertIn(
+ '"__import__" is an invalid variable', str(err.exception))
@unittest.skipUnless(zope.pagetemplate.engine.HAVE_UNTRUSTED,
"Needs untrusted")
diff --git a/tox.ini b/tox.ini
index 9f935b5..c251c8d 100644
--- a/tox.ini
+++ b/tox.ini
@@ -16,12 +16,14 @@ envlist =
[testenv]
usedevelop = true
deps =
+ zope.testrunner
commands =
zope-testrunner --test-path=src {posargs:-vc}
sphinx-build -b doctest -d {envdir}/.cache/doctrees docs {envdir}/.cache/doctest
extras =
test
docs
+ untrusted
[testenv:lint]
basepython = python3