From f7bfe72e7f806414561a93a1d7a050cedc48374f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Sat, 14 Oct 2023 14:53:22 +0200 Subject: [PATCH] Filter search result of model with resource The user needs to have read access to the resource target. Closes #12588 --- trytond/trytond/ir/resource.py | 39 ++++++++++++++++++++++++++ trytond/trytond/tests/test_resource.py | 25 +++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/trytond/trytond/ir/resource.py b/trytond/trytond/ir/resource.py index 6cb5808a68..cbfa0fd40c 100644 --- a/trytond/trytond/ir/resource.py +++ b/trytond/trytond/ir/resource.py @@ -1,11 +1,15 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. + +from collections import defaultdict + from sql.conditionals import Coalesce from trytond.i18n import lazy_gettext from trytond.model import Index, ModelSQL, ModelStorage, ModelView, fields from trytond.pool import Pool from trytond.pyson import Eval +from trytond.tools import grouped_slice from trytond.transaction import Transaction, without_check_access __all__ = ['ResourceAccessMixin', 'ResourceMixin', 'resource_copy'] @@ -61,6 +65,41 @@ class ResourceAccessMixin(ModelStorage): return [ (model, {'create': 'write', 'delete': 'write'}.get(mode, mode))] + @classmethod + def search( + cls, domain, offset=0, limit=None, order=None, count=False, + query=False): + transaction = Transaction() + result = super().search( + domain, offset=offset, limit=limit, order=order, + count=False if not query else count, query=query) + if not query and transaction.user and transaction.check_access: + records = result + resources = defaultdict(set) + allowed = set() + with without_check_access(): + records = cls.browse(records) + for record in records: + if record.resource: + resources[record.resource.__class__].add( + record.resource.id) + + for Model, ids in resources.items(): + for sub_ids in grouped_slice(ids): + allowed.update(Model.search([ + ('id', 'in', list(sub_ids)), + ])) + + records = [ + r for r in records + if not r.resource or r.resource in allowed] + if count: + result = len(records) + else: + # re-browse to have same context + result = cls.browse(records) + return result + @classmethod def read(cls, ids, fields_names): cls.check_access(ids, mode='read') diff --git a/trytond/trytond/tests/test_resource.py b/trytond/trytond/tests/test_resource.py index 4c2c51e6ac..cf11020ed4 100644 --- a/trytond/trytond/tests/test_resource.py +++ b/trytond/trytond/tests/test_resource.py @@ -78,3 +78,28 @@ class ResourceTestCase(unittest.TestCase): note = Note(note.id) self.assertEqual(user_note.write_date, write_date) + + @with_transaction() + def test_resources_rule(self): + "Test resources rules are applied on search" + pool = Pool() + Note = pool.get('ir.note') + Warning = pool.get('res.user.warning') + + warning1 = Warning(user=0, name="root") + warning1.save() + note1 = Note(resource=warning1) + note1.save() + warning2 = Warning(user=1, name="admin") + warning2.save() + note2 = Note(resource=warning2) + note2.save() + + with Transaction().set_context(_check_access=True): + notes = Note.search([]) + count = Note.search([], count=True) + query = Note.search([], query=True) + + self.assertEqual(notes, [note2]) + self.assertEqual(count, 1) + self.assertTrue(query)