RBAC scope-entity permission model consolidation & validation
- Dominant language
- Python
- Stars
- 670
- Forks
- 183
- Avg merge
- 17h 7m
- Merged PRs (30d)
- 358
Description
## Background
RBAC scope_entities (association_scopes_entities) and role_permission (permissions) are implemented per BEP-1048, but gaps remain:
1. scope_entities cannot express the maximum **delegable** permission per entity. Relations are distinguished only by relation_type (AUTO/REF), and REF's read-only meaning is not enforced as a permission cap.
2. Permission resolution does not clip scope-derived permissions by the relation's cap, so a permission granted at a scope can exceed what the relation should allow for the entity (e.g., a REF relation should only yield read-only).
3. RBAC scope_entities/role_permission repository access (querier/creator) is not consolidated into the repositories/ops layer.
## Resolution Model: 1-hop direct-scope query (no scope walk)
The Permission-bitmask-based permission check resolves permissions by querying only the scopes the target entity is directly associated with in scope_entities (1-hop). The recursive scope walk is removed.
- Premise: RBAC entity creation paths associate each entity directly with all of its effective scopes (primary + additional scope refs), so multi-hop traversal over scope-to-scope edges is redundant.
- Consequence: cap clipping reduces to a single-edge bitwise AND (granted permission AND the edge's permission_cap). No running-min accumulation along a multi-hop descent path is needed.
## Cap Semantics (design principle)
- cap is the \***ceiling on delegation through an edge**\*: it bounds how much of a scope's permission flows to the entity through that scope-entity edge. It is applied at \***resolution time**\* as a bitwise AND of the edge's permission_cap with the permission resolved via that scope (single-edge clip under the 1-hop model). A NULL cap means "no ceiling" (full).
- cap does \***not**\* reject or reduce a permission whose resolution does not cross the capped edge. The union/additive model has no deny; cap only bounds the permission flowing through its own edge. A permission resolved via one scope edge is never reduced by another edge's cap.
- \***Entity-scope direct grants bypass cap.**\* A permission whose scope is the target entity itself (scope_type/scope_id == the entity, i.e. the entity-as-scope pattern) — e.g. the read/write grant created by a VFolder invitation — is matched directly (Layer 1) and is not clipped by any edge cap. The ref edge's cap only stops the invitee's own scope permissions from applying beyond the cap (Layer 2).
## Goals
- Introduce a permission (cap) concept on scope_entities and reconcile AUTO/REF into the cap.
- Switch permission resolution to 1-hop direct-scope queries on the Permission bitmask, and enforce the cap by \***clipping edge-derived permissions (bitwise AND)**\* at resolution time (no rejection of assignment).
- Consolidate related repository access into repositories/ops.
## In Scope
- Finalize permission representation (done: Permission IntFlag bitmask in common/data/permission/types.py)
- association_scopes_entities schema change (permission_cap column) + migration/backfill
- Migrate permission resolution to 1-hop + cap-based: query direct scopes only, clip each edge-derived permission by that edge's cap (bitwise AND), leave direct entity-scope grants (Layer 1) un-clipped, and remove the recursive scope walk
- Consolidate scope_entities/role_permission repository access into repositories/ops
## Out of Scope
- Write-path rejection of over-cap role_permission assignment — \***dropped as outdated**\*. Enforcement is resolution-time clipping, not assignment rejection. Over-cap assignment is allowed; it simply has no effect beyond the cap when resolved through the edge.
- GRANT_\* (grant:\*) operations — intentionally dropped as legacy; the Permission bitmask has no grant bits.
- object_permissions table removal — tracked by BA-4360 (under BA-4622, currently Not Planned; needs reopening). Resolution code still reads ObjectPermissionRow, so it must be cleaned up before/with the operation column removal.
- Role preset / role_permission_preset (separate epic: BA-6173)
- Auto-grant/revoke of default role on scope creation (separate epic)
- General repository-wide ops consolidation beyond RBAC (separate track)
## Design Decisions
- permission representation: Permission IntFlag bitmask (READ/UPDATE/CREATE/SOFT_DELETE/HARD_DELETE), shared by grants (permissions.permission) and caps (scope_entities.permission_cap).
- Backfill rule (as shipped in BA-6283): REF -> read-only cap (READ bit), AUTO -> NULL (NULL = no ceiling). relation_type is kept for now; decide its removal once resolution no longer reads it (candidate: BA-4622).
- Resolution is 1-hop over direct scope associations; the recursive scope walk is removed (see Resolution Model above).
## Definition of Done
- scope_entities has a permission_cap column and existing AUTO/REF data is backfilled without loss (done: BA-6283)
- Permission resolution queries direct scopes only (1-hop) and clips each edge-derived permission by that edge's cap (bitwise AND); a permission resolved through a REF/low-cap edge cannot exceed the cap. Covered by tests.
- A permission resolved via an un-capped edge, or granted directly at the entity scope (sharing/invitation, scope == entity), is not clipped by any other edge's cap. Covered by tests.
- Permission resolution operates on the Permission bitmask column with regression parity to current decisions for AUTO-edge setups. (REF edges contributing capped read-only access via the invitee's own scopes is a deliberate behavior change, covered by new-behavior tests.)
- Target repository access is unified through repositories/ops
## Related
- BA-6173 RBAC Role Preset management (Epic 2)
- BA-4622 Clean up RBAC legacy code / BA-4360 Remove object_permissions table and business logic
JIRA Issue: BA-6194
Contributor guide
Assessment
This issue has not been assessed yet.