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:
@@ -8,17 +8,15 @@
|
||||
// append-only ledger, so it has no analogous reason to restrict its own
|
||||
// write access.
|
||||
//
|
||||
// This package is the storage building block a future OIDC/SAML login
|
||||
// HTTP handler would call to resolve "which tenant/role does this SSO
|
||||
// identity map to" and issue a session (internal/session) accordingly --
|
||||
// that handler itself isn't built yet (see cmd/enterprise-auth/main.go's
|
||||
// doc comment), so today rbacstore's only production caller is
|
||||
// -mint-service-token's future tenant-aware successor and its own tests.
|
||||
// dashboard_permissions doesn't have CRUD here yet -- no caller reads
|
||||
// per-resource grants (see api/dashboards/handler.go's doc
|
||||
// comment). data_sources CRUD was added once enterprise/internal/
|
||||
// chrunner needed a real place to read per-tenant ClickHouse credentials
|
||||
// from at startup (see that package's doc comment).
|
||||
// This package is the storage building block internal/loginhandler's
|
||||
// OIDC/SAML handlers call to resolve "which tenant/role does this SSO
|
||||
// identity map to" and issue a session (internal/session) accordingly.
|
||||
// dashboard_permissions CRUD (dashboard_permissions.go) is wrapped by
|
||||
// DashboardPermissions (dashboards_adapter.go) to implement
|
||||
// api/dashboards.PermissionStore -- see that adapter's doc comment.
|
||||
// data_sources CRUD was added once enterprise/internal/chrunner needed a
|
||||
// real place to read per-tenant ClickHouse credentials from at startup
|
||||
// (see that package's doc comment).
|
||||
package rbacstore
|
||||
|
||||
import (
|
||||
|
||||
Reference in New Issue
Block a user