Skip to content

Commit 15680c9

Browse files
committed
[MERGE] forward port of branch saas-3 up to revid 5115 [email protected]
bzr revid: [email protected]
2 parents ec2ea66 + 3d2e9b4 commit 15680c9

File tree

7 files changed

+93
-69
lines changed

7 files changed

+93
-69
lines changed

openerp/addons/base/module/module.py

+15-34
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import tempfile
3232
import urllib
3333
import urllib2
34+
import urlparse
3435
import zipfile
3536
import zipimport
3637
import lxml.html
@@ -41,6 +42,7 @@
4142
from StringIO import StringIO # NOQA
4243

4344
import openerp
45+
import openerp.exceptions
4446
from openerp import modules, tools
4547
from openerp.modules.db import create_categories
4648
from openerp.modules import get_module_resource
@@ -621,40 +623,14 @@ def update_list(self, cr, uid, context=None):
621623
return res
622624

623625
def download(self, cr, uid, ids, download=True, context=None):
624-
res = []
625-
default_version = modules.adapt_version('1.0')
626-
for mod in self.browse(cr, uid, ids, context=context):
627-
if not mod.url:
628-
continue
629-
match = re.search('-([a-zA-Z0-9\._-]+)(\.zip)', mod.url, re.I)
630-
version = default_version
631-
if match:
632-
version = match.group(1)
633-
if parse_version(mod.installed_version) >= parse_version(version):
634-
continue
635-
res.append(mod.url)
636-
if not download:
637-
continue
638-
zip_content = urllib.urlopen(mod.url).read()
639-
fname = modules.get_module_path(str(mod.name) + '.zip', downloaded=True)
640-
try:
641-
with open(fname, 'wb') as fp:
642-
fp.write(zip_content)
643-
except Exception:
644-
_logger.exception('Error when trying to create module '
645-
'file %s', fname)
646-
raise orm.except_orm(_('Error'), _('Can not create the module file:\n %s') % (fname,))
647-
terp = self.get_module_info(mod.name)
648-
self.write(cr, uid, mod.id, self.get_values_from_terp(terp))
649-
cr.execute('DELETE FROM ir_module_module_dependency WHERE module_id = %s', (mod.id,))
650-
self._update_dependencies(cr, uid, mod, terp.get('depends', []))
651-
self._update_category(cr, uid, mod, terp.get('category', 'Uncategorized'))
652-
# Import module
653-
zimp = zipimport.zipimporter(fname)
654-
zimp.load_module(mod.name)
655-
return res
626+
return []
656627

657628
def install_from_urls(self, cr, uid, urls, context=None):
629+
if not self.pool['res.users'].has_group(cr, uid, 'base.group_system'):
630+
raise openerp.exceptions.AccessDenied()
631+
632+
apps_server = urlparse.urlparse(self.get_apps_server(cr, uid, context=context))
633+
658634
OPENERP = 'openerp'
659635
tmp = tempfile.mkdtemp()
660636
_logger.debug('Install from url: %r', urls)
@@ -663,6 +639,11 @@ def install_from_urls(self, cr, uid, urls, context=None):
663639
for module_name, url in urls.items():
664640
if not url:
665641
continue # nothing to download, local version is already the last one
642+
643+
up = urlparse.urlparse(url)
644+
if up.scheme != apps_server.scheme or up.netloc != apps_server.netloc:
645+
raise openerp.exceptions.AccessDenied()
646+
666647
try:
667648
_logger.info('Downloading module `%s` from OpenERP Apps', module_name)
668649
content = urllib2.urlopen(url).read()
@@ -727,8 +708,8 @@ def install_from_urls(self, cr, uid, urls, context=None):
727708
finally:
728709
shutil.rmtree(tmp)
729710

730-
def install_by_names(self, cr, uid, names, context=None):
731-
raise NotImplementedError('# TODO')
711+
def get_apps_server(self, cr, uid, context=None):
712+
return tools.config.get('apps_server', 'https://apps.openerp.com/apps')
732713

733714
def _update_dependencies(self, cr, uid, mod_browse, depends=None):
734715
if depends is None:

openerp/addons/base/static/src/js/apps.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ openerp.base = function(instance) {
6363
if (instance.base.apps_client) {
6464
return check_client_available(instance.base.apps_client);
6565
} else {
66-
var ICP = new instance.web.Model('ir.config_parameter');
67-
return ICP.call('get_param', ['apps.server', 'https://apps.openerp.com/apps']).then(function(u) {
66+
var Mod = new instance.web.Model('ir.module.module');
67+
return Mod.call('get_apps_server').then(function(u) {
6868
var link = $(_.str.sprintf('<a href="%s"></a>', u))[0];
6969
var host = _.str.sprintf('%s//%s', link.protocol, link.host);
7070
var dbname = link.pathname;

openerp/addons/base/tests/test_expression.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def test_20_auto_join(self):
183183
self.assertIn('res_partner_bank', sql_query[0],
184184
"_auto_join off: ('bank_ids.name', 'like', '..') first query incorrect main table")
185185

186-
expected = "%s like %s" % (unaccent('"res_partner_bank"."name"'), unaccent('%s'))
186+
expected = "%s::text like %s" % (unaccent('"res_partner_bank"."name"'), unaccent('%s'))
187187
self.assertIn(expected, sql_query[1],
188188
"_auto_join off: ('bank_ids.name', 'like', '..') first query incorrect where condition")
189189

@@ -223,7 +223,7 @@ def test_20_auto_join(self):
223223
self.assertIn('"res_partner_bank" as "res_partner__bank_ids"', sql_query[0],
224224
"_auto_join on: ('bank_ids.name', 'like', '..') query incorrect join")
225225

226-
expected = "%s like %s" % (unaccent('"res_partner__bank_ids"."name"'), unaccent('%s'))
226+
expected = "%s::text like %s" % (unaccent('"res_partner__bank_ids"."name"'), unaccent('%s'))
227227
self.assertIn(expected, sql_query[1],
228228
"_auto_join on: ('bank_ids.name', 'like', '..') query incorrect where condition")
229229

@@ -305,7 +305,7 @@ def test_20_auto_join(self):
305305
self.assertIn('"res_country"', sql_query[0],
306306
"_auto_join on for state_id: ('state_id.country_id.code', 'like', '..') query 1 incorrect main table")
307307

308-
expected = "%s like %s" % (unaccent('"res_country"."code"'), unaccent('%s'))
308+
expected = "%s::text like %s" % (unaccent('"res_country"."code"'), unaccent('%s'))
309309
self.assertIn(expected, sql_query[1],
310310
"_auto_join on for state_id: ('state_id.country_id.code', 'like', '..') query 1 incorrect where condition")
311311

@@ -339,7 +339,7 @@ def test_20_auto_join(self):
339339
self.assertIn('"res_country" as "res_country_state__country_id"', sql_query[0],
340340
"_auto_join on for country_id: ('state_id.country_id.code', 'like', '..') query 1 incorrect join")
341341

342-
expected = "%s like %s" % (unaccent('"res_country_state__country_id"."code"'), unaccent('%s'))
342+
expected = "%s::text like %s" % (unaccent('"res_country_state__country_id"."code"'), unaccent('%s'))
343343
self.assertIn(expected, sql_query[1],
344344
"_auto_join on for country_id: ('state_id.country_id.code', 'like', '..') query 1 incorrect where condition")
345345

@@ -373,7 +373,7 @@ def test_20_auto_join(self):
373373
self.assertIn('"res_country" as "res_partner__state_id__country_id"', sql_query[0],
374374
"_auto_join on: ('state_id.country_id.code', 'like', '..') query incorrect join")
375375

376-
expected = "%s like %s" % (unaccent('"res_partner__state_id__country_id"."code"'), unaccent('%s'))
376+
expected = "%s::text like %s" % (unaccent('"res_partner__state_id__country_id"."code"'), unaccent('%s'))
377377
self.assertIn(expected, sql_query[1],
378378
"_auto_join on: ('state_id.country_id.code', 'like', '..') query incorrect where condition")
379379

@@ -403,7 +403,7 @@ def test_20_auto_join(self):
403403
# Test produced queries that domains effectively present
404404
sql_query = self.query_list[0].get_sql()
405405

406-
expected = "%s like %s" % (unaccent('"res_partner__child_ids__bank_ids"."acc_number"'), unaccent('%s'))
406+
expected = "%s::text like %s" % (unaccent('"res_partner__child_ids__bank_ids"."acc_number"'), unaccent('%s'))
407407
self.assertIn(expected, sql_query[1],
408408
"_auto_join on one2many with domains incorrect result")
409409
# TDE TODO: check first domain has a correct table name

openerp/osv/expression.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1182,14 +1182,15 @@ def __leaf_to_sql(self, eleaf):
11821182
else:
11831183
need_wildcard = operator in ('like', 'ilike', 'not like', 'not ilike')
11841184
sql_operator = {'=like': 'like', '=ilike': 'ilike'}.get(operator, operator)
1185+
cast = '::text' if sql_operator.endswith('like') else ''
11851186

11861187
if left in model._columns:
11871188
format = need_wildcard and '%s' or model._columns[left]._symbol_set[0]
11881189
unaccent = self._unaccent if sql_operator.endswith('like') else lambda x: x
11891190
column = '%s.%s' % (table_alias, _quote(left))
1190-
query = '(%s %s %s)' % (unaccent(column), sql_operator, unaccent(format))
1191+
query = '(%s%s %s %s)' % (unaccent(column), cast, sql_operator, unaccent(format))
11911192
elif left in MAGIC_COLUMNS:
1192-
query = "(%s.\"%s\" %s %%s)" % (table_alias, left, sql_operator)
1193+
query = "(%s.\"%s\"%s %s %%s)" % (table_alias, left, cast, sql_operator)
11931194
params = right
11941195
else: # Must not happen
11951196
raise ValueError("Invalid field %r in domain term %r" % (left, leaf))

openerp/sql_db.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ def __init__(self, pool, dbname, serialized=True):
167167
self.sql_log_count = 0
168168
self._closed = True # avoid the call of close() (by __del__) if an exception
169169
# is raised by any of the following initialisations
170-
self._pool = pool
170+
self.__pool = pool
171171
self.dbname = dbname
172172

173173
# Whether to enable snapshot isolation level for this cursor.
@@ -313,7 +313,7 @@ def _close(self, leak=False):
313313
chosen_template = tools.config['db_template']
314314
templates_list = tuple(set(['template0', 'template1', 'postgres', chosen_template]))
315315
keep_in_pool = self.dbname not in templates_list
316-
self._pool.give_back(self._cnx, keep_in_pool=keep_in_pool)
316+
self.__pool.give_back(self._cnx, keep_in_pool=keep_in_pool)
317317

318318
@check
319319
def autocommit(self, on):
@@ -537,12 +537,12 @@ class Connection(object):
537537

538538
def __init__(self, pool, dbname):
539539
self.dbname = dbname
540-
self._pool = pool
540+
self.__pool = pool
541541

542542
def cursor(self, serialized=True):
543543
cursor_type = serialized and 'serialized ' or ''
544544
_logger.debug('create %scursor to %r', cursor_type, self.dbname)
545-
return Cursor(self._pool, self.dbname, serialized=serialized)
545+
return Cursor(self.__pool, self.dbname, serialized=serialized)
546546

547547
def test_cursor(self, serialized=True):
548548
cursor_type = serialized and 'serialized ' or ''

openerp/tools/safe_eval.py

+61-19
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@
7171
'CONTINUE_LOOP', 'RAISE_VARARGS',
7272
# New in Python 2.7 - http://bugs.python.org/issue4715 :
7373
'JUMP_IF_FALSE_OR_POP', 'JUMP_IF_TRUE_OR_POP', 'POP_JUMP_IF_FALSE',
74-
'POP_JUMP_IF_TRUE', 'SETUP_EXCEPT', 'END_FINALLY'
74+
'POP_JUMP_IF_TRUE', 'SETUP_EXCEPT', 'END_FINALLY', 'LOAD_FAST',
75+
'LOAD_GLOBAL', # Only allows access to restricted globals
7576
] if x in opmap))
7677

7778
_logger = logging.getLogger(__name__)
@@ -86,16 +87,65 @@ def _get_opcodes(codeobj):
8687
[100, 100, 23, 100, 100, 102, 103, 83]
8788
"""
8889
i = 0
89-
opcodes = []
9090
byte_codes = codeobj.co_code
9191
while i < len(byte_codes):
9292
code = ord(byte_codes[i])
93-
opcodes.append(code)
93+
yield code
94+
9495
if code >= HAVE_ARGUMENT:
9596
i += 3
9697
else:
9798
i += 1
98-
return opcodes
99+
100+
def assert_no_dunder_name(code_obj, expr):
101+
""" assert_no_dunder_name(code_obj, expr) -> None
102+
103+
Asserts that the code object does not refer to any "dunder name"
104+
(__$name__), so that safe_eval prevents access to any internal-ish Python
105+
attribute or method (both are loaded via LOAD_ATTR which uses a name, not a
106+
const or a var).
107+
108+
Checks that no such name exists in the provided code object (co_names).
109+
110+
:param code_obj: code object to name-validate
111+
:type code_obj: CodeType
112+
:param str expr: expression corresponding to the code object, for debugging
113+
purposes
114+
:raises NameError: in case a forbidden name (containing two underscores)
115+
is found in ``code_obj``
116+
117+
.. note:: actually forbids every name containing 2 underscores
118+
"""
119+
for name in code_obj.co_names:
120+
if "__" in name:
121+
raise NameError('Access to forbidden name %r (%r)' % (name, expr))
122+
123+
def assert_valid_codeobj(allowed_codes, code_obj, expr):
124+
""" Asserts that the provided code object validates against the bytecode
125+
and name constraints.
126+
127+
Recursively validates the code objects stored in its co_consts in case
128+
lambdas are being created/used (lambdas generate their own separated code
129+
objects and don't live in the root one)
130+
131+
:param allowed_codes: list of permissible bytecode instructions
132+
:type allowed_codes: set(int)
133+
:param code_obj: code object to name-validate
134+
:type code_obj: CodeType
135+
:param str expr: expression corresponding to the code object, for debugging
136+
purposes
137+
:raises ValueError: in case of forbidden bytecode in ``code_obj``
138+
:raises NameError: in case a forbidden name (containing two underscores)
139+
is found in ``code_obj``
140+
"""
141+
assert_no_dunder_name(code_obj, expr)
142+
for opcode in _get_opcodes(code_obj):
143+
if opcode not in allowed_codes:
144+
raise ValueError(
145+
"opcode %s not allowed (%r)" % (opname[opcode], expr))
146+
for const in code_obj.co_consts:
147+
if isinstance(const, CodeType):
148+
assert_valid_codeobj(allowed_codes, const, 'lambda')
99149

100150
def test_expr(expr, allowed_codes, mode="eval"):
101151
"""test_expr(expression, allowed_codes[, mode]) -> code_object
@@ -110,15 +160,13 @@ def test_expr(expr, allowed_codes, mode="eval"):
110160
# eval() does not like leading/trailing whitespace
111161
expr = expr.strip()
112162
code_obj = compile(expr, "", mode)
113-
except (SyntaxError, TypeError):
163+
except (SyntaxError, TypeError, ValueError):
114164
raise
115165
except Exception, e:
116166
import sys
117167
exc_info = sys.exc_info()
118168
raise ValueError, '"%s" while compiling\n%r' % (ustr(e), expr), exc_info[2]
119-
for code in _get_opcodes(code_obj):
120-
if code not in allowed_codes:
121-
raise ValueError("opcode %s not allowed (%r)" % (opname[code], expr))
169+
assert_valid_codeobj(allowed_codes, code_obj, expr)
122170
return code_obj
123171

124172

@@ -187,19 +235,13 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal
187235
This can be used to e.g. evaluate
188236
an OpenERP domain expression from an untrusted source.
189237
190-
Throws TypeError, SyntaxError or ValueError (not allowed) accordingly.
191-
192-
>>> safe_eval("__import__('sys').modules")
193-
Traceback (most recent call last):
194-
...
195-
ValueError: opcode LOAD_NAME not allowed
196-
238+
:throws TypeError: If the expression provided is a code object
239+
:throws SyntaxError: If the expression provided is not valid Python
240+
:throws NameError: If the expression provided accesses forbidden names
241+
:throws ValueError: If the expression provided uses forbidden bytecode
197242
"""
198243
if isinstance(expr, CodeType):
199-
raise ValueError("safe_eval does not allow direct evaluation of code objects.")
200-
201-
if '__subclasses__' in expr:
202-
raise ValueError('expression not allowed (__subclasses__)')
244+
raise TypeError("safe_eval does not allow direct evaluation of code objects.")
203245

204246
if globals_dict is None:
205247
globals_dict = {}

setup.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ def py2exe_options():
150150
#include_package_data = True,
151151
install_requires = [
152152
'pychart', # not on pypi, use: pip install http://download.gna.org/pychart/PyChart-1.39.tar.gz
153-
'babel',
153+
'babel >= 1.0',
154154
'docutils',
155155
'feedparser',
156156
'gdata',
@@ -171,7 +171,7 @@ def py2exe_options():
171171
'python-openid',
172172
'pytz',
173173
'pyusb >= 1.0.0b1',
174-
'pywebdav',
174+
'pywebdav <= 0.9.4',
175175
'pyyaml',
176176
'qrcode',
177177
'reportlab', # windows binary pypi.python.org/pypi/reportlab

0 commit comments

Comments
 (0)