Issue #3586311: Harden usage record and listing access checks.
Description
The usage record page (view ai context usage) rendered context item labels and full content without checking $context_item->access('view'), allowing usage-only viewers to read unpublished items they cannot otherwise access. Related fields on the same page and in the usage listing also rendered links without matching route or entity access.
This MR gates those disclosures behind the same access checks used elsewhere in the module, bubbles access-result cacheability on every branch, and adds functional tests for the record page, usage listing, and context item list operations.
Changes
Usage record controller (AiContextUsageRecordController)
- Gate context item
toLink()and the "Context item content (current)" details block on$context_item->access('view'), withContext item @id (no access)fallback. - Gate the agent field on
ai_context.settings.agent_editroute access; show plain text when denied. - Show
@type:@id (no access)when the target entity exists but view access is denied; show plain@type:@idwhen the entity cannot be loaded. - Bubble access-result cacheability for context item, target entity, and agent route checks via
CacheableMetadata::applyTo().
Usage listing Views fields
AgentLinkField: apply the same agent edit route access check; render plain text when denied; bubble access cacheability on both allowed and denied branches.ContextItemLinkField(new): gate the context item column on$context_item->access('view'); showContext item @id (no access)instead of an empty cell when denied; bubble access cacheability.AiContextUsageViewsData: wire both custom field handlers foragent_idandcontext_item_id.
Access handler (AiContextItemAccessControlHandler)
- Return
cachePerUser()for unpublished item view checks wheneverview ai context itemsis held, since access depends on ownership for non-owners too.
Context item list (AiContextItemListBuilder)
- Gate the "Add subcontext" operation on create access for the current user.
Tests
AiContextUsageRecordAccessTest(new): unpublished context item hidden from usage-only viewer (label, content, target entity fallback); missing target entity shows plaintype:id(not(no access)); published item visible to admin; agent link respects context edit route access on record page and usage listing; usage listing context item column respects view access; owner-based access cacheability bubbled to the response (usercache context).AiContextItemListTest: "Add subcontext" operation requires create permission.
Behavior note
Users with only view ai context usage now see (no access) fallbacks for context items and target entities they cannot view, and plain-text agent IDs instead of configuration links. This is intended privilege separation.
Testing instructions
# From the ai_context module directory:
./lint.sh
# From the project root (DDEV):
ddev exec vendor/bin/phpunit \
web/modules/contrib/ai_context/tests/src/Functional/AiContextUsageRecordAccessTest.php
ddev exec vendor/bin/phpunit \
--filter testAddSubcontextOperationRequiresCreateAccess \
web/modules/contrib/ai_context/tests/src/Functional/AiContextItemListTest.phpManual checks (as admin and as a usage-only viewer):
/admin/config/ai/context/usage— context item and agent columns respect access.- A single usage record page — context item label/content, agent link, and target entity respect access.
Checklist
- I have updated the MR title to use format:
Issue #1234: My issue title. - I have updated the MR description to include:
Closes #1234 - I have performed a self-review of my own code
- I have added or updated tests, or explained in the description why this change is not covered by tests
- I have updated documentation for any new or changed functionality
- I have written testing instructions and verified them locally
- I have noted any required post-merge steps (config imports, cache rebuilds, manual changes)
- This MR contains no breaking API or hook changes, or they are explicitly documented in the description
AI Compliance
Note
Check the one that best describes your usage, or leave all unchecked if AI was not significantly used.
-
AI Assisted Code
Mainly written by a human; AI used for autocomplete or partial generation under full human supervision. -
AI Generated Code
-
Vibe Coded
Closes #3586311 (closed)