Skip to content

Commit

Permalink
Merge pull request #2 from collective/MOD-833_trusted_expr
Browse files Browse the repository at this point in the history
Added parameter `trusted=False` to `utils._evaluateExpression`
  • Loading branch information
sgeulette committed Jan 7, 2021
2 parents 08c885d + ccc502e commit 719df19
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 8 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Expand Up @@ -13,6 +13,9 @@ Changelog
[gbastien]
- Adapted code (except, implementer) to be Python3 compatible.
[gbastien]
- Added parameter `trusted=False` to `utils._evaluateExpression`, this will use
a trusted expression handler instead the restricted python default.
[gbastien]

0.11 (2019-05-16)
-----------------
Expand Down
1 change: 1 addition & 0 deletions buildout.d/versions.cfg
Expand Up @@ -3,6 +3,7 @@ setuptools = 38.5.1
zc.buildout = 2.13.2
ipdb = 0.11
ipython = 5.7.0
traitlets = 4.3.3

#zc.recipe.egg = 2.0.0
#coverage = 3.7.1
Expand Down
14 changes: 14 additions & 0 deletions src/collective/behavior/talcondition/tests/test_utils.py
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-

from AccessControl import Unauthorized
from collective.behavior.talcondition import PLONE_VERSION
from collective.behavior.talcondition.behavior import ITALCondition
from collective.behavior.talcondition.interfaces import ITALConditionable
Expand Down Expand Up @@ -95,3 +97,15 @@ def test_raise_on_error(self):
self.adapted.tal_condition = u'python: context.some_unexisting_method()'
self.assertFalse(evaluateExpressionFor(self.adapted))
self.assertRaises(AttributeError, evaluateExpressionFor, self.adapted, raise_on_error=True)

def test_trusted(self):
self.assertRaises(Unauthorized,
_evaluateExpression,
self.portal,
expression='python: context.unrestrictedTraverse("view")',
raise_on_error=True)
self.assertTrue(_evaluateExpression(
self.portal,
expression='python: context.unrestrictedTraverse("view")',
raise_on_error=True,
trusted=True))
97 changes: 89 additions & 8 deletions src/collective/behavior/talcondition/utils.py
@@ -1,9 +1,12 @@
# -*- coding: utf-8 -*-

from App.class_init import InitializeClass
from collective.behavior.talcondition import PLONE_VERSION
from plone import api
from Products.CMFCore.Expression import createExprContext
from Products.CMFCore.Expression import Expression
from Products.PageTemplates.Expressions import createTrustedZopeEngine
from Products.PageTemplates.Expressions import SecureModuleImporter

import logging
import unittest
Expand All @@ -13,7 +16,11 @@
WRONG_TAL_CONDITION = "The TAL expression '{0}' for element at '{1}' is wrong. Original exception : {2}"


def evaluateExpressionFor(obj, extra_expr_ctx={}, error_pattern=WRONG_TAL_CONDITION, raise_on_error=False):
def evaluateExpressionFor(obj,
extra_expr_ctx={},
error_pattern=WRONG_TAL_CONDITION,
raise_on_error=False,
trusted=False):
"""Evaluate the expression stored in 'tal_condition' of given p_obj.
"""
# get tal_condition
Expand All @@ -28,7 +35,8 @@ def evaluateExpressionFor(obj, extra_expr_ctx={}, error_pattern=WRONG_TAL_CONDIT
roles_bypassing_expression=roles_bypassing_talcondition,
extra_expr_ctx=extra_expr_ctx,
error_pattern=error_pattern,
raise_on_error=raise_on_error)
raise_on_error=raise_on_error,
trusted=trusted)


def _evaluateExpression(obj,
Expand All @@ -37,7 +45,8 @@ def _evaluateExpression(obj,
extra_expr_ctx={},
empty_expr_is_true=True,
error_pattern=WRONG_TAL_CONDITION,
raise_on_error=False):
raise_on_error=False,
trusted=False):
"""Evaluate given p_expression extending expression context with p_extra_expr_ctx."""
if not expression or not expression.strip():
return empty_expr_is_true
Expand All @@ -48,27 +57,99 @@ def _evaluateExpression(obj,
if member.has_role(str(role), obj):
return res
portal = api.portal.get()
ctx = createExprContext(obj.aq_inner.aq_parent,
portal,
obj)
if trusted:
ctx = createTrustedExprContext(obj.aq_inner.aq_parent,
portal,
obj)
expr_handler = TrustedExpression
else:
ctx = createExprContext(obj.aq_inner.aq_parent,
portal,
obj)
expr_handler = Expression
ctx.setGlobal('member', member)
ctx.setGlobal('context', obj)
ctx.setGlobal('portal', portal)
for extra_key, extra_value in extra_expr_ctx.items():
ctx.setGlobal(extra_key, extra_value)

if raise_on_error:
res = Expression(expression)(ctx)
res = expr_handler(expression)(ctx)
else:
try:
res = Expression(expression)(ctx)
res = expr_handler(expression)(ctx)
except Exception as e:
logger.warn(error_pattern.format(
expression, obj.absolute_url(), str(e)))
res = False
return res


def createTrustedExprContext(folder, portal, object):
'''
Expression evaluator trusted (not restricted python)
Same as createExprContext but use the trusted engine.
'''
pm = api.portal.get_tool('portal_membership')
if object is None:
object_url = ''
else:
object_url = object.absolute_url()
if pm.isAnonymousUser():
member = None
else:
member = pm.getAuthenticatedMember()
data = {
'object_url': object_url,
'folder_url': folder.absolute_url(),
'portal_url': portal.absolute_url(),
'object': object,
'folder': folder,
'portal': portal,
'nothing': None,
'request': getattr(portal, 'REQUEST', None),
'modules': SecureModuleImporter,
'member': member,
'here': object,
}
return getTrustedEngine().getContext(data)


_trusted_engine = createTrustedZopeEngine()


def getTrustedEngine():
""" """
return _trusted_engine


class TrustedExpression(Expression):
""" """

text = ''
_v_compiled = None

def __init__(self, text):
self.text = text
if text.strip():
self._v_compiled = getTrustedEngine().compile(text)

def __call__(self, econtext):
if not self.text.strip():
return ''
compiled = self._v_compiled
if compiled is None:
compiled = self._v_compiled = getTrustedEngine().compile(self.text)
# ?? Maybe expressions should manipulate the security
# context stack.
res = compiled(econtext)
if isinstance(res, Exception):
raise res
return res

InitializeClass(Expression)


@unittest.skipIf(PLONE_VERSION >= 5, 'Archetypes extender skipped in Plone 5')
def applyExtender(portal, meta_types):
"""
Expand Down

0 comments on commit 719df19

Please sign in to comment.