diff --git a/bin/addons/base/base_update.xml b/bin/addons/base/base_update.xml index 04d07efeafa..b93e10af2d6 100644 --- a/bin/addons/base/base_update.xml +++ b/bin/addons/base/base_update.xml @@ -78,7 +78,8 @@
- + @@ -110,7 +111,7 @@ - + diff --git a/bin/addons/base/res/res_company.py b/bin/addons/base/res/res_company.py index b14c5b403c1..c076eef612a 100644 --- a/bin/addons/base/res/res_company.py +++ b/bin/addons/base/res/res_company.py @@ -92,11 +92,18 @@ class res_company(osv.osv): def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False): - user_preference = context and context.get('user_preference', False) or False + if context is None: + context = {} + user_preference = context.get('user_preference', False) if user_preference: - user = self.pool.get('res.users').browse(cr, uid, uid, context=context) - cmp_ids = list(set([user.company_id.id] + [cmp.id for cmp in user.company_ids])) + # TODO: improve this as soon as the client sends the proper + # combination of active_id and active_model we'll be able to + # use active_id here to restrict to the user being modified instead + # of current user. + user_id = context.get('user_id', uid) + user = self.pool.get('res.users').browse(cr, uid, user_id, context=context) + cmp_ids = list(set([user.company_id.id] + [cmp.id for cmp in user.company_ids])) return cmp_ids return super(res_company, self).search(cr, uid, args, offset=offset, limit=limit, order=order, context=context, count=count) diff --git a/bin/addons/base/res/res_user.py b/bin/addons/base/res/res_user.py index 3439a00c2cb..1cf91c9d7f1 100644 --- a/bin/addons/base/res/res_user.py +++ b/bin/addons/base/res/res_user.py @@ -233,8 +233,13 @@ class users(osv.osv): 'menu_id': fields.many2one('ir.actions.actions', 'Menu Action'), 'groups_id': fields.many2many('res.groups', 'res_groups_users_rel', 'uid', 'gid', 'Groups'), 'roles_id': fields.many2many('res.roles', 'res_roles_users_rel', 'uid', 'rid', 'Roles'), + + # Special behavior for this field: res.company.search() will only return the companies + # available to the current user (should be the user's companies?), when the user_preference + # context is set. 'company_id': fields.many2one('res.company', 'Company', required=True, - help="The company this user is currently working for."), + help="The company this user is currently working for.", context={'user_preference': True}), + 'company_ids':fields.many2many('res.company','res_company_users_rel','user_id','cid','Companies'), 'context_lang': fields.selection(_lang_get, 'Language', required=True, help="Sets the language for the user's user interface, when UI " @@ -277,7 +282,7 @@ class users(osv.osv): return all(((this.company_id in this.company_ids) or not this.company_ids) for this in self.browse(cr, uid, ids, context)) _constraints = [ - (_check_company, 'The chosen company is not in the allowed companies', ['company_id', 'company_ids']), + (_check_company, 'The chosen company is not in the allowed companies for this user', ['company_id', 'company_ids']), ] _sql_constraints = [ diff --git a/bin/osv/orm.py b/bin/osv/orm.py index 40104e6570c..b166881798b 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -1228,17 +1228,23 @@ class orm_template(object): name = node.get('name') default = self.default_get(cr, user, [name], context=context).get(name) if default: - attrs['selection'] = relation.name_get(cr, 1, default, context=context) + attrs['selection'] = relation.name_get(cr, 1, [default], context=context) else: attrs['selection'] = [] # We can not use the 'string' domain has it is defined according to the record ! else: + # If domain and context are strings, we keep them for client-side, otherwise + # we evaluate them server-side to consider them when generating the list of + # possible values + # TODO: find a way to remove this hack, by allow dynamic domains dom = [] - if column._domain and not isinstance(column._domain, (str, unicode)): + if column._domain and not isinstance(column._domain, basestring): dom = column._domain dom += eval(node.get('domain','[]'), {'uid':user, 'time':time}) - context.update(eval(node.get('context','{}'))) - attrs['selection'] = relation._name_search(cr, user, '', dom, context=context, limit=None, name_get_uid=1) + search_context = dict(context) + if column._context and not isinstance(column._context, basestring): + search_context.update(column._context) + attrs['selection'] = relation._name_search(cr, user, '', dom, context=search_context, limit=None, name_get_uid=1) if (node.get('required') and not int(node.get('required'))) or not column.required: attrs['selection'].append((False,'')) fields[node.get('name')] = attrs diff --git a/bin/tools/convert.py b/bin/tools/convert.py index 7f318b377f2..6f1a4e5996a 100644 --- a/bin/tools/convert.py +++ b/bin/tools/convert.py @@ -35,9 +35,14 @@ import netsvc import osv import pooler from config import config -from tools.safe_eval import safe_eval as eval from yaml_import import convert_yaml_import +# Import of XML records requires the unsafe eval as well, +# almost everywhere, which is ok because it supposedly comes +# from trusted data, but at least we make it obvious now. +unsafe_eval = eval +from tools.safe_eval import safe_eval as eval + class ConvertError(Exception): def __init__(self, doc, orig_excpt): self.d = doc @@ -63,7 +68,7 @@ def _eval_xml(self,node, pool, cr, uid, idref, context=None): f_search = node.get("search",'').encode('utf-8') f_use = node.get("use",'id').encode('ascii') f_name = node.get("name",'').encode('utf-8') - q = eval(f_search, idref) + q = unsafe_eval(f_search, idref) ids = pool.get(f_model).search(cr, uid, q) if f_use != 'id': ids = map(lambda x: x[f_use], pool.get(f_model).read(cr, uid, ids, [f_use])) @@ -97,10 +102,10 @@ def _eval_xml(self,node, pool, cr, uid, idref, context=None): pytz=pytzclass() idref2['pytz'] = pytz try: - return eval(a_eval, idref2) - except: - logger = netsvc.Logger() - logger.notifyChannel("init", netsvc.LOG_WARNING, 'could eval(%s) for %s in %s, please get back and fix it!' % (a_eval,node.get('name'),context)) + return unsafe_eval(a_eval, idref2) + except Exception, e: + logger = logging.getLogger('init') + logger.warning('couldn\'t eval(%s) for %s in %s, please get back and fix it!' % (a_eval,node.get('name'),context), exc_info=True) return "" if t == 'xml': def _process(s, idref): @@ -145,7 +150,7 @@ def _eval_xml(self,node, pool, cr, uid, idref, context=None): a_eval = node.get('eval','') if a_eval: idref['ref'] = lambda x: self.id_get(cr, False, x) - args = eval(a_eval, idref) + args = unsafe_eval(a_eval, idref) for n in node: return_val = _eval_xml(self,n, pool, cr, uid, idref, context) if return_val is not None: @@ -206,13 +211,13 @@ class xml_import(object): def get_context(self, data_node, node, eval_dict): data_node_context = (len(data_node) and data_node.get('context','').encode('utf8')) if data_node_context: - context = eval(data_node_context, eval_dict) + context = unsafe_eval(data_node_context, eval_dict) else: context = {} node_context = node.get("context",'').encode('utf8') if node_context: - context.update(eval(node_context, eval_dict)) + context.update(unsafe_eval(node_context, eval_dict)) return context @@ -242,7 +247,7 @@ form: module.record_id""" % (xml_id,) d_id = rec.get("id",'') ids = [] if d_search: - ids = self.pool.get(d_model).search(cr,self.uid,eval(d_search)) + ids = self.pool.get(d_model).search(cr, self.uid, unsafe_eval(d_search)) if d_id: try: ids.append(self.id_get(cr, d_model, d_id)) @@ -405,7 +410,7 @@ form: module.record_id""" % (xml_id,) def ref(str_id): return self.id_get(cr, None, str_id) - context=eval(context, locals_dict=locals()) + context = unsafe_eval(context) # domain = eval(domain) # XXX need to test this line -> uid, active_id, active_ids, ... res = { @@ -625,7 +630,7 @@ form: module.record_id""" % (xml_id,) if rec_id: ids = [self.id_get(cr, rec_model, rec_id)] elif rec_src: - q = eval(rec_src, eval_dict) + q = unsafe_eval(rec_src, eval_dict) ids = self.pool.get(rec_model).search(cr, uid, q, context=context) if rec_src_count: count = int(rec_src_count) @@ -660,7 +665,7 @@ form: module.record_id""" % (xml_id,) for test in rec.findall('./test'): f_expr = test.get("expr",'').encode('utf-8') expected_value = _eval_xml(self, test, self.pool, cr, uid, self.idref, context=context) or True - expression_value = eval(f_expr, globals_dict, nocopy=True) + expression_value = unsafe_eval(f_expr, globals_dict) if expression_value != expected_value: # assertion failed self.assert_report.record_assertion(False, severity) msg = 'assertion "%s" failed!\n' \ @@ -684,7 +689,7 @@ form: module.record_id""" % (xml_id,) rec_id = rec.get("id",'').encode('ascii') rec_context = rec.get("context", None) if rec_context: - rec_context = eval(rec_context) + rec_context = unsafe_eval(rec_context) self._test_xml_id(rec_id) if self.isnoupdate(data_node) and self.mode != 'init': # check if the xml record has an id string @@ -724,7 +729,7 @@ form: module.record_id""" % (xml_id,) f_val = False if f_search: - q = eval(f_search, self.idref) + q = unsafe_eval(f_search, self.idref) field = [] assert f_model, 'Define an attribute model="..." in your .XML file !' f_obj = self.pool.get(f_model) diff --git a/bin/tools/safe_eval.py b/bin/tools/safe_eval.py index 0fcc7a20eb9..bd8e8ec04d6 100644 --- a/bin/tools/safe_eval.py +++ b/bin/tools/safe_eval.py @@ -33,6 +33,7 @@ condition/math builtins. # - python 2.6's ast.literal_eval from opcode import HAVE_ARGUMENT, opmap, opname +from types import CodeType __all__ = ['test_expr', 'literal_eval', 'safe_eval', 'const_eval', 'ext_eval' ] @@ -211,6 +212,9 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal ValueError: opcode LOAD_NAME not allowed """ + if isinstance(expr, CodeType): + raise ValueError("safe_eval does not allow direct evaluation of code objects.") + if '__subclasses__' in expr: raise ValueError('expression not allowed (__subclasses__)') @@ -220,6 +224,12 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal # prevent altering the globals/locals from within the sandbox # by taking a copy. if not nocopy: + # isinstance() does not work below, we want *exactly* the dict class + if (globals_dict is not None and type(globals_dict) is not dict) \ + or (locals_dict is not None and type(locals_dict) is not dict): + logging.getLogger('safe_eval').warning('Looks like you are trying to pass a dynamic environment,"\ + "you should probably pass nocopy=True to safe_eval()') + globals_dict = dict(globals_dict) if locals_dict is not None: locals_dict = dict(locals_dict) @@ -241,5 +251,4 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal return eval(test_expr(expr,_SAFE_OPCODES, mode=mode), globals_dict, locals_dict) - - +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bin/tools/yaml_import.py b/bin/tools/yaml_import.py index e1dd94e63c0..adbb42790e5 100644 --- a/bin/tools/yaml_import.py +++ b/bin/tools/yaml_import.py @@ -7,10 +7,14 @@ import pooler import netsvc import misc from config import config -from tools.safe_eval import safe_eval as eval import yaml_tag import yaml +# YAML import needs both safe and unsafe eval, but let's +# default to /safe/. +unsafe_eval = eval +from tools.safe_eval import safe_eval as eval + logger_channel = 'tests' class YamlImportException(Exception): @@ -234,8 +238,9 @@ class YamlInterpreter(object): record = model.browse(self.cr, self.uid, id, context) for test in expressions: try: - success = eval(test, self.eval_context, RecordDictWrapper(record)) + success = unsafe_eval(test, self.eval_context, RecordDictWrapper(record)) except Exception, e: + self.logger.debug('Exception during evaluation of !assert block in yaml_file %s.', self.filename, exc_info=True) raise YamlImportAbortion(e) if not success: msg = 'Assertion "%s" FAILED\ntest: %s\n' @@ -367,12 +372,13 @@ class YamlInterpreter(object): code_context = {'model': model, 'cr': self.cr, 'uid': self.uid, 'log': log, 'context': self.context} code_context.update({'self': model}) # remove me when no !python block test uses 'self' anymore try: - code = compile(statements, self.filename, 'exec') - eval(code, {'ref': self.get_id}, code_context) + code_obj = compile(statements, self.filename, 'exec') + unsafe_eval(code_obj, {'ref': self.get_id}, code_context) except AssertionError, e: self._log_assert_failure(python.severity, 'AssertionError in Python code %s: %s', python.name, e) return except Exception, e: + self.logger.debug('Exception during evaluation of !python block in yaml_file %s.', self.filename, exc_info=True) raise YamlImportAbortion(e) else: self.assert_report.record(True, python.severity) diff --git a/bin/workflow/wkf_expr.py b/bin/workflow/wkf_expr.py index 6d3c0a19401..0150105c3ab 100644 --- a/bin/workflow/wkf_expr.py +++ b/bin/workflow/wkf_expr.py @@ -55,7 +55,7 @@ def _eval_expr(cr, ident, workitem, action): ret=False else: env = Env(cr, uid, model, ids) - ret = eval(line, env) + ret = eval(line, env, nocopy=True) return ret def execute_action(cr, ident, workitem, activity):