[FIX] orm: access error due to prefetch of indirectly referenced records.

The new-api record prefetching algorithm attempts
to load data for all known records from the requested
model (i.e. all IDs present in the environment cache),
regardless of how indirectly/remotely they were
referenced. An indirect parent record may therefore
be prefetched along with its directly browsed children,
possibly crossing company boundaries involuntarily.

This patch implements a fallback mechanism when
the prefetching failed due to what looks like an
ACL restriction.

The implementation of `_read_from_database` handle
ACL directly and set an `AccessError` as cache value
for restricted records.

If a model (like `mail.message`) overwrites `read` to
implements its own ACL checks and raises an `AccessError`
before calling `super()` (which will then call
`_read_from_database`), the cache will be not fill,
leading to an unexpected exception.

If this commit messae looks familiar to you, that's
simply because this is the new-api counterpart of
b7865502e4
This commit is contained in:
Christophe Simonis 2015-07-24 14:01:09 +02:00
parent ee145347a3
commit af9393d505
3 changed files with 28 additions and 1 deletions

View File

@ -19,6 +19,7 @@
# #
############################################################################## ##############################################################################
from openerp.exceptions import AccessError
from openerp.osv import osv, fields from openerp.osv import osv, fields
class res_partner(osv.Model): class res_partner(osv.Model):
@ -49,6 +50,8 @@ class Category(models.Model):
name = fields.Char(required=True) name = fields.Char(required=True)
parent = fields.Many2one('test_new_api.category') parent = fields.Many2one('test_new_api.category')
display_name = fields.Char(compute='_compute_display_name', inverse='_inverse_display_name') display_name = fields.Char(compute='_compute_display_name', inverse='_inverse_display_name')
discussions = fields.Many2many('test_new_api.discussion', 'test_new_api_discussion_category',
'category', 'discussion')
@api.one @api.one
@api.depends('name', 'parent.display_name') # this definition is recursive @api.depends('name', 'parent.display_name') # this definition is recursive
@ -74,6 +77,10 @@ class Category(models.Model):
# assign name of last category, and reassign display_name (to normalize it) # assign name of last category, and reassign display_name (to normalize it)
self.name = names[-1].strip() self.name = names[-1].strip()
def read(self, fields=None, load='_classic_read'):
if self.search_count([('id', 'in', self._ids), ('name', '=', 'NOACCESS')]):
raise AccessError('Sorry')
return super(Category, self).read(fields, load)
class Discussion(models.Model): class Discussion(models.Model):
_name = 'test_new_api.discussion' _name = 'test_new_api.discussion'

View File

@ -4,6 +4,7 @@
from datetime import date, datetime from datetime import date, datetime
from collections import defaultdict from collections import defaultdict
from openerp.exceptions import AccessError
from openerp.tests import common from openerp.tests import common
from openerp.exceptions import except_orm from openerp.exceptions import except_orm
@ -339,6 +340,24 @@ class TestNewFields(common.TransactionCase):
self.assertEqual(data['display_name'], display_name) self.assertEqual(data['display_name'], display_name)
self.assertEqual(data['size'], size) self.assertEqual(data['size'], size)
def test_31_prefetch(self):
""" test prefetch of records handle AccessError """
Category = self.env['test_new_api.category']
cat_1 = Category.create({'name': 'NOACCESS'}).id
cat_2 = Category.create({'name': 'ACCESS', 'parent': cat_1}).id
self.env.clear()
cat = Category.browse(cat_2)
self.assertEqual(cat.name, 'ACCESS')
# both categories should be in prefetch ids
self.assertSetEqual(self.env.prefetch[Category._name], set([cat_1, cat_2]))
# but due to our (lame) overwrite of `read`, it should not forbid us to read records we have access to
self.assertFalse(len(cat.discussions))
self.assertEqual(cat.parent.id, cat_1)
with self.assertRaises(AccessError):
Category.browse(cat_1).name
def test_40_new(self): def test_40_new(self):
""" test new records. """ """ test new records. """
discussion = self.env.ref('test_new_api.discussion_0') discussion = self.env.ref('test_new_api.discussion_0')

View File

@ -3230,7 +3230,8 @@ class BaseModel(object):
try: try:
result = records.read(list(fnames), load='_classic_write') result = records.read(list(fnames), load='_classic_write')
except AccessError: except AccessError:
pass # not all records may be accessible, try with only current record
result = self.read(list(fnames), load='_classic_write')
# check the cache, and update it if necessary # check the cache, and update it if necessary
if not self._cache.contains(field): if not self._cache.contains(field):