From b220dc04ddbabb8d03b278d80640575bae61bf85 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 27 Apr 2010 14:13:05 +0200 Subject: [PATCH 001/120] [IMP] base: renamed __terp__ to __openerp__ bzr revid: odo@openerp.com-20100427121305-o0rby3wr3gwr3ydx --- bin/tools/safe_eval.py | 273 +++++++++++++++++++++++++++++++++-------- 1 file changed, 224 insertions(+), 49 deletions(-) diff --git a/bin/tools/safe_eval.py b/bin/tools/safe_eval.py index a7fce27d2e0..5eeb6258dc0 100644 --- a/bin/tools/safe_eval.py +++ b/bin/tools/safe_eval.py @@ -1,66 +1,241 @@ # -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (C) 2004-2010 OpenERP s.a. (). # -# Copyright P. Christeas 2008,2009 +# 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. # -# WARNING: This program as such is intended to be used by professional -# programmers who take the whole responsability of assessing all potential -# consequences resulting from its eventual inadequacies and bugs -# End users who are looking for a ready-to-use solution with commercial -# garantees and support are strongly adviced to contract a Free Software -# Service Company +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . # -# This program is Free Software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -############################################################################### +############################################################################## -__export_bis = {} -import sys +""" +safe_eval module - methods intended to provide more restricted alternatives to + evaluate simple and/or untrusted code. + +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. +""" -def __init_ebis(): - global __export_bis +# Module partially ripped from/inspired by several different sources: +# - http://code.activestate.com/recipes/286134/ +# - safe_eval in lp:~xrg/openobject-server/optimize-5.0 +# - safe_eval in tryton http://hg.tryton.org/hgwebdir.cgi/trytond/rev/bbb5f73319ad +# - python 2.6's ast.literal_eval - _evars = [ 'abs', 'all', 'any', 'basestring' , 'bool', - 'chr', 'cmp','complex', 'dict', 'divmod', 'enumerate', - 'float', 'frozenset', 'getattr', 'hasattr', 'hash', - 'hex', 'id','int', 'iter', 'len', 'list', 'long', 'map', 'max', - 'min', 'oct', 'ord','pow', 'range', 'reduce', 'repr', - 'reversed', 'round', 'set', 'setattr', 'slice','sorted', 'str', - 'sum', 'tuple','type', 'unichr','unicode', 'xrange', - 'True','False', 'None', 'NotImplemented', 'Ellipsis', ] +from opcode import HAVE_ARGUMENT, opmap, opname - if sys.version_info[0:2] >= (2,6): - _evars.extend(['bin', 'format', 'next']) - for v in _evars: - __export_bis[v] = __builtins__[v] +_CONST_CODES = set(opmap[x] for x in [ + 'POP_TOP', 'ROT_TWO', 'ROT_THREE', 'ROT_FOUR', 'DUP_TOP', + 'BUILD_LIST', 'BUILD_MAP', 'BUILD_TUPLE', + 'LOAD_CONST', 'RETURN_VALUE', 'STORE_SUBSCR'] if x in opmap) + +_EXPR_CODES = _CONST_CODES.union(set(opmap[x] for x in [ + 'UNARY_POSITIVE', 'UNARY_NEGATIVE', 'UNARY_NOT', + 'UNARY_INVERT', 'BINARY_POWER', 'BINARY_MULTIPLY', + 'BINARY_DIVIDE', 'BINARY_FLOOR_DIVIDE', 'BINARY_TRUE_DIVIDE', + 'BINARY_MODULO', 'BINARY_ADD', 'BINARY_SUBTRACT', 'BINARY_SUBSCR', + 'BINARY_LSHIFT', 'BINARY_RSHIFT', 'BINARY_AND', 'BINARY_XOR', + 'BINARY_OR'] if x in opmap)) + +_SAFE_CODES = _EXPR_CODES.union(set(opmap[x] for x in [ + 'STORE_MAP', 'LOAD_NAME', 'CALL_FUNCTION', 'COMPARE_OP', 'LOAD_ATTR', + 'STORE_NAME', 'GET_ITER', 'FOR_ITER', 'LIST_APPEND', 'JUMP_ABSOLUTE', + 'DELETE_NAME', 'JUMP_IF_TRUE', 'JUMP_IF_FALSE', + ] if x in opmap)) -__init_ebis() +def _get_opcodes(codeobj): + """_get_opcodes(codeobj) -> [opcodes] + + Extract the actual opcodes as a list from a code object + + >>> c = compile("[1 + 2, (1,2)]", "", "eval") + >>> _get_opcodes(c) + [100, 100, 23, 100, 100, 102, 103, 83] + """ + i = 0 + opcodes = [] + s = codeobj.co_code + while i < len(s): + code = ord(s[i]) + opcodes.append(code) + if code >= HAVE_ARGUMENT: + i += 3 + else: + i += 1 + return opcodes + +def test_expr(expr, allowed_codes): + """test_expr(expression) -> code_object + + Test that the expression contains only the allowed opcodes. + If the expression is valid and contains only allowed codes, + return the compiled code object. Otherwise raise a ValueError. + """ + try: + code_obj = compile(expr, "", "eval") + except: + 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)) + return code_obj -def safe_eval(expr,sglobals,slocals = None): - """ A little safer version of eval(). - This one, will use fewer builtin functions, so that only - arithmetic and logic expressions can really work """ +def const_eval(expr): + """const_eval(expression) -> value - global __export_bis + Safe Python constant evaluation + + Evaluates a string that contains an expression describing + a Python constant. Strings that are not valid Python expressions + or that contain other code besides the constant raise ValueError. + + >>> const_eval("10") + 10 + >>> const_eval("[1,2, (3,4), {'foo':'bar'}]") + [1, 2, (3, 4), {'foo': 'bar'}] + >>> const_eval("1+2") + Traceback (most recent call last): + ... + ValueError: opcode BINARY_ADD not allowed + """ + c = test_expr(expr, _CONST_CODES) + return eval(c) + +def expr_eval(expr): + """expr_eval(expression) -> value + + Restricted Python expression evaluation + + Evaluates a string that contains an expression that only + uses Python constants. This can be used to e.g. evaluate + a numerical expression from an untrusted source. + + >>> expr_eval("1+2") + 3 + >>> expr_eval("[1,2]*2") + [1, 2, 1, 2] + >>> expr_eval("__import__('sys').modules") + Traceback (most recent call last): + ... + ValueError: opcode LOAD_NAME not allowed + """ + c = test_expr(expr, _EXPR_CODES) + return eval(c) + + +# Port of Python 2.6's ast.literal_eval for use under Python 2.5 +SAFE_CONSTANTS = {'None': None, 'True': True, 'False': False} + +try: + # first, try importing directly + from ast import literal_eval +except ImportError: + from _ast import * + + def _convert(node): + if isinstance(node, Str): + return node.s + elif isinstance(node, Num): + return node.n + elif isinstance(node, Tuple): + return tuple(map(_convert, node.elts)) + elif isinstance(node, List): + return list(map(_convert, node.elts)) + elif isinstance(node, Dict): + return dict((_convert(k), _convert(v)) for k, v + in zip(node.keys, node.values)) + elif isinstance(node, Name): + if node.id in SAFE_CONSTANTS: + return SAFE_CONSTANTS[node.id] + raise ValueError('malformed or disallowed expression') + + def parse(expr, filename='', mode='eval'): + """parse(source[, filename], mode]] -> code object + Parse an expression into an AST node. + Equivalent to compile(expr, filename, mode, PyCF_ONLY_AST). + """ + return compile(expr, filename, mode, PyCF_ONLY_AST) + + def literal_eval(node_or_string): + """literal_eval(expression) -> value + Safely evaluate an expression node or a string containing a Python + expression. The string or node provided may only consist of the + following Python literal structures: strings, numbers, tuples, + lists, dicts, booleans, and None. + + >>> literal_eval('[1,True,"spam"]') + [1, True, 'spam'] + + >>> literal_eval('1+3') + Traceback (most recent call last): + ... + ValueError: malformed or disallowed expression + """ + if isinstance(node_or_string, basestring): + node_or_string = parse(node_or_string) + if isinstance(node_or_string, Expression): + node_or_string = node_or_string.body + return _convert(node_or_string) + + + +def safe_eval(expr, context = None): + """safe_eval(expression, context) -> value + + System-restricted Python expression evaluation + + Evaluates a string that contains an expression that mostly + uses Python constants, arithmetic expressions and the + use of the objects provided in context. + + This can be used to e.g. evaluate + an OpenERP domain expression from an untrusted expr. + + >>> safe_eval("__import__('sys').modules") + Traceback (most recent call last): + ... + ValueError: opcode LOAD_NAME not allowed + + """ + if '__subclasses__' in expr: + raise ValueError('expression not allowed (__subclasses__)') + code_obj = compile(expr, '', 'eval') + byte_codes = code_obj.co_code + i = 0 + while i < len(byte_codes): + op_code = ord(byte_codes[i]) + if op_code not in _SAFE_CODES: + raise ValueError('opcode %byte_codes not allowed' % dis.opname[op_code]) + if op_code >= HAVE_ARGUMENT: + i += 3 + else: + i += 1 + return eval(code_obj, {'__builtins__': { + 'True': True, + 'False': False, + 'None': None, + 'str': str, + 'globals': locals, + 'locals': locals, + 'bool': bool, + 'dict': dict, + 'list': list, + 'tuple': tuple, + } + }, context) - if not sglobals.has_key('__builtins__'): - # we copy, because we wouldn't want successive calls to safe_eval - # to be able to alter the builtins. - sglobals['__builtins__'] = __export_bis.copy() - return eval(expr,sglobals,slocals) -#eof From b020dc836345cadebb2e97ed883cb129f798e386 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Tue, 4 May 2010 00:49:53 +0100 Subject: [PATCH 002/120] [IMP] work-in-progress: initial version of safe_eval implementation -- still need fixing bzr revid: odo@openerp.com-20100503234953-qgbk64bmir95jvzd --- bin/addons/__init__.py | 3 + bin/addons/base/__openerp__.py | 1 + bin/addons/base/ir/ir_actions.py | 3 +- bin/addons/base/ir/ir_cron.py | 1 + bin/addons/base/ir/ir_report_custom.py | 1 + bin/addons/base/ir/ir_rule.py | 1 + bin/addons/base/ir/ir_ui_view.py | 1 + bin/addons/base/res/res_company.py | 1 + bin/addons/base/res/res_lang.py | 1 + bin/osv/orm.py | 10 +-- bin/report/custom.py | 1 + bin/report/print_xml.py | 3 +- bin/report/printscreen/ps_list.py | 1 + bin/report/render/rml2pdf/trml2pdf.py | 1 + bin/report/render/rml2pdf/utils.py | 1 + bin/report/render/rml2txt/utils.py | 3 +- bin/tools/convert.py | 1 + bin/tools/safe_eval.py | 119 ++++++++++++------------- bin/tools/yaml_import.py | 2 +- bin/workflow/wkf_expr.py | 1 + 20 files changed, 85 insertions(+), 71 deletions(-) diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py index ac8e951e2a3..bf4a0c78c18 100644 --- a/bin/addons/__init__.py +++ b/bin/addons/__init__.py @@ -28,6 +28,7 @@ import zipimport import osv import tools import tools.osutil +from tools.safe_eval import safe_eval as eval import pooler @@ -422,6 +423,8 @@ def register_class(m): msg = "Couldn't load %smodule %s" % (mt, m) logger.notifyChannel('init', netsvc.LOG_CRITICAL, msg) logger.notifyChannel('init', netsvc.LOG_CRITICAL, e) + import traceback + traceback.print_exc() global loaded if m in loaded: diff --git a/bin/addons/base/__openerp__.py b/bin/addons/base/__openerp__.py index 2308ecfccc8..26b2869758e 100644 --- a/bin/addons/base/__openerp__.py +++ b/bin/addons/base/__openerp__.py @@ -75,6 +75,7 @@ ], 'test': [ 'test/base_test.xml', + #'test/base_test.yml' 'test/test_context.xml', 'bug_lp541545.xml', ], diff --git a/bin/addons/base/ir/ir_actions.py b/bin/addons/base/ir/ir_actions.py index 6c51ab2f771..c3e9945df96 100644 --- a/bin/addons/base/ir/ir_actions.py +++ b/bin/addons/base/ir/ir_actions.py @@ -20,6 +20,7 @@ ############################################################################## from osv import fields,osv +from tools.safe_eval import safe_eval as eval import tools import time from tools.config import config @@ -564,7 +565,7 @@ class actions_server(osv.osv): 'object':obj, 'obj': obj, } - exec action.code in localdict + eval(action.code, localdict, "exec") if 'action' in localdict: return localdict['action'] else: diff --git a/bin/addons/base/ir/ir_cron.py b/bin/addons/base/ir/ir_cron.py index 66ead94be48..905b2dc1778 100644 --- a/bin/addons/base/ir/ir_cron.py +++ b/bin/addons/base/ir/ir_cron.py @@ -25,6 +25,7 @@ from datetime import datetime from dateutil.relativedelta import relativedelta import netsvc import tools +from tools.safe_eval import safe_eval as eval import pooler from osv import fields, osv diff --git a/bin/addons/base/ir/ir_report_custom.py b/bin/addons/base/ir/ir_report_custom.py index 3b20d193d8b..036469fb417 100644 --- a/bin/addons/base/ir/ir_report_custom.py +++ b/bin/addons/base/ir/ir_report_custom.py @@ -24,6 +24,7 @@ from osv.orm import browse_null import ir import report.custom from tools.translate import _ +from tools.safe_eval import safe_eval as eval import netsvc class report_custom(osv.osv): diff --git a/bin/addons/base/ir/ir_rule.py b/bin/addons/base/ir/ir_rule.py index de465fa8025..ba508b48920 100644 --- a/bin/addons/base/ir/ir_rule.py +++ b/bin/addons/base/ir/ir_rule.py @@ -22,6 +22,7 @@ from osv import fields,osv import time import tools +from tools.safe_eval import safe_eval as eval class ir_rule(osv.osv): _name = 'ir.rule' diff --git a/bin/addons/base/ir/ir_ui_view.py b/bin/addons/base/ir/ir_ui_view.py index f1a50143ef3..bc395325840 100644 --- a/bin/addons/base/ir/ir_ui_view.py +++ b/bin/addons/base/ir/ir_ui_view.py @@ -22,6 +22,7 @@ from osv import fields,osv from lxml import etree from tools import graph +from tools.safe_eval import safe_eval as eval import tools import netsvc import os diff --git a/bin/addons/base/res/res_company.py b/bin/addons/base/res/res_company.py index 7d8a9cda8f3..b14c5b403c1 100644 --- a/bin/addons/base/res/res_company.py +++ b/bin/addons/base/res/res_company.py @@ -24,6 +24,7 @@ from osv import fields import os import tools from tools.translate import _ +from tools.safe_eval import safe_eval as eval class multi_company_default(osv.osv): """ diff --git a/bin/addons/base/res/res_lang.py b/bin/addons/base/res/res_lang.py index 66c1463eba9..13c4a33aa9c 100644 --- a/bin/addons/base/res/res_lang.py +++ b/bin/addons/base/res/res_lang.py @@ -22,6 +22,7 @@ from osv import fields, osv from locale import localeconv import tools +from tools.safe_eval import safe_eval as eval class lang(osv.osv): _name = "res.lang" diff --git a/bin/osv/orm.py b/bin/osv/orm.py index d6e0e6b1395..40104e6570c 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -56,7 +56,7 @@ from tools.translate import _ import fields import tools - +from tools.safe_eval import safe_eval as eval regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I) @@ -353,8 +353,8 @@ def get_pg_type(f): f_type = ('int4', 'INTEGER') else: f_type = ('varchar', 'VARCHAR(%d)' % f_size) - elif isinstance(f, fields.function) and eval('fields.'+(f._type)) in type_dict: - t = eval('fields.'+(f._type)) + elif isinstance(f, fields.function) and eval('fields.'+(f._type),globals()) in type_dict: + t = eval('fields.'+(f._type), globals()) f_type = (type_dict[t], type_dict[t]) elif isinstance(f, fields.function) and f._type == 'float': if f.digits: @@ -1951,9 +1951,9 @@ class orm_memory(orm_template): f = True for arg in result: if arg[1] =='=': - val =eval('data[arg[0]]'+'==' +' arg[2]') + val =eval('data[arg[0]]'+'==' +' arg[2]', locals()) elif arg[1] in ['<','>','in','not in','<=','>=','<>']: - val =eval('data[arg[0]]'+arg[1] +' arg[2]') + val =eval('data[arg[0]]'+arg[1] +' arg[2]', locals()) elif arg[1] in ['ilike']: if str(data[arg[0]]).find(str(arg[2]))!=-1: val= True diff --git a/bin/report/custom.py b/bin/report/custom.py index d4309b31ab9..b53be34cfdb 100644 --- a/bin/report/custom.py +++ b/bin/report/custom.py @@ -24,6 +24,7 @@ import time import netsvc import tools +from tools.safe_eval import safe_eval as eval import print_xml import render from interface import report_int diff --git a/bin/report/print_xml.py b/bin/report/print_xml.py index 2c24d3766f3..f349e10b1ba 100644 --- a/bin/report/print_xml.py +++ b/bin/report/print_xml.py @@ -23,6 +23,7 @@ import os,types from lxml import etree import netsvc import tools +from tools.safe_eval import safe_eval import print_fnc import copy from osv.orm import browse_null, browse_record @@ -111,7 +112,7 @@ class document(object): #Pinky: Why not this ? eval(expr, browser) ? # name = browser.name # data_dict = browser._data[self.get_value(browser, 'id')] - return eval(expr, {}, {'obj': record}) + return safe_eval(expr, {}, {'obj': record}) def parse_node(self, node, parent, browser, datas=None): attrs = self.node_attrs_get(node) diff --git a/bin/report/printscreen/ps_list.py b/bin/report/printscreen/ps_list.py index 0dea67382fe..4eba7372f2d 100644 --- a/bin/report/printscreen/ps_list.py +++ b/bin/report/printscreen/ps_list.py @@ -22,6 +22,7 @@ from report.interface import report_int import pooler import tools +from tools.safe_eval import safe_eval as eval from lxml import etree from report import render import locale diff --git a/bin/report/render/rml2pdf/trml2pdf.py b/bin/report/render/rml2pdf/trml2pdf.py index a5912cd7a13..2db5f78ab5c 100644 --- a/bin/report/render/rml2pdf/trml2pdf.py +++ b/bin/report/render/rml2pdf/trml2pdf.py @@ -34,6 +34,7 @@ import os from lxml import etree import base64 from reportlab.platypus.doctemplate import ActionFlowable +from tools.safe_eval import safe_eval as eval encoding = 'utf-8' diff --git a/bin/report/render/rml2pdf/utils.py b/bin/report/render/rml2pdf/utils.py index ac4370ab204..ca51f136b6d 100644 --- a/bin/report/render/rml2pdf/utils.py +++ b/bin/report/render/rml2pdf/utils.py @@ -42,6 +42,7 @@ from lxml import etree import copy import locale import traceback, sys +from tools.safe_eval import safe_eval as eval _regex = re.compile('\[\[(.+?)\]\]') diff --git a/bin/report/render/rml2txt/utils.py b/bin/report/render/rml2txt/utils.py index dc7651ea2ad..abbf2dc8426 100644 --- a/bin/report/render/rml2txt/utils.py +++ b/bin/report/render/rml2txt/utils.py @@ -23,6 +23,7 @@ import re import reportlab import reportlab.lib.units from lxml import etree +from tools.safe_eval import safe_eval as eval _regex = re.compile('\[\[(.+?)\]\]') @@ -128,4 +129,4 @@ def attr_get(node, attrs, dict={}): res[key] = unit_get(node.get(key)) return res -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bin/tools/convert.py b/bin/tools/convert.py index a7e002e1a61..5fb20530408 100644 --- a/bin/tools/convert.py +++ b/bin/tools/convert.py @@ -35,6 +35,7 @@ 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 class ConvertError(Exception): diff --git a/bin/tools/safe_eval.py b/bin/tools/safe_eval.py index 5eeb6258dc0..f1a2ca1b5e7 100644 --- a/bin/tools/safe_eval.py +++ b/bin/tools/safe_eval.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- ############################################################################## -# # Copyright (C) 2004-2010 OpenERP s.a. (). # # This program is free software: you can redistribute it and/or modify @@ -14,15 +13,15 @@ # 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 . +# along with this program. If not, see . # ############################################################################## """ safe_eval module - methods intended to provide more restricted alternatives to evaluate simple and/or untrusted code. - -Methods in this module are typically used as alternatives to eval() to parse + +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. """ @@ -30,17 +29,19 @@ condition/math builtins. # Module partially ripped from/inspired by several different sources: # - http://code.activestate.com/recipes/286134/ # - safe_eval in lp:~xrg/openobject-server/optimize-5.0 -# - safe_eval in tryton http://hg.tryton.org/hgwebdir.cgi/trytond/rev/bbb5f73319ad +# - safe_eval in tryton http://hg.tryton.org/hgwebdir.cgi/trytond/rev/bbb5f73319ad # - python 2.6's ast.literal_eval from opcode import HAVE_ARGUMENT, opmap, opname -_CONST_CODES = set(opmap[x] for x in [ +__all__ = ['test_expr', 'literal_eval', 'safe_eval', 'const_eval', 'ext_eval' ] + +_CONST_OPCODES = set(opmap[x] for x in [ 'POP_TOP', 'ROT_TWO', 'ROT_THREE', 'ROT_FOUR', 'DUP_TOP', 'BUILD_LIST', 'BUILD_MAP', 'BUILD_TUPLE', 'LOAD_CONST', 'RETURN_VALUE', 'STORE_SUBSCR'] if x in opmap) -_EXPR_CODES = _CONST_CODES.union(set(opmap[x] for x in [ +_EXPR_OPCODES = _CONST_OPCODES.union(set(opmap[x] for x in [ 'UNARY_POSITIVE', 'UNARY_NEGATIVE', 'UNARY_NOT', 'UNARY_INVERT', 'BINARY_POWER', 'BINARY_MULTIPLY', 'BINARY_DIVIDE', 'BINARY_FLOOR_DIVIDE', 'BINARY_TRUE_DIVIDE', @@ -48,10 +49,10 @@ _EXPR_CODES = _CONST_CODES.union(set(opmap[x] for x in [ 'BINARY_LSHIFT', 'BINARY_RSHIFT', 'BINARY_AND', 'BINARY_XOR', 'BINARY_OR'] if x in opmap)) -_SAFE_CODES = _EXPR_CODES.union(set(opmap[x] for x in [ +_SAFE_OPCODES = _EXPR_OPCODES.union(set(opmap[x] for x in [ 'STORE_MAP', 'LOAD_NAME', 'CALL_FUNCTION', 'COMPARE_OP', 'LOAD_ATTR', 'STORE_NAME', 'GET_ITER', 'FOR_ITER', 'LIST_APPEND', 'JUMP_ABSOLUTE', - 'DELETE_NAME', 'JUMP_IF_TRUE', 'JUMP_IF_FALSE', + 'DELETE_NAME', 'JUMP_IF_TRUE', 'JUMP_IF_FALSE', ] if x in opmap)) @@ -66,25 +67,25 @@ def _get_opcodes(codeobj): """ i = 0 opcodes = [] - s = codeobj.co_code - while i < len(s): - code = ord(s[i]) + byte_codes = codeobj.co_code + while i < len(byte_codes): + code = ord(byte_codes[i]) opcodes.append(code) if code >= HAVE_ARGUMENT: i += 3 else: i += 1 - return opcodes + return opcodes -def test_expr(expr, allowed_codes): - """test_expr(expression) -> code_object +def test_expr(expr, allowed_codes, mode="eval"): + """test_expr(expression, allowed_codes[, mode]) -> code_object Test that the expression contains only the allowed opcodes. If the expression is valid and contains only allowed codes, return the compiled code object. Otherwise raise a ValueError. """ try: - code_obj = compile(expr, "", "eval") + code_obj = compile(expr, "", mode) except: raise ValueError("%s is not a valid expression" % expr) for code in _get_opcodes(code_obj): @@ -111,7 +112,7 @@ def const_eval(expr): ... ValueError: opcode BINARY_ADD not allowed """ - c = test_expr(expr, _CONST_CODES) + c = test_expr(expr, _CONST_OPCODES) return eval(c) def expr_eval(expr): @@ -120,7 +121,7 @@ def expr_eval(expr): Restricted Python expression evaluation Evaluates a string that contains an expression that only - uses Python constants. This can be used to e.g. evaluate + uses Python constants. This can be used to e.g. evaluate a numerical expression from an untrusted source. >>> expr_eval("1+2") @@ -132,7 +133,7 @@ def expr_eval(expr): ... ValueError: opcode LOAD_NAME not allowed """ - c = test_expr(expr, _EXPR_CODES) + c = test_expr(expr, _EXPR_OPCODES) return eval(c) @@ -143,31 +144,31 @@ try: # first, try importing directly from ast import literal_eval except ImportError: - from _ast import * - + import _ast as ast + def _convert(node): - if isinstance(node, Str): + if isinstance(node, ast.Str): return node.s - elif isinstance(node, Num): + elif isinstance(node, ast.Num): return node.n - elif isinstance(node, Tuple): + elif isinstance(node, ast.Tuple): return tuple(map(_convert, node.elts)) - elif isinstance(node, List): + elif isinstance(node, ast.List): return list(map(_convert, node.elts)) - elif isinstance(node, Dict): + elif isinstance(node, ast.Dict): return dict((_convert(k), _convert(v)) for k, v in zip(node.keys, node.values)) - elif isinstance(node, Name): + elif isinstance(node, ast.Name): if node.id in SAFE_CONSTANTS: return SAFE_CONSTANTS[node.id] raise ValueError('malformed or disallowed expression') - + def parse(expr, filename='', mode='eval'): """parse(source[, filename], mode]] -> code object Parse an expression into an AST node. Equivalent to compile(expr, filename, mode, PyCF_ONLY_AST). """ - return compile(expr, filename, mode, PyCF_ONLY_AST) + return compile(expr, filename, mode, ast.PyCF_ONLY_AST) def literal_eval(node_or_string): """literal_eval(expression) -> value @@ -175,10 +176,10 @@ except ImportError: expression. The string or node provided may only consist of the following Python literal structures: strings, numbers, tuples, lists, dicts, booleans, and None. - + >>> literal_eval('[1,True,"spam"]') [1, True, 'spam'] - + >>> literal_eval('1+3') Traceback (most recent call last): ... @@ -186,23 +187,23 @@ except ImportError: """ if isinstance(node_or_string, basestring): node_or_string = parse(node_or_string) - if isinstance(node_or_string, Expression): + if isinstance(node_or_string, ast.Expression): node_or_string = node_or_string.body return _convert(node_or_string) -def safe_eval(expr, context = None): - """safe_eval(expression, context) -> value +def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval"): + """safe_eval(expression[, globals[, locals[, mode]]]) -> value System-restricted Python expression evaluation Evaluates a string that contains an expression that mostly uses Python constants, arithmetic expressions and the - use of the objects provided in context. - - This can be used to e.g. evaluate - an OpenERP domain expression from an untrusted expr. + objects directly provided in context. + + This can be used to e.g. evaluate + an OpenERP domain expression from an untrusted source. >>> safe_eval("__import__('sys').modules") Traceback (most recent call last): @@ -212,30 +213,24 @@ def safe_eval(expr, context = None): """ if '__subclasses__' in expr: raise ValueError('expression not allowed (__subclasses__)') - code_obj = compile(expr, '', 'eval') - byte_codes = code_obj.co_code - i = 0 - while i < len(byte_codes): - op_code = ord(byte_codes[i]) - if op_code not in _SAFE_CODES: - raise ValueError('opcode %byte_codes not allowed' % dis.opname[op_code]) - if op_code >= HAVE_ARGUMENT: - i += 3 - else: - i += 1 - return eval(code_obj, {'__builtins__': { - 'True': True, - 'False': False, - 'None': None, - 'str': str, - 'globals': locals, - 'locals': locals, - 'bool': bool, - 'dict': dict, - 'list': list, - 'tuple': tuple, - } - }, context) + + if globals_dict is None: + globals_dict = {} + globals_dict.update( + __builtins__ = { + 'True': True, + 'False': False, + 'None': None, + 'str': str, + 'globals': locals, + 'locals': locals, + 'bool': bool, + 'dict': dict, + 'list': list, + 'tuple': tuple, + } + ) + return eval(test_expr(expr,_SAFE_OPCODES, mode=mode), globals_dict, locals_dict) diff --git a/bin/tools/yaml_import.py b/bin/tools/yaml_import.py index df825f09cbb..e1dd94e63c0 100644 --- a/bin/tools/yaml_import.py +++ b/bin/tools/yaml_import.py @@ -7,8 +7,8 @@ import pooler import netsvc import misc from config import config +from tools.safe_eval import safe_eval as eval import yaml_tag - import yaml logger_channel = 'tests' diff --git a/bin/workflow/wkf_expr.py b/bin/workflow/wkf_expr.py index 98896df9324..6d3c0a19401 100644 --- a/bin/workflow/wkf_expr.py +++ b/bin/workflow/wkf_expr.py @@ -23,6 +23,7 @@ import sys import netsvc import osv as base import pooler +from tools.safe_eval import safe_eval as eval class Env(dict): def __init__(self, cr, uid, model, ids): From fd14d4aa03f8c2296bd0ac8762c3e16b5d0641d2 Mon Sep 17 00:00:00 2001 From: rga Date: Tue, 18 May 2010 21:04:22 +0530 Subject: [PATCH 003/120] Added share wizard in res.partner bzr revid: rga@rga-desktop-20100518153422-v3d985fe68tgk1ed --- bin/addons/base/__terp__.py | 1 + bin/addons/base/res/partner/partner.py | 15 ++++--- .../base/res/partner/wizard/__init__.py | 5 ++- .../base/res/partner/wizard/wizard_share.py | 30 ++++++++++++++ .../res/partner/wizard/wizard_share_view.xml | 41 +++++++++++++++++++ 5 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 bin/addons/base/res/partner/wizard/wizard_share.py create mode 100644 bin/addons/base/res/partner/wizard/wizard_share_view.xml diff --git a/bin/addons/base/__terp__.py b/bin/addons/base/__terp__.py index d0dc9b68dd3..6a5e8bf0261 100644 --- a/bin/addons/base/__terp__.py +++ b/bin/addons/base/__terp__.py @@ -56,6 +56,7 @@ 'res/bank_view.xml', 'res/country_view.xml', 'res/res_currency_view.xml', + 'res/partner/wizard/wizard_share_view.xml', 'res/partner/crm_view.xml', 'res/partner/partner_data.xml', 'res/ir_property_view.xml', diff --git a/bin/addons/base/res/partner/partner.py b/bin/addons/base/res/partner/partner.py index 3e890e6efed..0bcf90819d7 100644 --- a/bin/addons/base/res/partner/partner.py +++ b/bin/addons/base/res/partner/partner.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- ############################################################################## -# +# # OpenERP, Open Source Management Solution # Copyright (C) 2004-2009 Tiny SPRL (). # @@ -15,7 +15,7 @@ # 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 . +# along with this program. If not, see . # ############################################################################## @@ -149,12 +149,12 @@ class res_partner(osv.osv): 'active': fields.boolean('Active'), 'customer': fields.boolean('Customer', help="Check this box if the partner is a customer."), 'supplier': fields.boolean('Supplier', help="Check this box if the partner is a supplier. If it's not checked, purchase people will not see it when encoding a purchase order."), - 'city': fields.related('address', 'city', type='char', string='City'), - 'phone': fields.related('address', 'phone', type='char', string='Phone'), + 'city': fields.related('address', 'city', type='char', string='City'), + 'phone': fields.related('address', 'phone', type='char', string='Phone'), 'country': fields.related('address', 'country_id', type='many2one', relation='res.country', string='Country'), 'employee': fields.boolean('Employee', help="Check this box if the partner is an Employee."), 'email': fields.related('address', 'email', type='char', size=240, string='E-mail'), - 'company_id': fields.many2one('res.company', 'Company', select=1), + 'company_id': fields.many2one('res.company', 'Company', select=1), } def _default_category(self, cr, uid, context={}): @@ -173,6 +173,9 @@ class res_partner(osv.osv): default.update({'name': name+ _(' (copy)'), 'events':[]}) return super(res_partner, self).copy(cr, uid, id, default, context) + def do_share(self, cr, uid, ids, *args): + return True + def _check_ean_key(self, cr, uid, ids): for partner_o in pooler.get_pool(cr.dbname).get('res.partner').read(cr, uid, ids, ['ean13',]): thisean=partner_o['ean13'] @@ -469,7 +472,7 @@ class res_partner_category(osv.osv): _columns = { 'partner_ids': fields.many2many('res.partner', 'res_partner_category_rel', 'category_id', 'partner_id', 'Partners'), } - + res_partner_category() # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bin/addons/base/res/partner/wizard/__init__.py b/bin/addons/base/res/partner/wizard/__init__.py index b1f0addf5d4..d0e69b37924 100644 --- a/bin/addons/base/res/partner/wizard/__init__.py +++ b/bin/addons/base/res/partner/wizard/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- ############################################################################## -# +# # OpenERP, Open Source Management Solution # Copyright (C) 2004-2009 Tiny SPRL (). # @@ -15,7 +15,7 @@ # 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 . +# along with this program. If not, see . # ############################################################################## @@ -23,6 +23,7 @@ import wizard_sms import wizard_spam import wizard_clear_ids import wizard_ean_check +import wizard_share # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bin/addons/base/res/partner/wizard/wizard_share.py b/bin/addons/base/res/partner/wizard/wizard_share.py new file mode 100644 index 00000000000..573a1faa287 --- /dev/null +++ b/bin/addons/base/res/partner/wizard/wizard_share.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). All Rights Reserved +# $Id$ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## +from osv import osv, fields + +class partner_share_create(osv.osv_memory): + _name = 'partner.share.create' + _description = 'Create parner share' + _columns = { + 'user_ids': fields.many2many('res.users', 'share_patner_rel', 'partner_id','user_id', 'Share Users'), + } +partner_share_create() \ No newline at end of file diff --git a/bin/addons/base/res/partner/wizard/wizard_share_view.xml b/bin/addons/base/res/partner/wizard/wizard_share_view.xml new file mode 100644 index 00000000000..6485d6307fb --- /dev/null +++ b/bin/addons/base/res/partner/wizard/wizard_share_view.xml @@ -0,0 +1,41 @@ + + + + + partner.share.create.form + partner.share.create + form + +
+ + + + +