Enforce per-resource dashboard grants (RBAC matrix's own/granted qualifier)
api/dashboards' handler previously enforced only tenant-baseline role
(RoleEditor+), so any Editor could edit/delete any dashboard in their
tenant -- the matrix's "(own/granted)" qualifier was explicitly named
as unbuilt in this handler's own doc comment. This closes that gap.
New core interface api/dashboards.PermissionStore (nil-safe, same "not
wired == no-op" shape as authz.Authorizer) resolves a per-resource
dashboard_permissions grant. canEditDashboard now requires the
identity be Admin/Owner, the dashboard's creator, or hold a grant of at
least Editor; canManageGrants is deliberately stricter (creator or
Admin/Owner only, never grant-derived access) so a user who can edit a
dashboard only because of a grant can't extend or re-grant that access
to themselves or others. Wired handlers: PUT/DELETE
/dashboards/{id}/permissions/{userId}, GET .../permissions.
Two real bugs found and fixed while wiring this up, before any of it
touched a live database:
- handleCreate/handleImport never stamped created_by from the
authenticated identity, so every dashboard was owned by "anonymous"
regardless of who made it -- the ownership check would have been
meaningless. Also fixed: ImportDashboard trusted the exported JSON's
created_by verbatim, so re-importing someone else's export would
leave the actual importer unable to edit their own copy.
- metadata/migrations/0024_create_dashboard_permissions.sql's CHECK
constraint diverged from /docs/phase-4-rbac-design.md's schema
(allowed role='admin', nullable granted_by). Reconciled via
0033_restrict_dashboard_permissions_role.sql: Admin/Owner already
have tenant-wide access so a resource-level "admin" grant is
meaningless, and every real grant now always has an attributable
granter.
enterprise/internal/rbacstore gets the storage side: raw CRUD
(dashboard_permissions.go) plus DashboardPermissions
(dashboards_adapter.go), an adapter implementing
api/dashboards.PermissionStore -- same pattern as audit.QueryAPILogger
over queryapi.AuditLogger. Wired into enterprise/cmd/enterprise-api
only; plain api/cmd/api passes nil (ownership/Admin checks still work
via the nil-permissions fallback, just without the "granted" bonus).
Verified: the full own/granted/admin/creator matrix, including the
granted-editor-cannot-manage-grants regression, passes against a fake
PermissionStore (api/dashboards/handler_test.go, all existing tests
also still pass unmodified in behavior). Real integration tests exist
in enterprise/internal/rbacstore/rbacstore_test.go (skip-gated on
RBACSTORE_TEST_POSTGRES_ADDR, same convention as every other
Postgres-backed piece this phase) but have not run against a live
database in this environment -- disclosed in threat-model.md,
phase-4-runbook.md, and enterprise/README.md alongside every other
piece carrying the same gap. Also fixed a stale path in
phase-4-runbook.md's dashboards-tenant-scoping section
(./internal/dashboards/... -> ./dashboards/..., stale since that
package moved out of api/internal/ earlier in this phase).
This commit is contained in:
@@ -166,6 +166,18 @@ present) is needed for the matrix above — "own vs. any" in the matrix is
|
||||
`created_by = current user` vs. tenant-wide Admin/Owner authority, not a
|
||||
separate grants table for those resource types.
|
||||
|
||||
**Implementation note (Phase 4 task 5, added after this design was
|
||||
signed off):** `dashboard_permissions` is now built --
|
||||
`enterprise/internal/rbacstore`'s CRUD plus a `DashboardPermissions`
|
||||
adapter implementing a new core interface, `api/dashboards.
|
||||
PermissionStore`, enforced in `api/dashboards`' handler. The applied
|
||||
migration (`metadata/migrations/0024_create_dashboard_permissions.sql`)
|
||||
diverged slightly from the schema above -- it allowed `role='admin'`
|
||||
and left `granted_by` nullable -- and was reconciled to match this
|
||||
document via `0033_restrict_dashboard_permissions_role.sql`, found
|
||||
while wiring the enforcement code up. See `enterprise/README.md` and
|
||||
`/docs/security/threat-model.md` for verification status.
|
||||
|
||||
## Enforcement shape (design only — implementation is task 5)
|
||||
|
||||
RBAC checks happen server-side, on every `/api`/`/alerting` endpoint
|
||||
|
||||
Reference in New Issue
Block a user