Authz refactor w/ orthogonal capabilities#2936
Open
GregorShear wants to merge 3 commits into
Open
Conversation
GregorShear
commented
May 11, 2026
Comment on lines
+103
to
+109
| if required.is_empty() { | ||
| debug_assert!( | ||
| false, | ||
| "is_authorized called with empty orthogonal capabilities" | ||
| ); | ||
| return false; | ||
| } |
Contributor
Author
There was a problem hiding this comment.
asking is_authorized()? with empty capabilities returns false
9c4d16d to
4ebba92
Compare
GregorShear
commented
May 11, 2026
Comment on lines
+1015
to
+1020
| let (rg, ug, uid) = build_orthogonal_scenario( | ||
| vec![("acmeCo/", vec![Write, Assume])], | ||
| vec![("acmeCo/", "bobCo/shared/", vec![Read, Billing, TeamAdmin])], | ||
| ); | ||
| assert_authorized(&rg, &ug, uid, "acmeCo/", vec![Write]); | ||
| assert_not_authorized(&rg, &ug, uid, "bobCo/shared/", vec![Write]); |
Contributor
Author
There was a problem hiding this comment.
note potentially non-obvious behavior
Adds an orthogonal capability system alongside the existing hierarchical (read/write/admin) authorization model. Both RoleGrant::is_authorized and UserGrant::is_authorized now accept `impl Into<AnyCapability>`, dispatching to either the legacy BFS (transitive_roles/GrantRef) or the new orthogonal BFS (reachable_nodes/NodeRef).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an orthogonal capability model that coexists with the legacy
read/write/adminhierarchy. Existing authorization paths (PostgREST, transitive-roles BFS) are untouched. New GraphQL authorization checks can opt into orthogonal capabilities, and we'll migrate existing GraphQL checks over one at a time. Once the GraphQL API covers everything PostgREST does, PostgREST and the legacy capability path can be retired together.What changes
A new
OrthogonalCapabilityenum androle_grants.capabilities/user_grants.capabilitiescolumns let a grant carry an independent set of capabilities, rather than a single level in a hierarchy. This is finer-grained than the legacy roles: instead ofadminimplying everything, a grant lists exactly which capabilities it confers.Special capabilities:
delegate— a grant carryingdelegatecan propagate its own capabilities to the next hop. The next hop's effective set isnode.capabilities ∩ edge.capabilities— you can only pass on capabilities you actually hold. Withoutdelegate, the capabilities apply at the object, but cannot chain further.assume— a grant carryingassumeis a trust root: the next hop inherits the full capability set declared on the edge, with no intersection against the parent's caps. Used when delegating complete authority (e.g. a user grant that says "this user fully impersonatestenantA/groups/editors/"), and as the BFS seed marker so user_grants get their declared capabilities through unfiltered.In short:
delegatecarries your own permissions forward;assumecarries the edge's permissions forward.Coexistence with legacy
AnyCapabilitywraps either a single legacyCapabilityor aVec<OrthogonalCapability>.RoleGrant::is_authorizedandUserGrant::is_authorizeddispatch on the variant: the legacy arm runs the existingtransitive_rolesBFS unchanged; the orthogonal arm runs a newreachable_nodesBFS that respects thedelegate/assumerules above. Call sites pick which model they want.Migration path
The two systems live side-by-side indefinitely. GraphQL authorization checks get migrated to orthogonal capabilities one at a time as we gain confidence. When the GraphQL API has full coverage of what PostgREST does today, PostgREST is retired and the legacy
capabilitycolumn / BFS can be dropped.Test plan
supabase db resetapplies cleanlycargo sqlx prepare --workspaceis up to datecargo check -p control-plane-apiandcargo test -p tablespassdelegatepropagation,assumetrust-root semantics, terminal nodes, multi-path capability union, andRoleGrantreachability