diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index b479f6d97a9..3fcd074aeb0 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -27,7 +27,7 @@ from cStringIO import StringIO import odoo import odoo.modules.registry -from odoo.api import Environment +from odoo.api import call_kw, Environment from odoo.modules import get_resource_path from odoo.tools import topological_sort from odoo.tools.translate import _ @@ -35,6 +35,7 @@ from odoo.tools.misc import str2bool, xlwt from odoo import http from odoo.http import request, serialize_exception as _serialize_exception, content_disposition from odoo.exceptions import AccessError +from odoo.models import check_method_name _logger = logging.getLogger(__name__) @@ -840,10 +841,8 @@ class DataSet(http.Controller): return self._call_kw(model, method, args, {}) def _call_kw(self, model, method, args, kwargs): - if method.startswith('_'): - raise AccessError(_("Underscore prefixed methods cannot be remotely called")) - - return getattr(request.registry.get(model), method)(request.cr, request.uid, *args, **kwargs) + check_method_name(method) + return call_kw(request.env[model], method, args, kwargs) @http.route('/web/dataset/call', type='json', auth="user") def call(self, model, method, args, domain_id=None, context_id=None): diff --git a/addons/web/static/src/js/web_client.js b/addons/web/static/src/js/web_client.js index 5fc1f3f2796..c6033d6cbad 100644 --- a/addons/web/static/src/js/web_client.js +++ b/addons/web/static/src/js/web_client.js @@ -67,9 +67,9 @@ return AbstractWebClient.extend({ logo_edit: function(ev) { var self = this; ev.preventDefault(); - self.alive(new Model("res.users").get_func("read")(session.uid, ["company_id"])).then(function(res) { + self.alive(new Model("res.users").call("read", [[session.uid], ["company_id"]])).then(function(data) { self.rpc("/web/action/load", { action_id: "base.action_res_company_form" }).done(function(result) { - result.res_id = res.company_id[0]; + result.res_id = data[0].company_id[0]; result.target = "new"; result.views = [[false, 'form']]; result.flags = { @@ -94,7 +94,8 @@ return AbstractWebClient.extend({ var state = $.bbq.getState(true); if (_.isEmpty(state) || state.action === "login") { self.menu.is_bound.done(function() { - new Model("res.users").call("read", [session.uid, ["action_id"]]).done(function(data) { + new Model("res.users").call("read", [[session.uid], ["action_id"]]).done(function(result) { + var data = result[0]; if(data.action_id) { self.action_manager.do_action(data.action_id[0]); self.menu.open_action(data.action_id[0]); diff --git a/openerp/api.py b/openerp/api.py index fb71a3c5b5c..d5d258164ae 100644 --- a/openerp/api.py +++ b/openerp/api.py @@ -42,6 +42,7 @@ __all__ = [ 'cr_uid_ids', 'cr_uid_ids_context', 'cr_uid_records', 'cr_uid_records_context', 'constrains', 'depends', 'onchange', 'returns', + 'call_kw', ] import logging @@ -76,6 +77,19 @@ WRAPPED_ATTRS = ('__module__', '__name__', '__doc__', '_constrains', INHERITED_ATTRS = ('_returns',) +class Params(object): + def __init__(self, args, kwargs): + self.args = args + self.kwargs = kwargs + def __str__(self): + params = [] + for arg in self.args: + params.append(repr(arg)) + for item in sorted(self.kwargs.iteritems()): + params.append("%s=%r" % item) + return ', '.join(params) + + class Meta(type): """ Metaclass that automatically decorates traditional-style methods by guessing their API. It also implements the inheritance of the @@ -643,6 +657,30 @@ def expected(decorator, func): +def call_kw_model(method, self, args, kwargs): + context, args, kwargs = split_context(method, args, kwargs) + recs = self.with_context(context or {}) + _logger.debug("call %s.%s(%s)", recs, method.__name__, Params(args, kwargs)) + result = method(recs, *args, **kwargs) + return downgrade(method, result, recs, args, kwargs) + +def call_kw_multi(method, self, args, kwargs): + ids, args = args[0], args[1:] + context, args, kwargs = split_context(method, args, kwargs) + recs = self.with_context(context or {}).browse(ids) + _logger.debug("call %s.%s(%s)", recs, method.__name__, Params(args, kwargs)) + result = method(recs, *args, **kwargs) + return downgrade(method, result, recs, args, kwargs) + +def call_kw(model, name, args, kwargs): + """ Invoke the given method ``name`` on the recordset ``model``. """ + method = getattr(type(model), name) + if getattr(method, '_api', None) == 'model': + return call_kw_model(method, model, args, kwargs) + else: + return call_kw_multi(method, model, args, kwargs) + + class Environment(Mapping): """ An environment wraps data for ORM records: diff --git a/openerp/models.py b/openerp/models.py index 9c7edc15f86..21258d77a98 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -113,6 +113,14 @@ def check_pg_name(name): if len(name) > 63: raise ValidationError("Table name %r is too long" % name) +# match private methods, to prevent their remote invocation +regex_private = re.compile(r'^(_.*|init)$') + +def check_method_name(name): + """ Raise an ``AccessError`` if ``name`` is a private method name. """ + if regex_private.match(name): + raise AccessError(_('Private methods (such as %s) cannot be called remotely.') % (name,)) + POSTGRES_CONFDELTYPES = { 'RESTRICT': 'r', 'NO ACTION': 'a', diff --git a/openerp/service/model.py b/openerp/service/model.py index 6cc4a3fbde9..02752355916 100644 --- a/openerp/service/model.py +++ b/openerp/service/model.py @@ -9,6 +9,7 @@ import time import openerp from openerp.exceptions import UserError, ValidationError, QWebException +from openerp.models import check_method_name from openerp.tools.translate import translate from openerp.tools.translate import _ @@ -164,10 +165,11 @@ def check(f): return wrapper def execute_cr(cr, uid, obj, method, *args, **kw): - object = openerp.registry(cr.dbname).get(obj) - if object is None: + recs = openerp.api.Environment(cr, uid, {}).get(obj) + if recs is None: raise UserError(_("Object %s doesn't exist") % obj) - return getattr(object, method)(cr, uid, *args, **kw) + return openerp.api.call_kw(recs, method, args, kw) + def execute_kw(db, uid, obj, method, args, kw=None): return execute(db, uid, obj, method, *args, **kw or {}) @@ -176,8 +178,7 @@ def execute_kw(db, uid, obj, method, args, kw=None): def execute(db, uid, obj, method, *args, **kw): threading.currentThread().dbname = db with openerp.registry(db).cursor() as cr: - if method.startswith('_'): - raise UserError(_('Private methods (such as %s) cannot be called remotely.') % (method,)) + check_method_name(method) res = execute_cr(cr, uid, obj, method, *args, **kw) if res is None: _logger.info('The method %s of the object %s can not return `None` !', method, obj)