summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Simonis <chs@openerp.com>2014-04-15 15:36:50 +0200
committerChristophe Simonis <chs@openerp.com>2014-04-15 15:36:50 +0200
commit3d2e9b4d2b6647016268ca3b5f7dd5c523a4fbb6 (patch)
tree8014712ec2ae83aa443eebca36fe253249ef6173
parentf8d5ef0f80c216abd2e810edd4b9761c69c36dad (diff)
parent001a034e587624e0bcca88b55d8cb5a580bec3a1 (diff)
[MERGE] forward port of branch 7.0 up to revid 5281 launchpad_translations_on_behalf_of_openerp-20140412094159-mhy3v2prb3ctx32k
bzr revid: jke@openerp.com-20140409153527-mic1a8afcvdhsd27 bzr revid: chs@openerp.com-20140410102422-fcwxhjk40z0oy8x5 bzr revid: chs@openerp.com-20140415133650-y46i0o3qkb2atbvi
-rw-r--r--openerp/addons/base/module/module.py49
-rw-r--r--openerp/addons/base/static/src/js/apps.js4
-rw-r--r--openerp/addons/base/tests/test_expression.py12
-rw-r--r--openerp/osv/expression.py5
-rw-r--r--openerp/sql_db.py8
-rw-r--r--openerp/tools/safe_eval.py80
-rw-r--r--setup.py2
7 files changed, 92 insertions, 68 deletions
diff --git a/openerp/addons/base/module/module.py b/openerp/addons/base/module/module.py
index d55061c3900..db93c12da22 100644
--- a/openerp/addons/base/module/module.py
+++ b/openerp/addons/base/module/module.py
@@ -31,6 +31,7 @@ import shutil
import tempfile
import urllib
import urllib2
+import urlparse
import zipfile
import zipimport
import lxml.html
@@ -41,6 +42,7 @@ except ImportError:
from StringIO import StringIO # NOQA
import openerp
+import openerp.exceptions
from openerp import modules, tools, addons
from openerp.modules.db import create_categories
from openerp.tools.parse_version import parse_version
@@ -620,40 +622,14 @@ class module(osv.osv):
return res
def download(self, cr, uid, ids, download=True, context=None):
- res = []
- default_version = modules.adapt_version('1.0')
- for mod in self.browse(cr, uid, ids, context=context):
- if not mod.url:
- continue
- match = re.search('-([a-zA-Z0-9\._-]+)(\.zip)', mod.url, re.I)
- version = default_version
- if match:
- version = match.group(1)
- if parse_version(mod.installed_version) >= parse_version(version):
- continue
- res.append(mod.url)
- if not download:
- continue
- zip_content = urllib.urlopen(mod.url).read()
- fname = modules.get_module_path(str(mod.name) + '.zip', downloaded=True)
- try:
- with open(fname, 'wb') as fp:
- fp.write(zip_content)
- except Exception:
- _logger.exception('Error when trying to create module '
- 'file %s', fname)
- raise orm.except_orm(_('Error'), _('Can not create the module file:\n %s') % (fname,))
- terp = self.get_module_info(mod.name)
- self.write(cr, uid, mod.id, self.get_values_from_terp(terp))
- cr.execute('DELETE FROM ir_module_module_dependency WHERE module_id = %s', (mod.id,))
- self._update_dependencies(cr, uid, mod, terp.get('depends', []))
- self._update_category(cr, uid, mod, terp.get('category', 'Uncategorized'))
- # Import module
- zimp = zipimport.zipimporter(fname)
- zimp.load_module(mod.name)
- return res
+ return []
def install_from_urls(self, cr, uid, urls, context=None):
+ if not self.pool['res.users'].has_group(cr, uid, 'base.group_system'):
+ raise openerp.exceptions.AccessDenied()
+
+ apps_server = urlparse.urlparse(self.get_apps_server(cr, uid, context=context))
+
OPENERP = 'openerp'
tmp = tempfile.mkdtemp()
_logger.debug('Install from url: %r', urls)
@@ -662,6 +638,11 @@ class module(osv.osv):
for module_name, url in urls.items():
if not url:
continue # nothing to download, local version is already the last one
+
+ up = urlparse.urlparse(url)
+ if up.scheme != apps_server.scheme or up.netloc != apps_server.netloc:
+ raise openerp.exceptions.AccessDenied()
+
try:
_logger.info('Downloading module `%s` from OpenERP Apps', module_name)
content = urllib2.urlopen(url).read()
@@ -726,8 +707,8 @@ class module(osv.osv):
finally:
shutil.rmtree(tmp)
- def install_by_names(self, cr, uid, names, context=None):
- raise NotImplementedError('# TODO')
+ def get_apps_server(self, cr, uid, context=None):
+ return tools.config.get('apps_server', 'https://apps.openerp.com/apps')
def _update_dependencies(self, cr, uid, mod_browse, depends=None):
if depends is None:
diff --git a/openerp/addons/base/static/src/js/apps.js b/openerp/addons/base/static/src/js/apps.js
index e339ed2230e..115d35fcc4f 100644
--- a/openerp/addons/base/static/src/js/apps.js
+++ b/openerp/addons/base/static/src/js/apps.js
@@ -63,8 +63,8 @@ openerp.base = function(instance) {
if (instance.base.apps_client) {
return check_client_available(instance.base.apps_client);
} else {
- var ICP = new instance.web.Model('ir.config_parameter');
- return ICP.call('get_param', ['apps.server', 'https://apps.openerp.com/apps']).then(function(u) {
+ var Mod = new instance.web.Model('ir.module.module');
+ return Mod.call('get_apps_server').then(function(u) {
var link = $(_.str.sprintf('<a href="%s"></a>', u))[0];
var host = _.str.sprintf('%s//%s', link.protocol, link.host);
var dbname = link.pathname;
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 52dc4a265ba..d3231c54462 100644
--- a/openerp/osv/expression.py
+++ b/openerp/osv/expression.py
@@ -1182,14 +1182,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))
diff --git a/openerp/sql_db.py b/openerp/sql_db.py
index 694ae02b3d9..3eb50d84494 100644
--- a/openerp/sql_db.py
+++ b/openerp/sql_db.py
@@ -167,7 +167,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.
@@ -313,7 +313,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):
@@ -484,12 +484,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 508a072a3b2..2f280685350 100644
--- a/openerp/tools/safe_eval.py
+++ b/openerp/tools/safe_eval.py
@@ -71,7 +71,8 @@ _SAFE_OPCODES = _EXPR_OPCODES.union(set(opmap[x] for x in [
'CONTINUE_LOOP', 'RAISE_VARARGS',
# 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__)
@@ -86,16 +87,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
@@ -110,15 +160,13 @@ 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):
raise
except Exception, e:
import sys
exc_info = sys.exc_info()
raise ValueError, '"%s" while compiling\n%r' % (ustr(e), expr), exc_info[2]
- 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
@@ -187,19 +235,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 = {}
diff --git a/setup.py b/setup.py
index c776b910a8c..24c1df899d1 100644
--- a/setup.py
+++ b/setup.py
@@ -136,7 +136,7 @@ setuptools.setup(
'python-openid',
'pytz',
'pyusb >= 1.0.0b1',
- 'pywebdav',
+ 'pywebdav < 0.9.8',
'pyyaml',
'qrcode',
'reportlab', # windows binary pypi.python.org/pypi/reportlab