Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions edi_core_oca/models/edi_exchange_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -616,8 +616,6 @@ def _search(self, domain, offset=0, limit=None, order=None):
extend_ids = list(extend_query)
result.extend(extend_ids[: limit - len(result)])

# Restore original ordering
result = [x for x in orig_ids if x in result]
if set(orig_ids) != set(result):
# Create a virgin query
query = self.browse(result)._as_query()
Expand Down
73 changes: 73 additions & 0 deletions edi_core_oca/tests/test_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,76 @@ def test_no_group_no_read_child(self):
msg = rf"not allowed to access '{model._description}' \({model._name}\)"
with self.assertRaisesRegex(AccessError, msg):
child_exchange_record.with_user(self.user).read()

def test_search_pagination_with_inaccessible_middle_records(self):
"""
Regression test:
If some records in the first page are filtered out due to access rules,
_search must fetch additional records from next pages without truncating them.
"""

self.user.write({"groups_id": [(4, self.group.id)]})

# Two different companies are used to trigger multi-company access filtering
company_1 = self.env.ref("base.main_company")
company_2 = self.env["res.company"].create({"name": "Other Company"})

# Three target records:
# - consumer_c1 and consumer_c3 belong to the active company and are readable
# - consumer_c2 belongs to another company and will be filtered out
# by access rules
consumer_c1 = self.env["res.partner"].create(
{"name": "c1-a", "company_id": company_1.id}
)
consumer_c2 = self.env["res.partner"].create(
{"name": "c2", "company_id": company_2.id}
)
consumer_c3 = self.env["res.partner"].create(
{"name": "c1-b", "company_id": company_1.id}
)

# One EDI records pointing to readable target records
self.backend.create_record(
"test_csv_output",
{"model": consumer_c1._name, "res_id": consumer_c1.id},
)

# One EDI records pointing to records from another company
self.backend.create_record(
"test_csv_output",
{"model": consumer_c2._name, "res_id": consumer_c2.id},
)

# One EDI records pointing to readable target records
visible_id_2 = self.backend.create_record(
"test_csv_output",
{"model": consumer_c3._name, "res_id": consumer_c3.id},
).id

# Restrict the environment to company_1 only, activating the multi-company rule
# that will hide records pointing to consumer_c2
env_company_1 = self.env(
context=dict(self.env.context, allowed_company_ids=[company_1.id])
)

# Execute the search as a non-superuser:
# - super()._search returns the first 2 IDs (1 visible + 1 hidden)
# - custom logic removes the 1 hidden
# - pagination logic fetches 1 more record from the next page
records = (
env_company_1["edi.exchange.record"]
.with_user(self.user)
.search([], limit=2, order="id asc")
)

# The result must NOT be truncated: the search should still return `
# limit` records
self.assertEqual(
len(records),
2,
"Search results were truncated when inaccessible records were "
"present in the first page",
)

# The records fetched from the second page must be present in the final result
self.assertIn(visible_id_2, records.ids)