[FIX] base_import_module, tools: raise a clear error for unsupported file types

Also restrict XML data attribute evaluation context
even for real module data files. This will prevent
accidentally depending on context parameters that
would not be available inside base_import_module.
This commit is contained in:
Olivier Dony 2016-09-09 15:08:10 +02:00
parent ca18a1699c
commit 12634e1227
No known key found for this signature in database
GPG Key ID: CD556E25E8A6D0D4
2 changed files with 23 additions and 22 deletions

View File

@ -41,6 +41,10 @@ class view(osv.osv):
for kind in ['data', 'init_xml', 'update_xml']: for kind in ['data', 'init_xml', 'update_xml']:
for filename in terp[kind]: for filename in terp[kind]:
ext = os.path.splitext(filename)[1].lower()
if ext not in ('.xml', '.csv', '.sql'):
_logger.info("module %s: skip unsupported file %s", module, filename)
continue
_logger.info("module %s: loading %s", module, filename) _logger.info("module %s: loading %s", module, filename)
noupdate = False noupdate = False
if filename.endswith('.csv') and kind in ('init', 'init_xml'): if filename.endswith('.csv') and kind in ('init', 'init_xml'):

View File

@ -61,11 +61,8 @@ from misc import pickle, unquote
from openerp import SUPERUSER_ID from openerp import SUPERUSER_ID
# Import of XML records requires the unsafe eval as well, from safe_eval import safe_eval as s_eval
# almost everywhere, which is ok because it supposedly comes safe_eval = lambda expr, ctx={}: s_eval(expr, ctx, nocopy=True)
# from trusted data, but at least we make it obvious now.
unsafe_eval = eval
from safe_eval import safe_eval as eval
class ParseError(Exception): class ParseError(Exception):
def __init__(self, msg, text, filename, lineno): def __init__(self, msg, text, filename, lineno):
@ -130,7 +127,7 @@ def _eval_xml(self, node, pool, cr, uid, idref, context=None):
idref2 = {} idref2 = {}
if f_search: if f_search:
idref2 = _get_idref(self, cr, uid, f_model, context, idref) idref2 = _get_idref(self, cr, uid, f_model, context, idref)
q = unsafe_eval(f_search, idref2) q = safe_eval(f_search, idref2)
ids = pool[f_model].search(cr, uid, q) ids = pool[f_model].search(cr, uid, q)
if f_use != 'id': if f_use != 'id':
ids = map(lambda x: x[f_use], pool[f_model].read(cr, uid, ids, [f_use])) ids = map(lambda x: x[f_use], pool[f_model].read(cr, uid, ids, [f_use]))
@ -147,7 +144,7 @@ def _eval_xml(self, node, pool, cr, uid, idref, context=None):
if a_eval: if a_eval:
idref2 = _get_idref(self, cr, uid, f_model, context, idref) idref2 = _get_idref(self, cr, uid, f_model, context, idref)
try: try:
return unsafe_eval(a_eval, idref2) return safe_eval(a_eval, idref2)
except Exception: except Exception:
logging.getLogger('openerp.tools.convert.init').error( logging.getLogger('openerp.tools.convert.init').error(
'Could not eval(%s) for %s in %s', a_eval, node.get('name'), context) 'Could not eval(%s) for %s in %s', a_eval, node.get('name'), context)
@ -219,7 +216,7 @@ def _eval_xml(self, node, pool, cr, uid, idref, context=None):
# FIXME: should probably be exclusive # FIXME: should probably be exclusive
if a_eval: if a_eval:
idref['ref'] = lambda x: self.id_get(cr, x) idref['ref'] = lambda x: self.id_get(cr, x)
args = unsafe_eval(a_eval, idref) args = safe_eval(a_eval, idref)
for n in node: for n in node:
return_val = _eval_xml(self,n, pool, cr, uid, idref, context) return_val = _eval_xml(self,n, pool, cr, uid, idref, context)
if return_val is not None: if return_val is not None:
@ -255,12 +252,12 @@ class xml_import(object):
for ctx in (data_node_context, node_context): for ctx in (data_node_context, node_context):
if ctx: if ctx:
try: try:
ctx_res = unsafe_eval(ctx, eval_dict) ctx_res = safe_eval(ctx, eval_dict)
if isinstance(context, dict): if isinstance(context, dict):
context.update(ctx_res) context.update(ctx_res)
else: else:
context = ctx_res context = ctx_res
except NameError: except ValueError, NameError:
# Some contexts contain references that are only valid at runtime at # Some contexts contain references that are only valid at runtime at
# client-side, so in that case we keep the original context string # client-side, so in that case we keep the original context string
# as it is. We also log it, just in case. # as it is. We also log it, just in case.
@ -299,7 +296,7 @@ form: module.record_id""" % (xml_id,)
if d_search: if d_search:
idref = _get_idref(self, cr, self.uid, d_model, context={}, idref={}) idref = _get_idref(self, cr, self.uid, d_model, context={}, idref={})
try: try:
ids = self.pool[d_model].search(cr, self.uid, unsafe_eval(d_search, idref)) ids = self.pool[d_model].search(cr, self.uid, safe_eval(d_search, idref))
except ValueError: except ValueError:
_logger.warning('Skipping deletion for failed search `%r`', d_search, exc_info=True) _logger.warning('Skipping deletion for failed search `%r`', d_search, exc_info=True)
pass pass
@ -332,14 +329,14 @@ form: module.record_id""" % (xml_id,)
if rec.get(field): if rec.get(field):
res[dest] = rec.get(field).encode('utf8') res[dest] = rec.get(field).encode('utf8')
if rec.get('auto'): if rec.get('auto'):
res['auto'] = eval(rec.get('auto','False')) res['auto'] = safe_eval(rec.get('auto','False'))
if rec.get('sxw'): if rec.get('sxw'):
sxw_content = misc.file_open(rec.get('sxw')).read() sxw_content = misc.file_open(rec.get('sxw')).read()
res['report_sxw_content'] = sxw_content res['report_sxw_content'] = sxw_content
if rec.get('header'): if rec.get('header'):
res['header'] = eval(rec.get('header','False')) res['header'] = safe_eval(rec.get('header','False'))
res['multi'] = rec.get('multi') and eval(rec.get('multi','False')) res['multi'] = rec.get('multi') and safe_eval(rec.get('multi','False'))
xml_id = rec.get('id','').encode('utf8') xml_id = rec.get('id','').encode('utf8')
self._test_xml_id(xml_id) self._test_xml_id(xml_id)
@ -359,12 +356,12 @@ form: module.record_id""" % (xml_id,)
id = self.pool['ir.model.data']._update(cr, self.uid, "ir.actions.report.xml", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode) id = self.pool['ir.model.data']._update(cr, self.uid, "ir.actions.report.xml", self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode)
self.idref[xml_id] = int(id) self.idref[xml_id] = int(id)
if not rec.get('menu') or eval(rec.get('menu','False')): if not rec.get('menu') or safe_eval(rec.get('menu','False')):
keyword = str(rec.get('keyword', 'client_print_multi')) keyword = str(rec.get('keyword', 'client_print_multi'))
value = 'ir.actions.report.xml,'+str(id) value = 'ir.actions.report.xml,'+str(id)
replace = rec.get('replace', True) replace = rec.get('replace', True)
self.pool['ir.model.data'].ir_set(cr, self.uid, 'action', keyword, res['name'], [res['model']], value, replace=replace, isobject=True, xml_id=xml_id) self.pool['ir.model.data'].ir_set(cr, self.uid, 'action', keyword, res['name'], [res['model']], value, replace=replace, isobject=True, xml_id=xml_id)
elif self.mode=='update' and eval(rec.get('menu','False'))==False: elif self.mode=='update' and safe_eval(rec.get('menu','False'))==False:
# Special check for report having attribute menu=False on update # Special check for report having attribute menu=False on update
value = 'ir.actions.report.xml,'+str(id) value = 'ir.actions.report.xml,'+str(id)
self._remove_ir_values(cr, res['name'], value, res['model']) self._remove_ir_values(cr, res['name'], value, res['model'])
@ -448,7 +445,7 @@ form: module.record_id""" % (xml_id,)
context = self.get_context(data_node, rec, eval_context) context = self.get_context(data_node, rec, eval_context)
try: try:
domain = unsafe_eval(domain, eval_context) domain = safe_eval(domain, eval_context)
except NameError: except NameError:
# Some domains contain references that are only valid at runtime at # Some domains contain references that are only valid at runtime at
# client-side, so in that case we keep the original domain string # client-side, so in that case we keep the original domain string
@ -486,7 +483,7 @@ form: module.record_id""" % (xml_id,)
if rec.get('target'): if rec.get('target'):
res['target'] = rec.get('target','') res['target'] = rec.get('target','')
if rec.get('multi'): if rec.get('multi'):
res['multi'] = eval(rec.get('multi', 'False')) res['multi'] = safe_eval(rec.get('multi', 'False'))
id = self.pool['ir.model.data']._update(cr, self.uid, 'ir.actions.act_window', self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode) id = self.pool['ir.model.data']._update(cr, self.uid, 'ir.actions.act_window', self.module, res, xml_id, noupdate=self.isnoupdate(data_node), mode=self.mode)
self.idref[xml_id] = int(id) self.idref[xml_id] = int(id)
@ -643,7 +640,7 @@ form: module.record_id""" % (xml_id,)
if rec_id: if rec_id:
ids = [self.id_get(cr, rec_id)] ids = [self.id_get(cr, rec_id)]
elif rec_src: elif rec_src:
q = unsafe_eval(rec_src, eval_dict) q = safe_eval(rec_src, eval_dict)
ids = self.pool[rec_model].search(cr, uid, q, context=context) ids = self.pool[rec_model].search(cr, uid, q, context=context)
if rec_src_count: if rec_src_count:
count = int(rec_src_count) count = int(rec_src_count)
@ -674,7 +671,7 @@ form: module.record_id""" % (xml_id,)
for test in rec.findall('./test'): for test in rec.findall('./test'):
f_expr = test.get("expr",'').encode('utf-8') f_expr = test.get("expr",'').encode('utf-8')
expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True
expression_value = unsafe_eval(f_expr, globals_dict) expression_value = safe_eval(f_expr, globals_dict)
if expression_value != expected_value: # assertion failed if expression_value != expected_value: # assertion failed
self.assertion_report.record_failure() self.assertion_report.record_failure()
msg = 'assertion "%s" failed!\n' \ msg = 'assertion "%s" failed!\n' \
@ -693,7 +690,7 @@ form: module.record_id""" % (xml_id,)
rec_id = rec.get("id",'').encode('ascii') rec_id = rec.get("id",'').encode('ascii')
rec_context = rec.get("context", None) rec_context = rec.get("context", None)
if rec_context: if rec_context:
rec_context = unsafe_eval(rec_context) rec_context = safe_eval(rec_context)
self._test_xml_id(rec_id) self._test_xml_id(rec_id)
# in update mode, the record won't be updated if the data node explicitely # in update mode, the record won't be updated if the data node explicitely
# opt-out using @noupdate="1". A second check will be performed in # opt-out using @noupdate="1". A second check will be performed in
@ -732,7 +729,7 @@ form: module.record_id""" % (xml_id,)
f_val = False f_val = False
if f_search: if f_search:
q = unsafe_eval(f_search, self.idref) q = safe_eval(f_search, self.idref)
assert f_model, 'Define an attribute model="..." in your .XML file !' assert f_model, 'Define an attribute model="..." in your .XML file !'
f_obj = self.pool[f_model] f_obj = self.pool[f_model]
# browse the objects searched # browse the objects searched