Repository navigation
feat(authorization): add a grants-only pre-check for staged writes - #1013
Conversation
A caller that stages a write and pre-checks it before commit, such as a staged upsert, needs to report a related document granting a role the caller does not hold even when the caller also lacks permission on the parent. authorizeCreate() and authorizeUpdate() check the parent first, so the grant refusal never surfaced at staging. authorizeGrants() runs only the grant part of those pre-checks: the Hook\Nested preparation, the staged view through $resolve and the same Exception\Authorization and Exception\Structure refusals, with no create or update permission check and no write. The grant check never reads the stored parent, so it takes no $old and is the same for a create and an update. While grants are not checked it does nothing and reads nothing, matching the toggle being off everywhere else. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to
|
What
Adds
Database::authorizeGrants(string $collection, Document $document, ?callable $resolve = null): void(8.1.1, additive).It runs only the grant part of
authorizeCreate()/authorizeUpdate(): a related document given as a document may add permissions only for roles the caller holds. Same preparation and same refusals:Hook\Nestedhooks (a hook-assigned$idis judged new);$resolve(collection, id)gives the staged view of a related document, as forauthorizeCreate()(empty = staged for deletion = new);Exception\Authorization(Permissions must be one of: (<roles>)), malformed permissions throwException\Structure.It checks no create or update permission on the collection, the document, or any related collection/document, and writes nothing. It does nothing (and reads nothing) unless
setCheckGrants(true), authorization is enabled, and the relationship hook is attached and enabled; while it runs, a missing collection throwsException\NotFound.There is no
$oldparameter: the grant check never reads the stored parent, so the result is the same whether the later write creates or updates.$documentcan be the full document or the changes.Why
A caller that stages writes and pre-checks them before commit (a staged single upsert in a transaction) must report a related document granting an unheld role even when the caller also lacks permission on the parent collection.
authorizeCreate()/authorizeUpdate()check the parent first, so the grant refusal could not surface at staging. Callers runauthorizeGrants()first, then the create/update pre-check.Tests
tests/unit/Relationships/AuthorizeGrantsTest.php, Memory + SQLite × all four relationship types (56 cases):authorizeCreate/authorizeUpdaterefuse the parent,authorizeGrantsreports the grant (the motivating case);authorizeCreate()andauthorizeUpdate();$resolve(staged permissions count as current, staged deletion is new), parity withauthorizeCreate();Hook\Nested: hooks run parent-first at every depth, rewritten IDs judged new and not resolved, grants checked as deep as the write relates, caller's document unchanged; hook-assigned stored ID judged new;withCheckGrants(false), underAuthorization::skip(), underskipRelationships()(no hook, no resolver, no collection read);Exception\NotFoundfor a missing collection while on.Fail-first: without the change all 56 fail (
Call to undefined method Database::authorizeGrants()).Docs: CHANGELOG 8.1.1 (Added), UPGRADE grant-check section.
🤖 Generated with Claude Code
Summary by CodeRabbit
authorizeGrants()to check permissions granted by related documents without checking create or update permissions on the target document or collection. Checks run only when grant checking, authorization, and the relationship hook are enabled.