From dbd1b3e7dd97e90778eb4fb515918ce3fde5a498 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Sun, 13 Oct 2013 02:07:11 +0200 Subject: [PATCH] restore qwebcontext, comment out sandboxed eval till a proper (fast) implementation is ready bzr revid: al@openerp.com-20131013000711-dwqgj803nfdjcz3d --- openerp/tools/qweb.py | 97 ++++++++++++++++++++++---- openerp/tools/safe_eval_qweb.py | 120 -------------------------------- 2 files changed, 82 insertions(+), 135 deletions(-) delete mode 100644 openerp/tools/safe_eval_qweb.py diff --git a/openerp/tools/qweb.py b/openerp/tools/qweb.py index 0898d81ef1b..2683ea0b1ca 100644 --- a/openerp/tools/qweb.py +++ b/openerp/tools/qweb.py @@ -1,15 +1,88 @@ +import datetime +import dateutil import logging import re +import traceback +import urllib +import xml # FIXME use lxml and etree import werkzeug.utils -from openerp.tools.safe_eval_qweb import safe_eval_qweb as eval, UndefinedError, SecurityError -import xml # FIXME use lxml -import traceback from openerp.osv import osv, orm _logger = logging.getLogger(__name__) +BUILTINS = { + 'False': False, + 'None': None, + 'True': True, + 'abs': abs, + 'bool': bool, + 'dict': dict, + 'filter': filter, + 'len': len, + 'list': list, + 'map': map, + 'max': max, + 'min': min, + 'reduce': reduce, + 'repr': repr, + 'round': round, + 'set': set, + 'str': str, + 'tuple': tuple, + 'quote': urllib.quote, + 'urlencode': urllib.urlencode, + 'datetime': datetime, + # dateutil.relativedelta is an old-style class and cannot be directly + # instanciated wihtin a jinja2 expression, so a lambda "proxy" is + # is needed, apparently. + 'relativedelta': lambda *a, **kw : dateutil.relativedelta.relativedelta(*a, **kw), +} + +## We use a jinja2 sandboxed environment to render qWeb templates. +#from openerp.tools.safe_eval import safe_eval as eval +#from jinja2.sandbox import SandboxedEnvironment +#from jinja2.exceptions import SecurityError, UndefinedError +#UNSAFE = ["browse", "search", "read", "unlink", "read_group"] +#SAFE = ["_name"] + +class QWebContext(dict): + def __init__(self, data, undefined_handler=None): + self.undefined_handler = undefined_handler + dic = BUILTINS.copy() + dic.update(data) + super(QWebContext, self).__init__(dic) + self['defined'] = lambda key: key in self + + def __getitem__(self, key): + if key in self: + return self.get(key) + elif not self.undefined_handler: + raise NameError("QWeb: name %r is not defined while rendering template %r" % (key, self.get('__template__'))) + else: + return self.get(key, self.undefined_handler(key, self)) + + def safe_eval(self, expr): + # This is too slow, we should cached compiled expressions attribute of + # qweb to will be changed into a model object ir.qweb. + # + # The cache should be on qweb, and qweb context contructor take qweb as + # argument to store the cache. + # + #class QWebSandboxedEnvironment(SandboxedEnvironment): + # def is_safe_attribute(self, obj, attr, value): + # if str(attr) in SAFE: + # res = True + # else: + # res = super(QWebSandboxedEnvironment, self).is_safe_attribute(obj, attr, value) + # if str(attr) in UNSAFE or not res: + # raise SecurityError("access to attribute '%s' of '%s' object is unsafe." % (attr,obj)) + # return res + #env = qWebSandboxedEnvironment(variable_start_string="${", variable_end_string="}") + #env.globals.update(context) + #env.compile_expression(expr)() + return eval(expr, None, self) class QWebXml(object): """QWeb Xml templating engine @@ -73,14 +146,9 @@ class QWebXml(object): def eval(self, expr, v): try: - return eval(expr, None, v) + return v.safe_eval(expr) except (osv.except_osv, orm.except_orm), err: raise orm.except_orm("QWeb Error", "Invalid expression %r while rendering template '%s'.\n\n%s" % (expr, v.get('__template__'), err[1])) - except (UndefinedError, SecurityError), err: - if self.undefined_handler: - return self.undefined_handler(expr, v) - else: - raise SyntaxError(err.message) except Exception: raise SyntaxError("QWeb: invalid expression %r while rendering template '%s'.\n\n%s" % (expr, v.get('__template__'), traceback.format_exc())) @@ -91,7 +159,6 @@ class QWebXml(object): if expr == "0": return v.get(0, '') val = self.eval(expr, v) - if isinstance(val, unicode): return val.encode("utf8") return str(val) @@ -126,7 +193,7 @@ class QWebXml(object): v['__caller__'] = stack[-1] stack.append(tname) v['__stack__'] = stack - v['xmlid'] = str(stack[0]) + v = QWebContext(v, self.undefined_handler) return self.render_node(self.get_template(tname), v) def render_node(self, e, v): @@ -245,7 +312,7 @@ class QWebXml(object): enum = self.eval_object(expr, v) if enum is not None: var = t_att.get('as', expr).replace('.', '_') - d = v.copy() + d = QWebContext(v.copy(), self.undefined_handler) size = -1 if isinstance(enum, (list, tuple)): size = len(enum) @@ -286,7 +353,7 @@ class QWebXml(object): if "import" in t_att: d = v else: - d = v.copy() + d = QWebContext(v.copy(), self.undefined_handler) d[0] = self.render_element(e, t_att, g_att, d) return self.render(self.eval_format(t_att["call"], d), d) @@ -314,7 +381,7 @@ class QWebXml(object): column = record._model._all_columns[field].column field_type = column._type - req = v['_request'] + req = v['request'] converter = req.registry['ir.fields.converter'].from_field( req.cr, req.uid, record._model, column, totype='html') @@ -341,4 +408,4 @@ class QWebXml(object): return self.render_element(e, t_att, g_att, v, content or "") -# leave this, al. +# vim:et: diff --git a/openerp/tools/safe_eval_qweb.py b/openerp/tools/safe_eval_qweb.py deleted file mode 100644 index a1ec46133be..00000000000 --- a/openerp/tools/safe_eval_qweb.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# Copyright (C) 2004-2012 OpenERP s.a. (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -""" -safe_eval_qweb - methods intended to provide more restricted alternatives - to evaluate simple and/or untrusted code, objects and browse record. - -Methods in this module are typically used as alternatives to eval() to parse -OpenERP domain strings, conditions and expressions, mostly based on locals -condition/math builtins and browse record values. - -Only safe Python attributes of objects may be accessed. - -Unsafe / not accepted attributes: -* All "private" attributes (not starting with '_') -* browse -* search -* read -* unlink -* read_group - -This is done on purpose: it prevents incidental or malicious execution of -Python code that may break the security of the server. - -""" - -from urllib import urlencode, quote as quote -import datetime -import dateutil.relativedelta as relativedelta -import logging - -# We use a jinja2 sandboxed environment to render qWeb templates. -from jinja2.sandbox import SandboxedEnvironment -from jinja2.exceptions import SecurityError, UndefinedError - - -_logger = logging.getLogger(__name__) - - -BUILTINS = { - 'False': False, - 'None': None, - 'True': True, - 'abs': abs, - 'bool': bool, - 'dict': dict, - 'filter': filter, - 'len': len, - 'list': list, - 'map': map, - 'max': max, - 'min': min, - 'reduce': reduce, - 'repr': repr, - 'round': round, - 'set': set, - 'str': str, - 'tuple': tuple, - 'str': str, - 'quote': quote, - 'urlencode': urlencode, - 'datetime': datetime, - # dateutil.relativedelta is an old-style class and cannot be directly - # instanciated wihtin a jinja2 expression, so a lambda "proxy" is - # is needed, apparently. - 'relativedelta': lambda *a, **kw : relativedelta.relativedelta(*a, **kw), -} -UNSAFE = [str("browse"), str("search"), str("read"), str("unlink"), str("read_group")] -SAFE = [str("_name")] - - -class qWebSandboxedEnvironment(SandboxedEnvironment): - def is_safe_attribute(self, obj, attr, value): - if str(attr) in SAFE: - res = True - else: - res = super(qWebSandboxedEnvironment, self).is_safe_attribute(obj, attr, value) - if str(attr) in UNSAFE or not res: - raise SecurityError("access to attribute '%s' of '%s' object is unsafe." % (attr,obj)) - return res - -def safe_eval_qweb(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=False): - if globals_dict is None: - globals_dict = {} - if locals_dict is None: - locals_dict = {} - - if not isinstance(locals_dict, dict): - _logger.warning("The globals_dict and locals_dict of the dynamic environment "+ - "pass to safe_eval_qweb must be dict") - - context = dict(globals_dict) - context = dict(locals_dict) - context.update(BUILTINS) - - for key, val in context.items(): - if str(key).startswith('_'): - context.pop(key) - - env = qWebSandboxedEnvironment(variable_start_string="${", variable_end_string="}") - env.globals.update(context) - - # use jinja environment - return env.compile_expression(expr)()