Skip to content

cms: dynamic per-path role access control (tenant-aware store, one resolver, UI in both Documents surfaces) #6563

Description

@delchev

Goal

An entitled user grants or revokes access for roles on a CMS path, at runtime, and it takes effect immediately. There is no UI for this today, and the mechanism behind it is a workaround.

What exists today

(no UI)  ->  api/constraints.js  ->  CMS __internal/roles-access.json
         ->  Quartz job, cron "0 * * * * ?"  (= every MINUTE)
         ->  rewrites the registry artefact documents/security/roles.access
         ->  AccessSynchronizer  ->  DIRIGIBLE_SECURITY_ACCESS
         ->  AccessVerifier (cache refreshed on a 5s modified-check)
         ->  CmisFacade.isAllowed(path, method)   [called only from cmis.ts]

Six hops; worst-case propagation ≈ one minute + a synchronization round + 5s; and a runtime job that rewrites a synchronizer-managed artefact, which inverts the platform's own line that synchronizers reconcile declared artefacts. Since nothing calls api/constraints.js any more, the loop currently maintains an empty {"constraints":[]} forever and churns the artefact for nothing.

Two further defects:

  • The ACL store is global while the content it protects is per-tenant. Access is a BaseSynchronizer over the shared DIRIGIBLE_SECURITY_ACCESS, but every CMS path is tenant-resolved by TenantPathResolver — so a rule set by one tenant governs every tenant's identically-named folder.
  • Enforcement lives in the GraalJS wrapper (separate issue).

Proposal — a CMS-only ACL: own store, own guard, own UI

The runtime ACL is user data, not an authoring artifact — which is exactly why the current design had to smuggle its state through a job. Modelling it as tenant data (the tenant-configuration shape) removes the whole contraption: no job, no artefact rewrite, no registry mirror, no scope: CMIS coupling. The generic .access stays what it is good at — static HTTP constraints authored by developers.

Concern Design
Storage new per-tenant DIRIGIBLE_CMS_ACCESS (PATH, METHOD READ|WRITE, ROLE, CREATED_BY, CREATED_AT), raw SqlFactory against the tenant-routed default datasource, create-if-absent — the DocumentNumberStore / TenantConfigurationStore pattern. A JPA entity would be wrong: JPA resolves against the default schema and is not tenant-routed
Resolution one service over a per-tenant cache invalidated on write — a grant takes effect on the next request, no polling window
Enforcement a guard consulted for CMS paths in the Java layer, so every consumer inherits it. Safe to apply platform-wide because no rules for a path = open: nothing changes until an admin writes a rule
Editing /services/documents/access (ADMINISTRATOR/OPERATOR): GET ?path= effective + inherited grants, PUT grant, DELETE revoke
UI a "Manage access" dialog on a folder — a role × {read, write} matrix with inherited rows shown greyed — in both surfaces: the AngularJS perspective (<bk-dialog> off the folder context menu) and the shared Harmonia Documents page (which covers the application / my / partner / admin shells at once)

Semantics, specified once and unit-tested: inheritance, most-specific-path-wins, roles OR, no-rules-is-open, WRITE independent of READ, __internal neither listable nor grantable. The three system-context escapes must be preserved (see the enforcement issue).

Migration: on first start per tenant, import any rows from the legacy __internal/roles-access.json (and any scope: CMIS rows in DIRIGIBLE_SECURITY_ACCESS) once, logged; then stop reading them and delete the job, constraintsProcessor and api/constraints.js.

Deliberate trade-off: without a declaration artefact a project cannot ship baseline folder ACLs. Nothing does today (the artefact is runtime-written and empty), so nothing real is lost; if it is ever wanted it returns as a .cmsaccess declaration with the .numbers semantics — insert-if-absent, never overwrite a user's row.

Tests that must exist, because these are the failure modes of what is being replaced: a grant/revoke takes effect on the next request with no sleep; a rule in tenant A does not affect tenant B; each of the three system-context escapes still succeeds under a restrictive rule; a restricted folder disappears from a listing and its children are unreachable by direct path (today's exact-path bug means it does not); DIRIGIBLE_CMS_ROLES_ENABLED=false disables enforcement wholesale.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions