From 71c61ce1df12e4ebd286a8b45fd448681f662489 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Wed, 9 Apr 2014 12:06:58 +0200 Subject: [PATCH 1/6] [FIX] account_asset: copy should not keep history and move lines of previous asset bzr revid: mat@openerp.com-20140409100658-1oj741aw5sne8dtr --- addons/account_asset/account_asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/account_asset/account_asset.py b/addons/account_asset/account_asset.py index 9200b81decf..806013e6a4c 100644 --- a/addons/account_asset/account_asset.py +++ b/addons/account_asset/account_asset.py @@ -330,7 +330,7 @@ class account_asset_asset(osv.osv): default = {} if context is None: context = {} - default.update({'depreciation_line_ids': [], 'state': 'draft'}) + default.update({'depreciation_line_ids': [], 'account_move_line_ids': [], 'history_ids': [], 'state': 'draft'}) return super(account_asset_asset, self).copy(cr, uid, id, default, context=context) def _compute_entries(self, cr, uid, ids, period_id, context=None): From 1e123f5be0eaf703ba2f3c9c4a4b6e61f4b351d1 Mon Sep 17 00:00:00 2001 From: Anael Closson Date: Wed, 9 Apr 2014 13:48:25 +0200 Subject: [PATCH 2/6] [FIX] pad: pad in readonly when opening form view in edit mode - opw 606208 bzr revid: acl@openerp.com-20140409114825-t10ps6o7qn7qbd7y --- addons/pad/static/src/js/pad.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/addons/pad/static/src/js/pad.js b/addons/pad/static/src/js/pad.js index e04184bc8f7..c939c9241e9 100644 --- a/addons/pad/static/src/js/pad.js +++ b/addons/pad/static/src/js/pad.js @@ -36,9 +36,12 @@ openerp.pad = function(instance) { var value = self.get('value'); if (self.get('effective_readonly')) { if (_.str.startsWith(value, 'http')) { + self.$('.oe_pad_content').addClass('oe_pad_loading') this.pad_loading_request = self.view.dataset.call('pad_get_content', {url: value}).done(function(data) { - self.$('.oe_pad_content').removeClass('oe_pad_loading').html('
'); - self.$('.oe_pad_readonly').html(data); + if (self.$('.oe_pad_loading').length) { + self.$('.oe_pad_content').removeClass('oe_pad_loading').html('
'); + self.$('.oe_pad_readonly').html(data); + } }).fail(function() { self.$('.oe_pad_content').text(_t('Unable to load pad')); }); From 141f24abc4d53f3cf18ab25de69456e268b9a0f1 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Wed, 9 Apr 2014 16:29:17 +0200 Subject: [PATCH 3/6] [FIX] analytic_user_function: add missing _rec_name bzr revid: mat@openerp.com-20140409142917-ueqzyblgh3iwblc0 --- addons/analytic_user_function/analytic_user_function.py | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/analytic_user_function/analytic_user_function.py b/addons/analytic_user_function/analytic_user_function.py index 2671fc92581..5a17ac91e53 100644 --- a/addons/analytic_user_function/analytic_user_function.py +++ b/addons/analytic_user_function/analytic_user_function.py @@ -26,6 +26,7 @@ import openerp.addons.decimal_precision as dp class analytic_user_funct_grid(osv.osv): _name="analytic.user.funct.grid" _description= "Price per User" + _rec_name="user_id" _columns={ 'user_id': fields.many2one("res.users", "User", required=True,), 'product_id': fields.many2one("product.product", "Service", required=True,), From 1578776814f180412df852f38f9271928d0727f6 Mon Sep 17 00:00:00 2001 From: jba <> Date: Wed, 9 Apr 2014 16:43:46 +0200 Subject: [PATCH 4/6] [FIX] expression: when evaluatoing *like operator, add cast to text to column in case of different type bzr revid: mat@openerp.com-20140409144346-nbn0dno3bs6ozwk5 --- openerp/addons/base/tests/test_expression.py | 12 ++++++------ openerp/osv/expression.py | 5 +++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/openerp/addons/base/tests/test_expression.py b/openerp/addons/base/tests/test_expression.py index a89ae193f5c..bbdaa697bcd 100644 --- a/openerp/addons/base/tests/test_expression.py +++ b/openerp/addons/base/tests/test_expression.py @@ -183,7 +183,7 @@ class test_expression(common.TransactionCase): self.assertIn('res_partner_bank', sql_query[0], "_auto_join off: ('bank_ids.name', 'like', '..') first query incorrect main table") - expected = "%s like %s" % (unaccent('"res_partner_bank"."name"'), unaccent('%s')) + expected = "%s::text like %s" % (unaccent('"res_partner_bank"."name"'), unaccent('%s')) self.assertIn(expected, sql_query[1], "_auto_join off: ('bank_ids.name', 'like', '..') first query incorrect where condition") @@ -223,7 +223,7 @@ class test_expression(common.TransactionCase): self.assertIn('"res_partner_bank" as "res_partner__bank_ids"', sql_query[0], "_auto_join on: ('bank_ids.name', 'like', '..') query incorrect join") - expected = "%s like %s" % (unaccent('"res_partner__bank_ids"."name"'), unaccent('%s')) + expected = "%s::text like %s" % (unaccent('"res_partner__bank_ids"."name"'), unaccent('%s')) self.assertIn(expected, sql_query[1], "_auto_join on: ('bank_ids.name', 'like', '..') query incorrect where condition") @@ -305,7 +305,7 @@ class test_expression(common.TransactionCase): self.assertIn('"res_country"', sql_query[0], "_auto_join on for state_id: ('state_id.country_id.code', 'like', '..') query 1 incorrect main table") - expected = "%s like %s" % (unaccent('"res_country"."code"'), unaccent('%s')) + expected = "%s::text like %s" % (unaccent('"res_country"."code"'), unaccent('%s')) self.assertIn(expected, sql_query[1], "_auto_join on for state_id: ('state_id.country_id.code', 'like', '..') query 1 incorrect where condition") @@ -339,7 +339,7 @@ class test_expression(common.TransactionCase): self.assertIn('"res_country" as "res_country_state__country_id"', sql_query[0], "_auto_join on for country_id: ('state_id.country_id.code', 'like', '..') query 1 incorrect join") - expected = "%s like %s" % (unaccent('"res_country_state__country_id"."code"'), unaccent('%s')) + expected = "%s::text like %s" % (unaccent('"res_country_state__country_id"."code"'), unaccent('%s')) self.assertIn(expected, sql_query[1], "_auto_join on for country_id: ('state_id.country_id.code', 'like', '..') query 1 incorrect where condition") @@ -373,7 +373,7 @@ class test_expression(common.TransactionCase): self.assertIn('"res_country" as "res_partner__state_id__country_id"', sql_query[0], "_auto_join on: ('state_id.country_id.code', 'like', '..') query incorrect join") - expected = "%s like %s" % (unaccent('"res_partner__state_id__country_id"."code"'), unaccent('%s')) + expected = "%s::text like %s" % (unaccent('"res_partner__state_id__country_id"."code"'), unaccent('%s')) self.assertIn(expected, sql_query[1], "_auto_join on: ('state_id.country_id.code', 'like', '..') query incorrect where condition") @@ -403,7 +403,7 @@ class test_expression(common.TransactionCase): # Test produced queries that domains effectively present sql_query = self.query_list[0].get_sql() - expected = "%s like %s" % (unaccent('"res_partner__child_ids__bank_ids"."acc_number"'), unaccent('%s')) + expected = "%s::text like %s" % (unaccent('"res_partner__child_ids__bank_ids"."acc_number"'), unaccent('%s')) self.assertIn(expected, sql_query[1], "_auto_join on one2many with domains incorrect result") # TDE TODO: check first domain has a correct table name diff --git a/openerp/osv/expression.py b/openerp/osv/expression.py index da49ae12850..68ad4c2e589 100644 --- a/openerp/osv/expression.py +++ b/openerp/osv/expression.py @@ -1181,14 +1181,15 @@ class expression(object): else: need_wildcard = operator in ('like', 'ilike', 'not like', 'not ilike') sql_operator = {'=like': 'like', '=ilike': 'ilike'}.get(operator, operator) + cast = '::text' if sql_operator.endswith('like') else '' if left in model._columns: format = need_wildcard and '%s' or model._columns[left]._symbol_set[0] unaccent = self._unaccent if sql_operator.endswith('like') else lambda x: x column = '%s.%s' % (table_alias, _quote(left)) - query = '(%s %s %s)' % (unaccent(column), sql_operator, unaccent(format)) + query = '(%s%s %s %s)' % (unaccent(column), cast, sql_operator, unaccent(format)) elif left in MAGIC_COLUMNS: - query = "(%s.\"%s\" %s %%s)" % (table_alias, left, sql_operator) + query = "(%s.\"%s\"%s %s %%s)" % (table_alias, left, cast, sql_operator) params = right else: # Must not happen raise ValueError("Invalid field %r in domain term %r" % (left, leaf)) From 3285feab53ccae8ed7e6abd0dc335145caff5102 Mon Sep 17 00:00:00 2001 From: Kersten Jeremy Date: Wed, 9 Apr 2014 17:16:59 +0200 Subject: [PATCH 5/6] [FIX] Allow missing opcodes, harden check for private attributes (dunder), check inside embedded code objects. bzr revid: jke@openerp.com-20140409151659-xwttchbtbj02v1w7 --- openerp/sql_db.py | 8 ++-- openerp/tools/safe_eval.py | 81 +++++++++++++++++++++++++++++--------- 2 files changed, 66 insertions(+), 23 deletions(-) diff --git a/openerp/sql_db.py b/openerp/sql_db.py index 9844de48d2a..1c57b729569 100644 --- a/openerp/sql_db.py +++ b/openerp/sql_db.py @@ -172,7 +172,7 @@ class Cursor(object): self.sql_log_count = 0 self.__closed = True # avoid the call of close() (by __del__) if an exception # is raised by any of the following initialisations - self._pool = pool + self.__pool = pool self.dbname = dbname # Whether to enable snapshot isolation level for this cursor. @@ -318,7 +318,7 @@ class Cursor(object): chosen_template = tools.config['db_template'] templates_list = tuple(set(['template0', 'template1', 'postgres', chosen_template])) keep_in_pool = self.dbname not in templates_list - self._pool.give_back(self._cnx, keep_in_pool=keep_in_pool) + self.__pool.give_back(self._cnx, keep_in_pool=keep_in_pool) @check def autocommit(self, on): @@ -476,12 +476,12 @@ class Connection(object): def __init__(self, pool, dbname): self.dbname = dbname - self._pool = pool + self.__pool = pool def cursor(self, serialized=True): cursor_type = serialized and 'serialized ' or '' _logger.debug('create %scursor to %r', cursor_type, self.dbname) - return Cursor(self._pool, self.dbname, serialized=serialized) + return Cursor(self.__pool, self.dbname, serialized=serialized) # serialized_cursor is deprecated - cursors are serialized by default serialized_cursor = cursor diff --git a/openerp/tools/safe_eval.py b/openerp/tools/safe_eval.py index b99de385af3..aa6abccd549 100644 --- a/openerp/tools/safe_eval.py +++ b/openerp/tools/safe_eval.py @@ -66,7 +66,8 @@ _SAFE_OPCODES = _EXPR_OPCODES.union(set(opmap[x] for x in [ 'MAKE_FUNCTION', 'SLICE+0', 'SLICE+1', 'SLICE+2', 'SLICE+3', # New in Python 2.7 - http://bugs.python.org/issue4715 : 'JUMP_IF_FALSE_OR_POP', 'JUMP_IF_TRUE_OR_POP', 'POP_JUMP_IF_FALSE', - 'POP_JUMP_IF_TRUE', 'SETUP_EXCEPT', 'END_FINALLY' + 'POP_JUMP_IF_TRUE', 'SETUP_EXCEPT', 'END_FINALLY', 'LOAD_FAST', + 'LOAD_GLOBAL', # Only allows access to restricted globals ] if x in opmap)) _logger = logging.getLogger(__name__) @@ -81,16 +82,65 @@ def _get_opcodes(codeobj): [100, 100, 23, 100, 100, 102, 103, 83] """ i = 0 - opcodes = [] byte_codes = codeobj.co_code while i < len(byte_codes): code = ord(byte_codes[i]) - opcodes.append(code) + yield code + if code >= HAVE_ARGUMENT: i += 3 else: i += 1 - return opcodes + +def assert_no_dunder_name(code_obj, expr): + """ assert_no_dunder_name(code_obj, expr) -> None + + Asserts that the code object does not refer to any "dunder name" + (__$name__), so that safe_eval prevents access to any internal-ish Python + attribute or method (both are loaded via LOAD_ATTR which uses a name, not a + const or a var). + + Checks that no such name exists in the provided code object (co_names). + + :param code_obj: code object to name-validate + :type code_obj: CodeType + :param str expr: expression corresponding to the code object, for debugging + purposes + :raises NameError: in case a forbidden name (containing two underscores) + is found in ``code_obj`` + + .. note:: actually forbids every name containing 2 underscores + """ + for name in code_obj.co_names: + if "__" in name: + raise NameError('Access to forbidden name %r (%r)' % (name, expr)) + +def assert_valid_codeobj(allowed_codes, code_obj, expr): + """ Asserts that the provided code object validates against the bytecode + and name constraints. + + Recursively validates the code objects stored in its co_consts in case + lambdas are being created/used (lambdas generate their own separated code + objects and don't live in the root one) + + :param allowed_codes: list of permissible bytecode instructions + :type allowed_codes: set(int) + :param code_obj: code object to name-validate + :type code_obj: CodeType + :param str expr: expression corresponding to the code object, for debugging + purposes + :raises ValueError: in case of forbidden bytecode in ``code_obj`` + :raises NameError: in case a forbidden name (containing two underscores) + is found in ``code_obj`` + """ + assert_no_dunder_name(code_obj, expr) + for opcode in _get_opcodes(code_obj): + if opcode not in allowed_codes: + raise ValueError( + "opcode %s not allowed (%r)" % (opname[opcode], expr)) + for const in code_obj.co_consts: + if isinstance(const, CodeType): + assert_valid_codeobj(allowed_codes, const, 'lambda') def test_expr(expr, allowed_codes, mode="eval"): """test_expr(expression, allowed_codes[, mode]) -> code_object @@ -105,15 +155,14 @@ def test_expr(expr, allowed_codes, mode="eval"): # eval() does not like leading/trailing whitespace expr = expr.strip() code_obj = compile(expr, "", mode) - except (SyntaxError, TypeError): + except (SyntaxError, TypeError, ValueError): _logger.debug('Invalid eval expression', exc_info=True) raise except Exception: _logger.debug('Disallowed or invalid eval expression', exc_info=True) raise ValueError("%s is not a valid expression" % expr) - for code in _get_opcodes(code_obj): - if code not in allowed_codes: - raise ValueError("opcode %s not allowed (%r)" % (opname[code], expr)) + + assert_valid_codeobj(allowed_codes, code_obj, expr) return code_obj @@ -182,19 +231,13 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal This can be used to e.g. evaluate an OpenERP domain expression from an untrusted source. - Throws TypeError, SyntaxError or ValueError (not allowed) accordingly. - - >>> safe_eval("__import__('sys').modules") - Traceback (most recent call last): - ... - ValueError: opcode LOAD_NAME not allowed - + :throws TypeError: If the expression provided is a code object + :throws SyntaxError: If the expression provided is not valid Python + :throws NameError: If the expression provided accesses forbidden names + :throws ValueError: If the expression provided uses forbidden bytecode """ 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__)') + raise TypeError("safe_eval does not allow direct evaluation of code objects.") if globals_dict is None: globals_dict = {} From c28a5a1021a568fa1a60011f2a5ff7b6c9005829 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Wed, 9 Apr 2014 18:29:32 +0200 Subject: [PATCH 6/6] [FIX] pad: render_value binded on change effective_readonly event, so render value calls wait for each others Proper fix than 9968 revid:dle@openerp.com-20140409160214-1anxi8z07xj49vsp bzr revid: dle@openerp.com-20140409162932-y5fxd5wbojing2t2 --- addons/pad/static/src/js/pad.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/addons/pad/static/src/js/pad.js b/addons/pad/static/src/js/pad.js index d44ca07417a..cac1de89ea1 100644 --- a/addons/pad/static/src/js/pad.js +++ b/addons/pad/static/src/js/pad.js @@ -13,6 +13,7 @@ openerp.pad = function(instance) { event.preventDefault(); self.set("configured", true); }); + this.pad_loading_request = null; }, initialize_content: function() { var self = this; @@ -25,23 +26,20 @@ openerp.pad = function(instance) { self.$(".oe_unconfigured").toggle(!configured); self.$(".oe_configured").toggle(configured); }); - this.on("change:effective_readonly", this, this.render_value); + this.render_value(); }, render_value: function() { var self = this; - this._configured_deferred.always(function() { + $.when(this._configured_deferred, this.pad_loading_request).always(function() { if (! self.get('configured')) { return; }; var value = self.get('value'); if (self.get('effective_readonly')) { if (_.str.startsWith(value, 'http')) { - self.$('.oe_pad_content').addClass('oe_pad_loading') - this.pad_loading_request = self.view.dataset.call('pad_get_content', {url: value}).done(function(data) { - if (self.$('.oe_pad_loading').length) { - self.$('.oe_pad_content').removeClass('oe_pad_loading').html('
'); - self.$('.oe_pad_readonly').html(data); - } + self.pad_loading_request = self.view.dataset.call('pad_get_content', {url: value}).done(function(data) { + self.$('.oe_pad_content').removeClass('oe_pad_loading').html('
'); + self.$('.oe_pad_readonly').html(data); }).fail(function() { self.$('.oe_pad_content').text(_t('Unable to load pad')); });