diff --git a/odoo/addons/base/models/ir_config_parameter.py b/odoo/addons/base/models/ir_config_parameter.py index 34730022bbe..75be28c9359 100644 --- a/odoo/addons/base/models/ir_config_parameter.py +++ b/odoo/addons/base/models/ir_config_parameter.py @@ -64,7 +64,7 @@ class IrConfigParameter(models.Model): return self._get_param(key) or default @api.model - @ormcache('self._uid', 'key') + @ormcache('self.env.uid', 'self.env.su', 'key') def _get_param(self, key): params = self.search_read([('key', '=', key)], fields=['value'], limit=1) return params[0]['value'] if params else None diff --git a/odoo/addons/base/models/ir_model.py b/odoo/addons/base/models/ir_model.py index 4bb1ef7d840..1217a4d2cbb 100644 --- a/odoo/addons/base/models/ir_model.py +++ b/odoo/addons/base/models/ir_model.py @@ -988,7 +988,7 @@ class IrModelConstraint(models.Model): """ Delete PostgreSQL foreign keys and constraints tracked by this model. """ - if not (self._uid == SUPERUSER_ID or self.env.user.has_group('base.group_system')): + if not (self.env.su or self.env.user.has_group('base.group_system')): raise AccessError(_('Administrator access is required to uninstall a module')) ids_set = set(self.ids) @@ -1101,7 +1101,7 @@ class IrModelRelation(models.Model): """ Delete PostgreSQL many2many relations tracked by this model. """ - if not (self._uid == SUPERUSER_ID or self.env.user.has_group('base.group_system')): + if not (self.env.su or self.env.user.has_group('base.group_system')): raise AccessError(_('Administrator access is required to uninstall a module')) ids_set = set(self.ids) @@ -1216,9 +1216,9 @@ class IrModelAccess(models.Model): # not be really necessary as a cache key, unless the `ormcache_context` # decorator catches the exception (it does not at the moment.) @api.model - @tools.ormcache_context('self._uid', 'model', 'mode', 'raise_exception', keys=('lang',)) + @tools.ormcache_context('self.env.uid', 'self.env.su', 'model', 'mode', 'raise_exception', keys=('lang',)) def check(self, model, mode='read', raise_exception=True): - if self._uid == SUPERUSER_ID: + if self.env.su: # User root have all accesses return True @@ -1566,7 +1566,7 @@ class IrModelData(models.Model): the chance of gracefully deleting all records. This step is performed as part of the full uninstallation of a module. """ - if not (self._uid == SUPERUSER_ID or self.env.user.has_group('base.group_system')): + if not (self.env.su or self.env.user.has_group('base.group_system')): raise AccessError(_('Administrator access is required to uninstall a module')) # enable model/field deletion diff --git a/odoo/addons/base/models/ir_rule.py b/odoo/addons/base/models/ir_rule.py index 6f6a0d9863f..63102084dbd 100644 --- a/odoo/addons/base/models/ir_rule.py +++ b/odoo/addons/base/models/ir_rule.py @@ -115,7 +115,7 @@ class IrRule(models.Model): if mode not in self._MODES: raise ValueError('Invalid mode: %r' % (mode,)) - if self._uid == SUPERUSER_ID: + if self.env.su: return self.browse(()) query = """ SELECT r.id FROM ir_rule r JOIN ir_model m ON (r.model_id=m.id) @@ -132,7 +132,7 @@ class IrRule(models.Model): @api.model @tools.conditional( 'xml' not in config['dev_mode'], - tools.ormcache('self._uid', 'model_name', 'mode', + tools.ormcache('self.env.uid', 'self.env.su', 'model_name', 'mode', 'tuple(self._compute_domain_context_values())'), ) def _compute_domain(self, model_name, mode="read"): diff --git a/odoo/addons/base/models/res_partner.py b/odoo/addons/base/models/res_partner.py index 87822b05279..4116fc2a6b8 100644 --- a/odoo/addons/base/models/res_partner.py +++ b/odoo/addons/base/models/res_partner.py @@ -579,7 +579,7 @@ class Partner(models.Model): result = True # To write in SUPERUSER on field is_company and avoid access rights problems. - if 'is_company' in vals and self.user_has_groups('base.group_partner_manager') and not self.env.uid == SUPERUSER_ID: + if 'is_company' in vals and self.user_has_groups('base.group_partner_manager') and not self.env.su: result = super(Partner, self.sudo()).write({'is_company': vals.get('is_company')}) del vals['is_company'] result = result and super(Partner, self).write(vals) diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py index c5b749ae279..143f50ec2ad 100644 --- a/odoo/addons/base/models/res_users.py +++ b/odoo/addons/base/models/res_users.py @@ -437,7 +437,7 @@ class Users(models.Model): @api.model def _search(self, args, offset=0, limit=None, order=None, count=False, access_rights_uid=None): - if self._uid != SUPERUSER_ID and args: + if not self.env.su and args: domain_fields = {term[0] for term in args if isinstance(term, (tuple, list))} if domain_fields.intersection(USER_PRIVATE_FIELDS): raise AccessError(_('Invalid search criterion')) diff --git a/odoo/addons/test_access_rights/tests/test_feedback.py b/odoo/addons/test_access_rights/tests/test_feedback.py index 1c349b75f6f..00a9c2c3fd4 100644 --- a/odoo/addons/test_access_rights/tests/test_feedback.py +++ b/odoo/addons/test_access_rights/tests/test_feedback.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from odoo import SUPERUSER_ID from odoo.exceptions import AccessError from odoo.tests import common, TransactionCase @@ -17,6 +18,64 @@ class Feedback(TransactionCase): }) +class TestSudo(Feedback): + """ Test the behavior of method sudo(). """ + def test_sudo(self): + record = self.env['test_access_right.some_obj'].create({'val': 5}) + user1 = self.user + user2 = self.env.ref('base.user_demo') + + # with_user(user) + record1 = record.with_user(user1) + self.assertEqual(record1.env.uid, user1.id) + self.assertFalse(record1.env.su) + + record2 = record1.with_user(user2) + self.assertEqual(record2.env.uid, user2.id) + self.assertFalse(record2.env.su) + + # the superuser is always in superuser mode + record3 = record2.with_user(SUPERUSER_ID) + self.assertEqual(record3.env.uid, SUPERUSER_ID) + self.assertTrue(record3.env.su) + + # sudo() + surecord1 = record1.sudo() + self.assertEqual(surecord1.env.uid, user1.id) + self.assertTrue(surecord1.env.su) + + surecord2 = record2.sudo() + self.assertEqual(surecord2.env.uid, user2.id) + self.assertTrue(surecord2.env.su) + + surecord3 = record3.sudo() + self.assertEqual(surecord3.env.uid, SUPERUSER_ID) + self.assertTrue(surecord3.env.su) + + # sudo().sudo() + surecord1 = surecord1.sudo() + self.assertEqual(surecord1.env.uid, user1.id) + self.assertTrue(surecord1.env.su) + + # sudo(False) + record1 = surecord1.sudo(False) + self.assertEqual(record1.env.uid, user1.id) + self.assertFalse(record1.env.su) + + record2 = surecord2.sudo(False) + self.assertEqual(record2.env.uid, user2.id) + self.assertFalse(record2.env.su) + + record3 = surecord3.sudo(False) + self.assertEqual(record3.env.uid, SUPERUSER_ID) + self.assertTrue(record3.env.su) + + # sudo().with_user(user) + record2 = surecord1.with_user(user2) + self.assertEqual(record2.env.uid, user2.id) + self.assertFalse(record2.env.su) + + class TestACLFeedback(Feedback): """ Tests that proper feedback is returned on ir.model.access errors """ diff --git a/odoo/api.py b/odoo/api.py index bf7e6b3fbc1..e0358bba202 100644 --- a/odoo/api.py +++ b/odoo/api.py @@ -750,7 +750,8 @@ class Environment(Mapping): - :attr:`cr`, the current database cursor; - :attr:`uid`, the current user id; - - :attr:`context`, the current context dictionary. + - :attr:`context`, the current context dictionary; + - :attr:`su`, whether in superuser mode. It provides access to the registry by implementing a mapping from model names to new api models. It also holds a cache for records, and a data @@ -782,9 +783,11 @@ class Environment(Mapping): """ cls._local.environments = Environments() - def __new__(cls, cr, uid, context): + def __new__(cls, cr, uid, context, su=False): + if uid == SUPERUSER_ID: + su = True assert context is not None - args = (cr, uid, context) + args = (cr, uid, context, su) # if env already exists, return it env, envs = None, cls.envs @@ -794,10 +797,11 @@ class Environment(Mapping): # otherwise create environment, and add it in the set self = object.__new__(cls) - self.cr, self.uid, self.context = self.args = (cr, uid, frozendict(context)) + args = (cr, uid, frozendict(context), su) + self.cr, self.uid, self.context, self.su = self.args = args self.registry = Registry(cr.dbname) self.cache = envs.cache - self._cache_key = (cr, uid) + self._cache_key = (cr, uid, su) self._protected = StackMap() # {field: ids, ...} self.dirty = defaultdict(set) # {record: set(field_name), ...} self.all = envs @@ -833,17 +837,19 @@ class Environment(Mapping): def __hash__(self): return object.__hash__(self) - def __call__(self, cr=None, user=None, context=None): + def __call__(self, cr=None, user=None, context=None, su=None): """ Return an environment based on ``self`` with modified parameters. :param cr: optional database cursor to change the current cursor :param user: optional user/user id to change the current user :param context: optional context dictionary to change the current context + :param su: optional boolean to change the superuser mode """ cr = self.cr if cr is None else cr uid = self.uid if user is None else int(user) context = self.context if context is None else context - return Environment(cr, uid, context) + su = (user is None and self.su) if su is None else su + return Environment(cr, uid, context, su) def ref(self, xml_id, raise_if_not_found=True): """ return the record corresponding to the given ``xml_id`` """ @@ -852,7 +858,7 @@ class Environment(Mapping): @property def user(self): """ return the current user (as an instance) """ - return self(user=SUPERUSER_ID)['res.users'].browse(self.uid) + return self(su=True)['res.users'].browse(self.uid) @property def company(self): diff --git a/odoo/fields.py b/odoo/fields.py index db856b646e4..5a146f6664d 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -659,7 +659,7 @@ class Field(MetaField('DummyField', (object,), {})): if 'force_company' not in context: company = records.env.company context = dict(context, force_company=company.id) - Property = records.env(user=SUPERUSER_ID, context=context)['ir.property'] + Property = records.env(context=context, su=True)['ir.property'] values = Property.get_multi(self.name, self.model_name, records.ids) for record in records: record[self.name] = values.get(record.id) @@ -670,7 +670,7 @@ class Field(MetaField('DummyField', (object,), {})): if 'force_company' not in context: company = records.env.company context = dict(context, force_company=company.id) - Property = records.env(user=SUPERUSER_ID, context=context)['ir.property'] + Property = records.env(context=context, su=True)['ir.property'] values = { record.id: self.convert_to_write(record[self.name], record) for record in records @@ -2929,6 +2929,5 @@ def prefetch_value_ids(record, field): # imported here to avoid dependency cycle issues -from odoo import SUPERUSER_ID from .exceptions import AccessError, MissingError, UserError from .models import check_pg_name, BaseModel, NewId, IdType diff --git a/odoo/models.py b/odoo/models.py index d44bc56a875..9e93514b207 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -2735,7 +2735,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): fields (as is if the fields is not falsy, or the readable/writable fields if fields is falsy). """ - if self._uid == SUPERUSER_ID: + if self.env.su: return fields or list(self._fields) def valid(fname): @@ -2922,7 +2922,7 @@ Fields: return env = self.env - cr, user, context = env.args + cr, user, context, su = env.args # make a query object for selecting ids, and apply security rules to it param_ids = object() @@ -3103,7 +3103,7 @@ Fields: :raise UserError: * if current ir.rules do not permit this operation. :return: None if the operation is allowed """ - if self._uid == SUPERUSER_ID: + if self.env.su: return invalid = self - self._filter_access_rules(operation) @@ -3135,7 +3135,7 @@ Fields: def _filter_access_rules(self, operation): """ Return the subset of ``self`` for which ``operation`` is allowed. """ - if self._uid == SUPERUSER_ID: + if self.env.su: return self if not self._ids: @@ -3337,7 +3337,7 @@ Fields: bad_names = {'id', 'parent_path'} if self._log_access: # the superuser can set log_access fields while loading registry - if not(self.env.uid == SUPERUSER_ID and not self.pool.ready): + if not(self.env.su and not self.pool.ready): bad_names.update(LOG_ACCESS_COLUMNS) # distribute fields into sets for various purposes @@ -3579,7 +3579,7 @@ Fields: bad_names = {'id', 'parent_path'} if self._log_access: # the superuser can set log_access fields while loading registry - if not(self.env.uid == SUPERUSER_ID and not self.pool.ready): + if not(self.env.su and not self.pool.ready): bad_names.update(LOG_ACCESS_COLUMNS) unknown_names = set() @@ -3995,7 +3995,7 @@ Fields: :param query: the current query object """ - if self._uid == SUPERUSER_ID: + if self.env.su: return def apply_rule(clauses, params, tables, parent_model=None): @@ -4813,14 +4813,12 @@ Fields: """ return self._browse(env, self._ids, self._prefetch_ids) - def sudo(self, user=SUPERUSER_ID): - """ sudo([user=SUPERUSER]) + def sudo(self, flag=True): + """ sudo([flag=True]) - Returns a new version of this recordset attached to the provided - user. - - By default this returns a ``SUPERUSER`` recordset, where access - control and record rules are bypassed. + Returns a new version of this recordset with superuser mode enabled or + disabled, depending on `flag`. The superuser mode does not change the + current user, and simply bypasses access rights checks. .. note:: @@ -4842,7 +4840,19 @@ Fields: The returned recordset has the same prefetch object as ``self``. """ - return self.with_env(self.env(user=user)) + if not isinstance(flag, bool): + _logger.warning("deprecated use of sudo(user), use with_user(user) instead", stack_info=True) + return self.with_user(flag) + return self.with_env(self.env(su=flag)) + + def with_user(self, user): + """ with_user(user) + + Return a new version of this recordset attached to the given user, in + non-superuser mode, unless `user` is the superuser (by convention, the + superuser is always in superuser mode.) + """ + return self.with_env(self.env(user=user, su=False)) def with_context(self, *args, **kwargs): """ with_context([context][, **overrides]) -> records diff --git a/odoo/osv/expression.py b/odoo/osv/expression.py index ebf6c56f628..ed480b60174 100644 --- a/odoo/osv/expression.py +++ b/odoo/osv/expression.py @@ -713,7 +713,7 @@ class expression(object): :var obj comodel: relational model of field (field.comodel) (res_partner.bank_ids -> res.partner.bank) """ - cr, uid, context = self.root_model.env.args + cr, uid, context, su = self.root_model.env.args def to_ids(value, comodel, leaf): """ Normalize a single id or name, or a list of those, into a list of ids