Honour group object-permissions in BaseVisibilityManager (#1731)#1740
Conversation
BaseVisibilityManager.visible_to_user only joined the *userobjectpermission table, while Manager.user_can resolves group object-permissions (_default_user_can runs with include_group_permissions=True). A user whose only READ grant on a model using the un-overridden manager body -- CorpusActionExecution -- was via a group therefore passed obj.user_can(user, READ) yet was excluded from Model.objects.visible_to_user(user): authorized for the row but never able to see it in a list query. This is the #1714 follow-up tracked by issue #1731. #1714 closed the identical drift in the shared/QuerySets.py bodies but deliberately left the structurally identical manager body for a follow-up. The body now also resolves the *groupobjectpermission table (filtered by the requesting user's group ids) and ORs Q(id__in=<group subquery>) into the visibility filter, mirroring the QuerySets.py fix. The user- and group-table lookups sit in separate try/except LookupError blocks so a model missing the group table never discards already-resolved user-level grants; group ids resolve as a lazy values_list subquery, keeping the lookup a single round-trip. New CorpusActionExecutionGroupVisibilityTest pins filter/check equivalence for a group-shared user, the read/write asymmetry (a group READ grant never widens to UPDATE/DELETE), non-member exclusion, and graceful degradation when the guardian tables cannot be resolved. Closes #1731
Code Review — PR #1740: Honour group object-permissions in BaseVisibilityManagerOverviewThis PR closes issue #1731 — the follow-up to #1714 — by mirroring the group-permission fix from The fix is clean, the separation into two independent Issues1. Misleading warning messages (minor correctness bug) Both
These messages will mislead anyone diagnosing permission issues from logs. Suggested replacements: # user-level except:
logger.warning(
f"Permission model {app_label}.{permission_model_name} not found. "
"User-level grants will be skipped; group-level grants (if resolvable) still apply."
)
# group-level except:
logger.warning(
f"Group permission model {app_label}.{group_permission_model_name} not found. "
"Group-level grants will be skipped; user-level grants still apply."
)2. Degradation test covers both-tables-fail but not partial failure
Worth adding a test that patches only the user-level table to fail and asserts that group grants still work, explicitly confirming that the two-block separation does what the PR description says: def test_group_grants_survive_when_user_table_missing(self):
real_get_model = apps.get_model
def fake(app_label, model_name=None, *args, **kwargs):
name = model_name if model_name is not None else app_label
if 'userobjectpermission' in name and 'group' not in name:
raise LookupError(name)
return real_get_model(app_label, model_name, *args, **kwargs)
with patch.object(apps, 'get_model', side_effect=fake):
visible_ids = set(
CorpusActionExecution.objects.visible_to_user(self.group_user)
.values_list('pk', flat=True)
)
self.assertIn(self.group_exec.pk, visible_ids) # group grants surviveWithout this test the resilience claim is documented but not exercised. Nitpicks
VerdictThe bug fix is correct and the core test matrix (filter/check equivalence, write-escalation guard, non-member exclusion, graceful degradation) is solid. The two issues above are real:
Both are straightforward to address before merge. Everything else is cosmetic. |
Signed-off-by: JSIV <5049984+JSv4@users.noreply.github.com>
Code Review — PR #1740: Honour group object-permissions in
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Summary
BaseVisibilityManager.visible_to_useronly joined the*userobjectpermissionguardian table, whileManager.user_canresolves group object-permissions (_default_user_canruns withinclude_group_permissions=True). A user whose only READ grant on a model using the un-overridden manager body —CorpusActionExecution— was via a group therefore passedobj.user_can(user, READ)yet was excluded fromModel.objects.visible_to_user(user): authorized for the row but never able to see it in a list query.This is the follow-up tracked by issue #1731. PR #1724 (issue #1714) closed the structurally identical drift in the
shared/QuerySets.pybodies but deliberately left the manager body for a follow-up, sinceCorpusActionExecutionis the only model reaching it un-overridden (CorpusActionExecutionManager.visible_to_usermerely re-wrapssuper().visible_to_user(...)in a custom QuerySet).BaseVisibilityManager.visible_to_usernow also resolves the*groupobjectpermissiontable, filtered by the requesting user's group ids, and ORsQ(id__in=<group subquery>)into the visibility filter — mirroring the QuerySet.visible_to_user ignores group object-permissions (Document/Annotation/Note) — filter/check drift vs Manager.user_can #1714QuerySets.pyfix.try/except LookupErrorblocks, so a model missing the group table never discards already-resolved user-level grants.values_listsubquery, keeping the lookup a single round-trip (query count independent of how many groups the user belongs to).CorpusActionExecutionGroupVisibilityTestpins filter/check equivalence for a group-shared user, the read/write asymmetry (a group READ grant never widens to UPDATE/DELETE), non-member exclusion, and graceful degradation when the guardian tables cannot be resolved.Closes #1731
Test plan
docker compose -f test.yml run --rm django pytest opencontractserver/tests/test_corpus_action_execution.py::CorpusActionExecutionGroupVisibilityTest --keepdbdocker compose -f test.yml run --rm django pytest opencontractserver/tests/test_corpus_action_execution.py::CorpusActionExecutionPermissionsTestCase --keepdb— no regression on existingvisible_to_usertestsdocker compose -f test.yml run --rm django pytest opencontractserver/tests/permissioning/test_permissioned_querysets.py::GroupObjectPermissionVisibilityTest --keepdb— sibling QuerySet.visible_to_user ignores group object-permissions (Document/Annotation/Note) — filter/check drift vs Manager.user_can #1714 suite still greenNote: the backend suite could not be run in the authoring environment — the Docker image build is blocked by the network policy (pip/wget cannot verify TLS through the proxy). CI runs these tests.
Generated by Claude Code