Add new tool safe_eval

This commit is contained in:
Cédric Krier committed 2009-09-28 11:21:11 +02:00
1 parent f3d71ce308
commit b427d21224
19 files changed
+111 -31

No files matched your search

+1
View File
@@ -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
+2 -1
View File
@@ -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'
+2 -1
View File
@@ -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
View File
@@ -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 -2
View File
@@ -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
View File
@@ -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 = ""
+1 -1
View File
@@ -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
View File
@@ -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,
+2 -2
View File
@@ -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 -1
View File
@@ -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 -3
View File
@@ -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',
+2 -2
View File
@@ -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
+2 -1
View File
@@ -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
View File
@@ -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,
}
+27 -1
View File
@@ -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):
'''
+7 -5
View File
@@ -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,
})
+43
View File
@@ -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)
+2 -2
View File
@@ -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:
+2 -1
View File
@@ -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):