Add new tool safe_eval
This commit is contained in:
1 parent
f3d71ce308
commit
b427d21224
19 files changed
+111
-31
No files matched your search
@@ -1,3 +1,4 @@
|
||||
* Add new tool safe_eval
|
||||
* Handle sequence and history renaming when renaming table
|
||||
* Add old_password to set_preferences of res.user on password change
|
||||
* Allow to drop constraint, index and foreign key with custom table name
|
||||
|
||||
@@ -205,6 +205,7 @@ class Database(DatabaseInterface):
|
||||
|
||||
@staticmethod
|
||||
def init(cursor):
|
||||
from trytond.tools import safe_eval
|
||||
sql_file = os.path.join(os.path.dirname(__file__), 'init.sql')
|
||||
for line in file(sql_file).read().split(';'):
|
||||
if (len(line)>0) and (not line.isspace()):
|
||||
@@ -214,7 +215,7 @@ class Database(DatabaseInterface):
|
||||
root_path = os.path.join(os.path.dirname(__file__), '..', '..')
|
||||
tryton_file = os.path.join(root_path, i, '__tryton__.py')
|
||||
mod_path = os.path.join(root_path, i)
|
||||
info = eval(file(tryton_file).read())
|
||||
info = safe_eval(file(tryton_file).read())
|
||||
active = info.get('active', False)
|
||||
if active:
|
||||
state = 'to install'
|
||||
|
||||
@@ -162,6 +162,7 @@ class Database(DatabaseInterface):
|
||||
|
||||
@staticmethod
|
||||
def init(cursor):
|
||||
from trytond.tools import safe_eval
|
||||
sql_file = os.path.join(os.path.dirname(__file__), 'init.sql')
|
||||
for line in file(sql_file).read().split(';'):
|
||||
if (len(line)>0) and (not line.isspace()):
|
||||
@@ -171,7 +172,7 @@ class Database(DatabaseInterface):
|
||||
root_path = os.path.join(os.path.dirname(__file__), '..', '..')
|
||||
tryton_file = os.path.join(root_path, i, '__tryton__.py')
|
||||
mod_path = os.path.join(root_path, i)
|
||||
info = eval(file(tryton_file).read())
|
||||
info = safe_eval(file(tryton_file).read())
|
||||
active = info.get('active', False)
|
||||
if active:
|
||||
state = 'to install'
|
||||
|
||||
+2
-1
@@ -10,6 +10,7 @@ SPEC: Execute "model.function(*eval(args))" periodically
|
||||
|
||||
from trytond.backend import Database
|
||||
from trytond.model import ModelView, ModelSQL, fields
|
||||
from trytond.tools import safe_eval
|
||||
from mx import DateTime
|
||||
import datetime
|
||||
import traceback
|
||||
@@ -98,7 +99,7 @@ class Cron(ModelSQL, ModelView):
|
||||
|
||||
def _callback(self, cursor, cron):
|
||||
try:
|
||||
args = (cron['args'] or []) and eval(cron['args'])
|
||||
args = (cron['args'] or []) and safe_eval(cron['args'])
|
||||
obj = self.pool.get(cron['model'])
|
||||
if not obj and hasattr(obj, cron['function']):
|
||||
return False
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#this repository contains the full copyright notices and license terms.
|
||||
"Default"
|
||||
from trytond.model import ModelView, ModelSQL, fields
|
||||
from trytond.tools import Cache
|
||||
from trytond.tools import Cache, safe_eval
|
||||
from decimal import Decimal
|
||||
import mx.DateTime
|
||||
import datetime
|
||||
@@ -69,7 +69,7 @@ class Default(ModelSQL, ModelView):
|
||||
date.month, date.day, date.hour, date.minute,
|
||||
int(date.second))
|
||||
else:
|
||||
res[default.field.name] = eval(default.value)
|
||||
res[default.field.name] = safe_eval(default.value)
|
||||
return res
|
||||
|
||||
get_default = Cache('ir_default.get_default')(get_default)
|
||||
|
||||
+4
-3
@@ -3,6 +3,7 @@
|
||||
"Lang"
|
||||
from trytond.model import ModelView, ModelSQL, fields
|
||||
from trytond.model.cacheable import Cacheable
|
||||
from trytond.tools import safe_eval
|
||||
import time
|
||||
from time_locale import TIME_LOCALE
|
||||
|
||||
@@ -99,7 +100,7 @@ class Lang(ModelSQL, ModelView, Cacheable):
|
||||
'''
|
||||
for lang in self.browse(cursor, user, ids):
|
||||
try:
|
||||
grouping = eval(lang.grouping)
|
||||
grouping = safe_eval(lang.grouping)
|
||||
for i in grouping:
|
||||
if not isinstance(i, int):
|
||||
return False
|
||||
@@ -174,10 +175,10 @@ class Lang(ModelSQL, ModelView, Cacheable):
|
||||
# Code from _group in locale.py
|
||||
if monetary:
|
||||
thousands_sep = monetary['mon_thousands_sep']
|
||||
grouping = eval(monetary['mon_grouping'])
|
||||
grouping = safe_eval(monetary['mon_grouping'])
|
||||
else:
|
||||
thousands_sep = lang['thousands_sep']
|
||||
grouping = eval(lang['grouping'])
|
||||
grouping = safe_eval(lang['grouping'])
|
||||
if not grouping:
|
||||
return (s, 0)
|
||||
result = ""
|
||||
|
||||
@@ -181,7 +181,7 @@ class Module(ModelSQL, ModelView):
|
||||
else:
|
||||
file_p = tools.file_open(os.path.join(name, '__tryton__.py'))
|
||||
data = file_p.read()
|
||||
info = eval(data)
|
||||
info = tools.safe_eval(data)
|
||||
file_p.close()
|
||||
except:
|
||||
return {}
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
#this repository contains the full copyright notices and license terms.
|
||||
"Rule"
|
||||
from trytond.model import ModelView, ModelSQL, fields
|
||||
from trytond.tools import Cache
|
||||
from trytond.tools import Cache, safe_eval
|
||||
import time
|
||||
|
||||
|
||||
@@ -188,7 +188,7 @@ class Rule(ModelSQL, ModelView):
|
||||
recur=['many2one'], root_tech='user', root='User')
|
||||
# Use root user without context to prevent recursion
|
||||
for rule in self.browse(cursor, 0, ids):
|
||||
dom = eval("[('%s', '%s', %s)]" % \
|
||||
dom = safe_eval("[('%s', '%s', %s)]" % \
|
||||
(rule.field.name, rule.operator,
|
||||
operand2query[rule.operand]),
|
||||
{'user': self.pool.get('res.user').browse(cursor, 0,
|
||||
|
||||
@@ -272,12 +272,12 @@ class EvalEnvironment(dict):
|
||||
def __getattr__(self, item):
|
||||
return self.__getitem__(item)
|
||||
|
||||
def get(self, item):
|
||||
def get(self, item, default=None):
|
||||
try:
|
||||
return self.__getitem__(item)
|
||||
except:
|
||||
pass
|
||||
return super(EvalEnvironment, self).get(item)
|
||||
return super(EvalEnvironment, self).get(item, default=default)
|
||||
|
||||
def __nonzero__(self):
|
||||
return bool(self._record)
|
||||
@@ -2,6 +2,7 @@
|
||||
#this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.model.fields.field import Field
|
||||
from trytond.tools import safe_eval
|
||||
|
||||
|
||||
class Reference(Field):
|
||||
@@ -63,7 +64,7 @@ class Reference(Field):
|
||||
ref_obj = model.pool.get(ref_model)
|
||||
ref_id_found.setdefault(ref_model, set())
|
||||
try:
|
||||
ref_id = eval(ref_id)
|
||||
ref_id = safe_eval(ref_id)
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
|
||||
@@ -4,6 +4,7 @@ from trytond.model import Model
|
||||
from trytond.model import fields
|
||||
from trytond.model.browse import BrowseRecordList, BrowseRecord, BrowseRecordNull
|
||||
from trytond.model.browse import EvalEnvironment
|
||||
from trytond.tools import safe_eval
|
||||
import datetime
|
||||
import time
|
||||
from decimal import Decimal
|
||||
@@ -813,7 +814,7 @@ class ModelStorage(Model):
|
||||
context=context):
|
||||
if not line.values:
|
||||
continue
|
||||
xml_values = eval(line.values, {
|
||||
xml_values = safe_eval(line.values, {
|
||||
'Decimal': Decimal,
|
||||
'datetime': datetime,
|
||||
})
|
||||
@@ -920,7 +921,7 @@ class ModelStorage(Model):
|
||||
else:
|
||||
if not env:
|
||||
return True
|
||||
res.append(eval(arg, env))
|
||||
res.append(safe_eval(arg, env))
|
||||
elif isinstance(arg, tuple):
|
||||
res.append(arg)
|
||||
elif isinstance(arg, list):
|
||||
@@ -1002,7 +1003,7 @@ class ModelStorage(Model):
|
||||
env['time'] = time
|
||||
env['context'] = context
|
||||
env['active_id'] = record.id
|
||||
required = eval(field.states['required'], env)
|
||||
required = safe_eval(field.states['required'], env)
|
||||
if required and not record[field_name]:
|
||||
self.raise_user_error(cursor,
|
||||
'required_validation_record',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
#this repository contains the full copyright notices and license terms.
|
||||
from trytond.model import Model
|
||||
from trytond.tools import Cache
|
||||
from trytond.tools import Cache, safe_eval
|
||||
from trytond.modules import create_graph, get_module_list
|
||||
from lxml import etree
|
||||
try:
|
||||
@@ -179,7 +179,7 @@ class ModelView(Model):
|
||||
raise_p = True
|
||||
for arch, domain, _ in sql_inherit:
|
||||
if domain:
|
||||
if not eval(domain, {'context': context}):
|
||||
if not safe_eval(domain, {'context': context}):
|
||||
continue
|
||||
if not arch or not arch.strip():
|
||||
continue
|
||||
|
||||
@@ -144,7 +144,8 @@ def create_graph(module_list, force=None):
|
||||
mod_path = OPJ(ep.dist.location, 'trytond', 'modules', module)
|
||||
if os.path.isfile(tryton_file) or zipfile.is_zipfile(mod_path+'.zip'):
|
||||
try:
|
||||
info = eval(tools.file_open(tryton_file, subdir='').read())
|
||||
info = tools.safe_eval(tools.file_open(tryton_file,
|
||||
subdir='').read())
|
||||
except:
|
||||
logger.error('%s:eval file %s' % (module, tryton_file))
|
||||
raise
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ import copy
|
||||
from trytond.model import ModelView, ModelSQL, fields
|
||||
from trytond.wizard import Wizard
|
||||
from lxml import etree
|
||||
from trytond.tools import Cache
|
||||
from trytond.tools import Cache, safe_eval
|
||||
from trytond.backend import TableHandler
|
||||
from trytond.security import get_connections
|
||||
import string
|
||||
@@ -257,7 +257,7 @@ class User(ModelSQL, ModelView):
|
||||
date = date.replace(i, j)
|
||||
res['locale'] = {
|
||||
'date': date,
|
||||
'grouping': eval(user.language.grouping),
|
||||
'grouping': safe_eval(user.language.grouping),
|
||||
'decimal_point': user.language.decimal_point,
|
||||
'thousands_sep': user.language.thousands_sep,
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import unittest
|
||||
import time
|
||||
from decimal import Decimal
|
||||
from trytond import pysocket
|
||||
from trytond.tools import reduce_ids
|
||||
from trytond.tools import reduce_ids, safe_eval
|
||||
from lxml import etree
|
||||
|
||||
ADMIN_PASSWORD = 'admin'
|
||||
@@ -102,6 +102,32 @@ class ToolsTestCase(unittest.TestCase):
|
||||
reduce_ids('id', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15,
|
||||
18, 19, 21]))
|
||||
|
||||
def test0060safe_eval_builtin(self):
|
||||
'''
|
||||
Attempt to access a unsafe builtin.
|
||||
'''
|
||||
self.assertRaises(Exception, safe_eval, "open('test.txt', 'w')")
|
||||
|
||||
def test0061safe_eval_getattr(self):
|
||||
'''
|
||||
Attempt to get arround direct attr access.
|
||||
'''
|
||||
self.assertRaises(Exception, safe_eval, "getattr(int, '__abs__')")
|
||||
|
||||
def test0062safe_eval_func_globals(self):
|
||||
'''
|
||||
Attempt to access global enviroment where fun was defined.
|
||||
'''
|
||||
self.assertRaises(Exception, safe_eval,
|
||||
"def x(): pass; print x.func_globals")
|
||||
|
||||
def test0063safe_eval_lowlevel(self):
|
||||
'''
|
||||
Lowlevel tricks to access 'object'.
|
||||
'''
|
||||
self.assertRaises(Exception, safe_eval,
|
||||
"().__class__.mro()[1].__subclasses__()")
|
||||
|
||||
|
||||
class FieldsTestCase(unittest.TestCase):
|
||||
'''
|
||||
|
||||
@@ -9,6 +9,7 @@ except ImportError:
|
||||
import os.path
|
||||
from trytond.config import CONFIG
|
||||
from trytond.version import VERSION
|
||||
from trytond.tools import safe_eval
|
||||
import time
|
||||
from xml import sax
|
||||
from decimal import Decimal
|
||||
@@ -54,7 +55,7 @@ class MenuitemTagHandler:
|
||||
values[attr] = attributes.get(attr)
|
||||
|
||||
if attributes.get('active'):
|
||||
values['active'] = bool(eval(attributes['active']))
|
||||
values['active'] = bool(safe_eval(attributes['active']))
|
||||
|
||||
if values.get('parent'):
|
||||
values['parent'] = self.mh.get_id(values['parent'])
|
||||
@@ -201,7 +202,8 @@ class RecordTagHandler:
|
||||
f_obj = self.mh.pool.get(search_model)
|
||||
answer = f_obj.browse(
|
||||
self.mh.cursor, self.mh.user,
|
||||
f_obj.search(self.mh.cursor, self.mh.user, eval(search_attr),
|
||||
f_obj.search(self.mh.cursor, self.mh.user,
|
||||
safe_eval(search_attr),
|
||||
context={'active_test': False}))
|
||||
|
||||
if not answer: return
|
||||
@@ -221,7 +223,7 @@ class RecordTagHandler:
|
||||
context['version'] = VERSION.rsplit('.', 1)[0]
|
||||
context['ref'] = self.mh.get_id
|
||||
context['obj'] = lambda *a: 1
|
||||
self.values[field_name] = eval(eval_attr, context)
|
||||
self.values[field_name] = safe_eval(eval_attr, context)
|
||||
|
||||
else:
|
||||
raise Exception("Tags '%s' not supported inside tag record."% (name,))
|
||||
@@ -511,7 +513,7 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
|
||||
return False
|
||||
ref_mode, ref_id = browse_record[key].split(',', 1)
|
||||
try:
|
||||
ref_id = eval(ref_id)
|
||||
ref_id = safe_eval(ref_id)
|
||||
except:
|
||||
pass
|
||||
if isinstance(ref_id, (list, tuple)):
|
||||
@@ -581,7 +583,7 @@ class TrytondXmlHandler(sax.handler.ContentHandler):
|
||||
if not old_values:
|
||||
old_values = {}
|
||||
else:
|
||||
old_values = eval(old_values, {
|
||||
old_values = safe_eval(old_values, {
|
||||
'Decimal': Decimal,
|
||||
'datetime': datetime,
|
||||
})
|
||||
|
||||
@@ -18,6 +18,7 @@ try:
|
||||
import cStringIO as StringIO
|
||||
except ImportError:
|
||||
import StringIO
|
||||
import dis
|
||||
|
||||
def find_in_path(name):
|
||||
if os.name == "nt":
|
||||
@@ -492,3 +493,45 @@ def reduce_ids(field, ids):
|
||||
','.join(('%s',) * len(discontinue_list)) + '))')
|
||||
args.extend(discontinue_list)
|
||||
return '(' + ' OR '.join(sql) + ')', args
|
||||
|
||||
_ALLOWED_CODES = set(dis.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',
|
||||
'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_LSHIFT','BINARY_RSHIFT','BINARY_AND','BINARY_XOR', 'BINARY_OR',
|
||||
'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', 'BINARY_SUBSCR',
|
||||
] if x in dis.opmap)
|
||||
|
||||
|
||||
def safe_eval(source, data=None):
|
||||
if '__subclasses__' in source:
|
||||
raise ValueError('__subclasses__ not allowed')
|
||||
c = compile(source, '', 'eval')
|
||||
codes = []
|
||||
s = c.co_code
|
||||
i = 0
|
||||
while i < len(s):
|
||||
code = ord(s[i])
|
||||
codes.append(code)
|
||||
if code >= dis.HAVE_ARGUMENT:
|
||||
i += 3
|
||||
else:
|
||||
i += 1
|
||||
for code in codes:
|
||||
if code not in _ALLOWED_CODES:
|
||||
raise ValueError('opcode %s not allowed' % dis.opname[code])
|
||||
return eval(c, {'__builtins__': {
|
||||
'True': True,
|
||||
'False': False,
|
||||
'str': str,
|
||||
'globals': locals,
|
||||
'locals': locals,
|
||||
'bool': bool,
|
||||
'dict': dict,
|
||||
}}, data)
|
||||
@@ -7,7 +7,7 @@ import time
|
||||
from trytond.model import ModelView, ModelSQL, fields
|
||||
from trytond.version import PACKAGE, VERSION, WEBSITE
|
||||
from trytond.report import Report
|
||||
from trytond.tools import reduce_ids
|
||||
from trytond.tools import reduce_ids, safe_eval
|
||||
|
||||
|
||||
class Collection(ModelSQL, ModelView):
|
||||
@@ -271,7 +271,7 @@ class Collection(ModelSQL, ModelView):
|
||||
if not model_obj:
|
||||
return res
|
||||
model_ids = model_obj.search(cursor, user,
|
||||
eval(collection.domain or "[]"), context=context)
|
||||
safe_eval(collection.domain or "[]"), context=context)
|
||||
for child in model_obj.browse(cursor, user,
|
||||
model_ids, context=context):
|
||||
if '/' in child.rec_name:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
#this repository contains the full copyright notices and license terms.
|
||||
from trytond.pool import Pool
|
||||
from trytond.tools import safe_eval
|
||||
|
||||
|
||||
class EnvCall(object):
|
||||
@@ -55,7 +56,7 @@ def eval_expr(cursor, user, model, obj_id, action, context=None):
|
||||
elif line =='False':
|
||||
res = False
|
||||
else:
|
||||
res = eval(line, env)
|
||||
res = safe_eval(line, env)
|
||||
return res
|
||||
|
||||
def execute(cursor, user, model, obj_id, activity, context=None):
|
||||
|
||||
Reference in new issue
Block a user