[IMP] tools.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. (use jinja sandboxed environment) This is done on purpose: it prevents incidental or malicious execution of Python code that may break the security of the server. bzr revid: chm@openerp.com-20131009124032-elygz03eg23uq1yp
This commit is contained in:
parent
00532ab64e
commit
a7a2241bca
|
@ -2,7 +2,7 @@ import logging
|
|||
import re
|
||||
|
||||
import werkzeug.utils
|
||||
#from openerp.tools.safe_eval import safe_eval as eval
|
||||
from openerp.tools.safe_eval_qweb import safe_eval_qweb as eval, UndefinedError, SecurityError
|
||||
|
||||
import xml # FIXME use lxml
|
||||
import traceback
|
||||
|
@ -10,42 +10,6 @@ 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,
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
class QWebXml(object):
|
||||
"""QWeb Xml templating engine
|
||||
|
@ -112,6 +76,11 @@ class QWebXml(object):
|
|||
return eval(expr, None, v)
|
||||
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()))
|
||||
|
||||
|
@ -122,6 +91,7 @@ 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)
|
||||
|
@ -156,7 +126,6 @@ class QWebXml(object):
|
|||
v['__caller__'] = stack[-1]
|
||||
stack.append(tname)
|
||||
v['__stack__'] = stack
|
||||
v = QWebContext(v, self.undefined_handler)
|
||||
return self.render_node(self.get_template(tname), v)
|
||||
|
||||
def render_node(self, e, v):
|
||||
|
@ -275,7 +244,7 @@ class QWebXml(object):
|
|||
enum = self.eval_object(expr, v)
|
||||
if enum is not None:
|
||||
var = t_att.get('as', expr).replace('.', '_')
|
||||
d = QWebContext(v.copy(), self.undefined_handler)
|
||||
d = v.copy()
|
||||
size = -1
|
||||
if isinstance(enum, (list, tuple)):
|
||||
size = len(enum)
|
||||
|
@ -316,7 +285,7 @@ class QWebXml(object):
|
|||
if "import" in t_att:
|
||||
d = v
|
||||
else:
|
||||
d = QWebContext(v.copy(), self.undefined_handler)
|
||||
d = v.copy()
|
||||
d[0] = self.render_element(e, t_att, g_att, d)
|
||||
return self.render(self.eval_format(t_att["call"], d), d)
|
||||
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
# Copyright (C) 2004-2012 OpenERP s.a. (<http://www.openerp.com>).
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
"""
|
||||
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")]
|
||||
|
||||
|
||||
class qWebSandboxedEnvironment(SandboxedEnvironment):
|
||||
def is_safe_attribute(self, obj, attr, value):
|
||||
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)
|
||||
|
||||
env = qWebSandboxedEnvironment(variable_start_string="${", variable_end_string="}")
|
||||
env.globals.update(context)
|
||||
|
||||
# use jinja environment
|
||||
return env.compile_expression(expr)()
|
Loading…
Reference in New Issue