diff --git a/specs/013-rbac-authorization-model/tasks.md b/specs/013-rbac-authorization-model/tasks.md index cb9a185ad..e2adad7f4 100644 --- a/specs/013-rbac-authorization-model/tasks.md +++ b/specs/013-rbac-authorization-model/tasks.md @@ -12,6 +12,13 @@ ## Format: `[ID] [P?] [Story] Description` +> **Reconciled 2026-08-27.** The checkboxes below were stale: the implementation had landed across +> many sessions without the list being updated. Every task was re-verified against the code on this +> date — 52 were confirmed complete and ticked; the 13 that remain open carry an inline +> note naming the missing evidence. Verified by inspection of the working tree at `origin/main`, not +> by a full build. + + - **[P]**: Can run in parallel — touches different files and has no dependency on another incomplete task in the same phase. - **[Story]**: Maps to a user story in `spec.md` (US1–US9). @@ -21,15 +28,15 @@ **Purpose**: The permission model and the single decision point. Entirely additive — nothing changes behavior, and no endpoint is touched. -- [ ] T001 [P] [US1] Define `Permission` as a `(Resource, Verb)` pair with canonical parse and format in `src/common/Elsa.Api.Common/Authorization/Permission.cs`. Reject strings containing a comma, per the persistence constraint. -- [ ] T002 [P] [US1] Declare the recommended core verbs — `view`, `create`, `update`, `write`, `delete`, `execute` — as constants in `src/common/Elsa.Api.Common/Authorization/CoreVerbs.cs`. -- [ ] T003 [US1] Implement `PermissionMatcher` in `src/common/Elsa.Api.Common/Authorization/PermissionMatcher.cs`: exact match on both axes; a trailing `*` on the resource axis matching the named node **and all descendants at any depth**; `*` on the verb axis matching any verb. -- [ ] T004 [US1] Define `IPermissionEvaluator` and its implementation in `src/common/Elsa.Api.Common/Authorization/`, resolving a principal's grants from the `permissions` claim and evaluating them through `PermissionMatcher`. -- [ ] T005 [US2] Add `PermissionRequirement` and `PermissionAuthorizationHandler` in `src/common/Elsa.Api.Common/Authorization/`, replacing the FastEndpoints exact-match permission check as the enforcement path. -- [ ] T006 [US3] Promote `PermissionDescriptor`, `IPermissionDescriptorProvider`, `IPermissionDescriptorRegistry` and `DefaultPermissionDescriptorRegistry` from `Elsa.ExternalAuthentication` into `src/common/Elsa.Api.Common/Permissions/`, leaving type-forwarding shims so External Authentication keeps compiling. -- [ ] T007 [US3] Extend `PermissionDescriptor` with the verbs a resource supports, so the catalog can drive a role editor and validate submitted grants. -- [ ] T008 [P] [US1] Unit-test the matcher table in `test/unit/Elsa.Api.Common.UnitTests/Authorization/PermissionMatcherTests.cs`: exact match on each axis; subtree wildcard covering the node itself and descendants; verb wildcard; `*:*`; absence denying; a wildcard covering a newly registered resource and a newly registered verb. -- [ ] T009 [P] [US1] Unit-test `IPermissionEvaluator` for union-across-roles semantics and for the absence of verb implication (FR-009). +- [x] T001 [P] [US1] Define `Permission` as a `(Resource, Verb)` pair with canonical parse and format in `src/common/Elsa.Api.Common/Authorization/Permission.cs`. Reject strings containing a comma, per the persistence constraint. **Verified done 2026-08-27.** +- [x] T002 [P] [US1] Declare the recommended core verbs — `view`, `create`, `update`, `write`, `delete`, `execute` — as constants in `src/common/Elsa.Api.Common/Authorization/CoreVerbs.cs`. **Verified done 2026-08-27.** +- [x] T003 [US1] Implement `PermissionMatcher` in `src/common/Elsa.Api.Common/Authorization/PermissionMatcher.cs`: exact match on both axes; a trailing `*` on the resource axis matching the named node **and all descendants at any depth**; `*` on the verb axis matching any verb. **Verified done 2026-08-27.** +- [x] T004 [US1] Define `IPermissionEvaluator` and its implementation in `src/common/Elsa.Api.Common/Authorization/`, resolving a principal's grants from the `permissions` claim and evaluating them through `PermissionMatcher`. **Verified done 2026-08-27.** +- [x] T005 [US2] Add `PermissionRequirement` and `PermissionAuthorizationHandler` in `src/common/Elsa.Api.Common/Authorization/`, replacing the FastEndpoints exact-match permission check as the enforcement path. **Verified done 2026-08-27.** +- [x] T006 [US3] Promote `PermissionDescriptor`, `IPermissionDescriptorProvider`, `IPermissionDescriptorRegistry` and `DefaultPermissionDescriptorRegistry` from `Elsa.ExternalAuthentication` into `src/common/Elsa.Api.Common/Permissions/`, leaving type-forwarding shims so External Authentication keeps compiling. **Verified done 2026-08-27.** +- [x] T007 [US3] Extend `PermissionDescriptor` with the verbs a resource supports, so the catalog can drive a role editor and validate submitted grants. **Verified done 2026-08-27.** +- [x] T008 [P] [US1] Unit-test the matcher table in `test/unit/Elsa.Api.Common.UnitTests/Authorization/PermissionMatcherTests.cs`: exact match on each axis; subtree wildcard covering the node itself and descendants; verb wildcard; `*:*`; absence denying; a wildcard covering a newly registered resource and a newly registered verb. **Verified done 2026-08-27.** +- [x] T009 [P] [US1] Unit-test `IPermissionEvaluator` for union-across-roles semantics and for the absence of verb implication (FR-009). **Verified done 2026-08-27.** --- @@ -39,24 +46,24 @@ Each module task creates `Permissions/Permissions.cs` following the pattern already proven in `src/modules/Elsa.ExternalAuthentication/Permissions/ExternalAuthenticationPermissions.cs` — constants and descriptors colocated — refined to **one constant per resource**, with verbs supplied separately. Resources and verbs come from `contracts/permissions.md`. -- [ ] T010 [P] [US3] `Elsa.Workflows.Api` — 20 resources spanning `workflows/definitions`, `.../versions`, `.../labels`, `workflows/instances`, `activity-executions`, `runtime`, `bookmark-queue/dead-letters`, `events`, `tasks`, `tests`, the nine `descriptors/*`, and `scripting/javascript`. -- [ ] T011 [P] [US3] `Elsa.Identity` — `identity/users`, `identity/roles`, `identity/applications`. -- [ ] T012 [P] [US3] `Elsa.Secrets` — `secrets`. Retire the unused `use:`, `import:` and `export:` constants. -- [ ] T013 [P] [US3] `Elsa.ExternalAuthentication` — `connections`, `descriptors`, `identity-links`, `sessions`, `policies`, `policies/default-roles`, `provider-trust`, `permission-grants`. Correct the `roles:assign` descriptor text to describe what it actually guards. -- [ ] T014 [P] [US3] `Elsa.Labels` — `labels`. -- [ ] T015 [P] [US3] `Elsa.Tenants` — `tenants`. -- [ ] T016 [P] [US3] `Elsa.Alterations` — `alterations`. -- [ ] T017 [P] [US3] `Elsa.Dashboard.Api` — `dashboard`. -- [ ] T018 [P] [US3] `Elsa.Resilience` — `resilience/retries`, `resilience/strategies`, `resilience/simulation`. -- [ ] T019 [P] [US3] `Elsa.Diagnostics.ConsoleLogs`, `Elsa.Diagnostics.StructuredLogs`, `Elsa.Diagnostics.OpenTelemetry` — the three `diagnostics/*` resources. Retire the unused `ingest:` constant. -- [ ] T020 [P] [US3] `Elsa.AI.Host` — `ai/chat`, `ai/tools`, `ai/capabilities`. Retire the unused `ai:proposals:*` and `ai:tools:manage` constants. -- [ ] T021 [P] [US3] `Elsa.Shells.Api` and `Elsa.Workflows.Api` platform resources — `system/shells`, `system/features`. -- [ ] T022 [US3] Reduce `src/common/Elsa.Api.Common/PermissionNames.cs` to the claim type and the whole-vocabulary grant; move the workflow-runtime and bookmark-queue constants to the Workflows.Api declarations. Remove the dead `AdminRoleName`/`ReaderRoleName`/`WriteRoleName` fields from `EndpointSecurityOptions.cs`. -- [ ] T022a [US3] Wire descriptor validation into the role write paths (`Endpoints/Roles/Create`, `Endpoints/Roles/Update`), which today persist `request.Permissions` after only the caller-subset check. Reject a concrete resource with no registered descriptor and a concrete verb outside that resource's supported verbs; accept structurally valid wildcard segments, including ones that currently match nothing. Covers FR-012a. -- [ ] T022b [P] [US3] Unit-test role-authoring validation: unknown concrete resource rejected, unsupported concrete verb rejected, `workflows/*:view` accepted, `*:*` accepted, and a wildcard matching no installed module accepted. -- [ ] T023 [US3] Implement `GET /identity/permissions` in `src/modules/Elsa.Identity/Endpoints/Permissions/List/Endpoint.cs`, returning core verbs, every registered resource with supported verbs, category and display metadata, and a `nonCoreVerbs` marker. Contract: `contracts/rest-api.md`. Requires `identity/roles:view`. -- [ ] T024 [US1] Implement `GET /identity/permissions/reach` in `src/modules/Elsa.Identity/Endpoints/Permissions/Reach/Endpoint.cs`, reporting the resources a wildcard grant currently covers. This is the mitigation for forward reach on the resource axis. -- [ ] T025 [P] [US3] Integration-test the catalog endpoint for descriptor consistency only: every registered resource exposes display metadata, a category and a non-empty supported-verb list, verbs outside the core set are marked, and no two modules register the same resource. **Endpoint-to-descriptor resolution is deliberately not asserted here** — in Phase 2 endpoints still declare legacy strings, so that assertion belongs to the cutover gate T041. +- [x] T010 [P] [US3] `Elsa.Workflows.Api` — 20 resources spanning `workflows/definitions`, `.../versions`, `.../labels`, `workflows/instances`, `activity-executions`, `runtime`, `bookmark-queue/dead-letters`, `events`, `tasks`, `tests`, the nine `descriptors/*`, and `scripting/javascript`. **Verified done 2026-08-27.** +- [x] T011 [P] [US3] `Elsa.Identity` — `identity/users`, `identity/roles`, `identity/applications`. **Verified done 2026-08-27.** +- [x] T012 [P] [US3] `Elsa.Secrets` — `secrets`. Retire the unused `use:`, `import:` and `export:` constants. **Verified done 2026-08-27.** +- [x] T013 [P] [US3] `Elsa.ExternalAuthentication` — `connections`, `descriptors`, `identity-links`, `sessions`, `policies`, `policies/default-roles`, `provider-trust`, `permission-grants`. Correct the `roles:assign` descriptor text to describe what it actually guards. **Verified done 2026-08-27.** +- [x] T014 [P] [US3] `Elsa.Labels` — `labels`. **Verified done 2026-08-27.** +- [x] T015 [P] [US3] `Elsa.Tenants` — `tenants`. **Verified done 2026-08-27.** +- [x] T016 [P] [US3] `Elsa.Alterations` — `alterations`. **Verified done 2026-08-27.** +- [x] T017 [P] [US3] `Elsa.Dashboard.Api` — `dashboard`. **Verified done 2026-08-27.** +- [x] T018 [P] [US3] `Elsa.Resilience` — `resilience/retries`, `resilience/strategies`, `resilience/simulation`. **Verified done 2026-08-27.** +- [x] T019 [P] [US3] `Elsa.Diagnostics.ConsoleLogs`, `Elsa.Diagnostics.StructuredLogs`, `Elsa.Diagnostics.OpenTelemetry` — the three `diagnostics/*` resources. Retire the unused `ingest:` constant. **Verified done 2026-08-27.** +- [x] T020 [P] [US3] `Elsa.AI.Host` — `ai/chat`, `ai/tools`, `ai/capabilities`. Retire the unused `ai:proposals:*` and `ai:tools:manage` constants. **Verified done 2026-08-27.** +- [x] T021 [P] [US3] `Elsa.Shells.Api` and `Elsa.Workflows.Api` platform resources — `system/shells`, `system/features`. **Verified done 2026-08-27.** +- [x] T022 [US3] Reduce `src/common/Elsa.Api.Common/PermissionNames.cs` to the claim type and the whole-vocabulary grant; move the workflow-runtime and bookmark-queue constants to the Workflows.Api declarations. Remove the dead `AdminRoleName`/`ReaderRoleName`/`WriteRoleName` fields from `EndpointSecurityOptions.cs`. **Verified done 2026-08-27.** +- [x] T022a [US3] Wire descriptor validation into the role write paths (`Endpoints/Roles/Create`, `Endpoints/Roles/Update`), which today persist `request.Permissions` after only the caller-subset check. Reject a concrete resource with no registered descriptor and a concrete verb outside that resource's supported verbs; accept structurally valid wildcard segments, including ones that currently match nothing. Covers FR-012a. **Verified done 2026-08-27.** +- [ ] T022b [P] [US3] Unit-test role-authoring validation: unknown concrete resource rejected, unsupported concrete verb rejected, `workflows/*:view` accepted, `*:*` accepted, and a wildcard matching no installed module accepted. **Still open (verified 2026-08-27)** — no role-authoring validation tests found in `test/unit/Elsa.Identity.UnitTests`. +- [x] T023 [US3] Implement `GET /identity/permissions` in `src/modules/Elsa.Identity/Endpoints/Permissions/List/Endpoint.cs`, returning core verbs, every registered resource with supported verbs, category and display metadata, and a `nonCoreVerbs` marker. Contract: `contracts/rest-api.md`. Requires `identity/roles:view`. **Verified done 2026-08-27.** +- [x] T024 [US1] Implement `GET /identity/permissions/reach` in `src/modules/Elsa.Identity/Endpoints/Permissions/Reach/Endpoint.cs`, reporting the resources a wildcard grant currently covers. This is the mitigation for forward reach on the resource axis. **Verified done 2026-08-27.** +- [ ] T025 [P] [US3] Integration-test the catalog endpoint for descriptor consistency only: every registered resource exposes display metadata, a category and a non-empty supported-verb list, verbs outside the core set are marked, and no two modules register the same resource. **Endpoint-to-descriptor resolution is deliberately not asserted here** — in Phase 2 endpoints still declare legacy strings, so that assertion belongs to the cutover gate T041. **Still open (verified 2026-08-27)** — no catalog descriptor-consistency integration test found. --- @@ -66,43 +73,43 @@ Each module task creates `Permissions/Permissions.cs` following the patt No migration scaffold is required — the obsolete declaration path (T027) translates legacy *endpoint declarations*, while a legacy *stored grant* still fails to match, and the seeded admin `*` satisfies every endpoint throughout. See `research.md` D20. -- [ ] T026a [US2] Add an explicit authenticated-only declaration to the base classes in `src/common/Elsa.Api.Common/Abstractions/Endpoints.cs`, so FR-019's third state is expressible and the coverage gate can distinguish a deliberate choice from an omission. -- [ ] T026 [US8] Add `RequirePermission(string resource, string verb)` to the six base classes in `src/common/Elsa.Api.Common/Abstractions/Endpoints.cs`, collapsing the six copy-pasted `ConfigurePermissions` bodies into one shared implementation. -- [ ] T027 [US8] Keep `ConfigurePermissions(params string[])` as `[Obsolete]` but functional: resolve legacy strings through the migration table; register an implicit descriptor marked unverified and log a warning for anything unresolvable, rather than failing the host at boot. Follows the existing `unknown_permission_descriptor` precedent in `DefaultPermissionGrantResolver`. -- [ ] T028 [US1] Migrate `Elsa.Workflows.Api` — `WorkflowDefinitions` (31 files) to `RequirePermission`. -- [ ] T028a [P] [US1] Migrate `Elsa.Workflows.Api` — `WorkflowInstances`, `ActivityExecutions`, `ActivityExecutionSummaries` (20 files). -- [ ] T028b [P] [US1] Migrate `Elsa.Workflows.Api` — `RuntimeAdmin`, `BookmarkQueueDeadLetters`, `Bookmarks`, `Events`, `Tasks`, `Tests`, `Features` (15 files). -- [ ] T028c [P] [US1] Migrate `Elsa.Workflows.Api` — the nine descriptor folders and `Scripting` (12 files). -- [ ] T029 [P] [US1] Migrate `Elsa.ExternalAuthentication` endpoints (13 files, 34 endpoint classes — several classes per file). -- [ ] T030 [P] [US1] Migrate `Elsa.Identity` endpoints (11 files). -- [ ] T031 [P] [US1] Migrate `Elsa.Secrets` endpoints (10 files). -- [ ] T032 [P] [US1] Migrate `Elsa.Labels` (7) and `Elsa.Tenants` (6) endpoints. -- [ ] T033 [P] [US1] Migrate `Elsa.Diagnostics.OpenTelemetry` (7), `Elsa.Diagnostics.StructuredLogs` (3) and `Elsa.Diagnostics.ConsoleLogs` (2) endpoints. -- [ ] T034 [P] [US1] Migrate `Elsa.Dashboard.Api` (5) and `Elsa.Alterations` (5) endpoints. -- [ ] T035 [P] [US1] Migrate `Elsa.Resilience` (3), `Elsa.Bpmn.Interchange` (3) and `Elsa.AI.Host` (3) endpoints. -- [ ] T036 [P] [US1] Migrate `Elsa.Shells.Api` (2), `Elsa.Http.Webhooks` (1) and `Elsa.Expressions.JavaScript` (1) endpoints. -- [ ] T037 [US2] Declare the two endpoints in `src/modules/Elsa.ExternalAuthentication/Endpoints/Broker/Logout.cs` separately: `Logout` as authenticated-only (it reads the external session claim from the principal), `ContinueLogout` as `AllowAnonymous` (the route handle carries the authority, matching every other broker callback). `ContinueLogout` inheriting the authenticated default today is a probable live bug — the identity provider redirects the browser there during upstream logout, potentially after the Elsa session is gone. Fix tracked as #7976. -- [ ] T038 [US1] Replace the hand-rolled `PermissionNames.ClaimType` claim inspections with `IPermissionEvaluator` calls across `Elsa.ExternalAuthentication` (5 files), `Elsa.Workflows.Api` (2), `Elsa.Identity` (2), `Elsa.Api.Common` (1) and `Elsa.AI.Host` (1). Note `AIHttpContextIdentity` currently compares case-insensitively while every other site is ordinal; the evaluator standardises this. -- [ ] T038a [P] [US1] Unit-test `RoleAuthorizationService` under wildcard containment, extending the existing tests: a held `workflows/*:view` may grant `workflows/definitions:view`; a held `workflows/definitions:view` may **not** grant `workflows/*:view`; a held concrete grant may not grant an unsupported verb; `*:*` may grant anything. -- [ ] T039 [US1] Route the four SignalR hub permission checks through `IPermissionEvaluator` — `WorkflowInstanceHub`, `ElsaConsoleLogStreamHubAuthorizer`, `StructuredLogsHub`, `OpenTelemetryHub` — replacing hard-coded arrays such as `["*", "read:*", "read:workflow-instances"]`. -- [ ] T040 [US1] Replace the three `Policies(IdentityPolicyNames.SecurityRoot)` usages in `Elsa.Identity` (`Secrets/Hash`, `Roles/Create`, `Applications/Create`) and remove the obsolete policy. **Scope note**: per FR-017, the 15 mid-handler `AuthorizeAsync(..., NotReadOnlyPolicy)` calls in Workflows.Api are *not* in scope — read-only mode is a separate axis from permissions and keeps its own check. -- [ ] T041 [US2] Add the fail-closed coverage gate in `test/unit/Elsa.Api.Common.UnitTests/Authorization/EndpointCoverageTests.cs`: enumerate every in-repository `ElsaEndpoint*` type by reflection and assert each declares exactly one of a permission resolving to a registered descriptor, `AllowAnonymous`, or authenticated-only. No exemption list. -- [ ] T042 [US5] Update `DefaultAccessTokenIssuer` and `DefaultElsaTokenService` to emit new-format `{resource}:{verb}` claims, and update `DefaultApiKeyProvider`, `AdminApiKeyProvider`, `LocalHostPermissionRequirement` and `DefaultExternalAuthenticationTokenIssuer` to match. -- [ ] T043 [US5] Add a startup validator that scans stored roles and logs every permission that does not resolve, identified by role name. Fails closed and loudly; the seeded `*` grant is unaffected so an instance cannot be locked out. -- [ ] T044 [P] [US5] Regression-test that legacy stored grants no longer authorize, that `*` still authorizes everything, and that a legacy *endpoint declaration* still resolves through T027. +- [x] T026a [US2] Add an explicit authenticated-only declaration to the base classes in `src/common/Elsa.Api.Common/Abstractions/Endpoints.cs`, so FR-019's third state is expressible and the coverage gate can distinguish a deliberate choice from an omission. **Verified done 2026-08-27.** +- [x] T026 [US8] Add `RequirePermission(string resource, string verb)` to the six base classes in `src/common/Elsa.Api.Common/Abstractions/Endpoints.cs`, collapsing the six copy-pasted `ConfigurePermissions` bodies into one shared implementation. **Verified done 2026-08-27.** +- [ ] T027 [US8] Keep `ConfigurePermissions(params string[])` as `[Obsolete]` but functional: resolve legacy strings through the migration table; register an implicit descriptor marked unverified and log a warning for anything unresolvable, rather than failing the host at boot. Follows the existing `unknown_permission_descriptor` precedent in `DefaultPermissionGrantResolver`. **Still open (verified 2026-08-27)** — `ConfigurePermissions` is preserved and functional, but is **not** marked `[Obsolete]` and does not resolve legacy strings through a migration table, register an unverified descriptor, or log a warning — it simply prepends `PermissionNames.All`. +- [x] T028 [US1] Migrate `Elsa.Workflows.Api` — `WorkflowDefinitions` (31 files) to `RequirePermission`. **Verified done 2026-08-27.** +- [x] T028a [P] [US1] Migrate `Elsa.Workflows.Api` — `WorkflowInstances`, `ActivityExecutions`, `ActivityExecutionSummaries` (20 files). **Verified done 2026-08-27.** +- [x] T028b [P] [US1] Migrate `Elsa.Workflows.Api` — `RuntimeAdmin`, `BookmarkQueueDeadLetters`, `Bookmarks`, `Events`, `Tasks`, `Tests`, `Features` (15 files). **Verified done 2026-08-27.** +- [x] T028c [P] [US1] Migrate `Elsa.Workflows.Api` — the nine descriptor folders and `Scripting` (12 files). **Verified done 2026-08-27.** +- [x] T029 [P] [US1] Migrate `Elsa.ExternalAuthentication` endpoints (13 files, 34 endpoint classes — several classes per file). **Verified done 2026-08-27.** +- [x] T030 [P] [US1] Migrate `Elsa.Identity` endpoints (11 files). **Verified done 2026-08-27.** +- [x] T031 [P] [US1] Migrate `Elsa.Secrets` endpoints (10 files). **Verified done 2026-08-27.** +- [x] T032 [P] [US1] Migrate `Elsa.Labels` (7) and `Elsa.Tenants` (6) endpoints. **Verified done 2026-08-27.** +- [x] T033 [P] [US1] Migrate `Elsa.Diagnostics.OpenTelemetry` (7), `Elsa.Diagnostics.StructuredLogs` (3) and `Elsa.Diagnostics.ConsoleLogs` (2) endpoints. **Verified done 2026-08-27.** +- [x] T034 [P] [US1] Migrate `Elsa.Dashboard.Api` (5) and `Elsa.Alterations` (5) endpoints. **Verified done 2026-08-27.** +- [x] T035 [P] [US1] Migrate `Elsa.Resilience` (3), `Elsa.Bpmn.Interchange` (3) and `Elsa.AI.Host` (3) endpoints. **Verified done 2026-08-27.** +- [x] T036 [P] [US1] Migrate `Elsa.Shells.Api` (2), `Elsa.Http.Webhooks` (1) and `Elsa.Expressions.JavaScript` (1) endpoints. **Verified done 2026-08-27.** +- [x] T037 [US2] Declare the two endpoints in `src/modules/Elsa.ExternalAuthentication/Endpoints/Broker/Logout.cs` separately: `Logout` as authenticated-only (it reads the external session claim from the principal), `ContinueLogout` as `AllowAnonymous` (the route handle carries the authority, matching every other broker callback). `ContinueLogout` inheriting the authenticated default today is a probable live bug — the identity provider redirects the browser there during upstream logout, potentially after the Elsa session is gone. Fix tracked as #7976. **Verified done 2026-08-27.** +- [ ] T038 [US1] Replace the hand-rolled `PermissionNames.ClaimType` claim inspections with `IPermissionEvaluator` calls across `Elsa.ExternalAuthentication` (5 files), `Elsa.Workflows.Api` (2), `Elsa.Identity` (2), `Elsa.Api.Common` (1) and `Elsa.AI.Host` (1). Note `AIHttpContextIdentity` currently compares case-insensitively while every other site is ordinal; the evaluator standardises this. **Still open (verified 2026-08-27)** — two hand-rolled claim inspections remain: `Elsa.AI.Host/Endpoints/AI/AIHttpContextIdentity.cs` (still case-insensitive) and `Elsa.Identity/Services/RoleDeletionCoordinator.cs`. +- [x] T038a [P] [US1] Unit-test `RoleAuthorizationService` under wildcard containment, extending the existing tests: a held `workflows/*:view` may grant `workflows/definitions:view`; a held `workflows/definitions:view` may **not** grant `workflows/*:view`; a held concrete grant may not grant an unsupported verb; `*:*` may grant anything. **Verified done 2026-08-27.** +- [x] T039 [US1] Route the four SignalR hub permission checks through `IPermissionEvaluator` — `WorkflowInstanceHub`, `ElsaConsoleLogStreamHubAuthorizer`, `StructuredLogsHub`, `OpenTelemetryHub` — replacing hard-coded arrays such as `["*", "read:*", "read:workflow-instances"]`. **Verified done 2026-08-27.** +- [ ] T040 [US1] Replace the three `Policies(IdentityPolicyNames.SecurityRoot)` usages in `Elsa.Identity` (`Secrets/Hash`, `Roles/Create`, `Applications/Create`) and remove the obsolete policy. **Scope note**: per FR-017, the 15 mid-handler `AuthorizeAsync(..., NotReadOnlyPolicy)` calls in Workflows.Api are *not* in scope — read-only mode is a separate axis from permissions and keeps its own check. **Still open (verified 2026-08-27)** — the three `Policies(IdentityPolicyNames.SecurityRoot)` usages remain in `Secrets/Hash`, `Roles/Create` and `Applications/Create`; the constant is marked `[Obsolete]` but is still consumed. +- [x] T041 [US2] Add the fail-closed coverage gate in `test/unit/Elsa.Api.Common.UnitTests/Authorization/EndpointCoverageTests.cs`: enumerate every in-repository `ElsaEndpoint*` type by reflection and assert each declares exactly one of a permission resolving to a registered descriptor, `AllowAnonymous`, or authenticated-only. No exemption list. **Verified done 2026-08-27.** Note: landed as the shared `EndpointCoverage.AssertEveryEndpointDeclaresAccess(assembly)` helper invoked per module (e.g. `test/unit/Elsa.Identity.UnitTests/Authorization/EndpointCoverageTests.cs`) rather than a single test in `Elsa.Api.Common.UnitTests`. +- [ ] T042 [US5] Update `DefaultAccessTokenIssuer` and `DefaultElsaTokenService` to emit new-format `{resource}:{verb}` claims, and update `DefaultApiKeyProvider`, `AdminApiKeyProvider`, `LocalHostPermissionRequirement` and `DefaultExternalAuthenticationTokenIssuer` to match. **Partially done (verified 2026-08-27)** — the issuers emit role-derived permissions, but `LocalHostPermissionRequirement` still carries the legacy `BootstrapPermissions` list (`create:application`, `create:user`, `create:role`). Those satisfy none of the three endpoints they exist to unlock, all of which now declare structured permissions, so the localhost bootstrap path grants nothing. Tracked for repair. +- [x] T043 [US5] Add a startup validator that scans stored roles and logs every permission that does not resolve, identified by role name. Fails closed and loudly; the seeded `*` grant is unaffected so an instance cannot be locked out. **Verified done 2026-08-27.** +- [ ] T044 [P] [US5] Regression-test that legacy stored grants no longer authorize, that `*` still authorizes everything, and that a legacy *endpoint declaration* still resolves through T027. **Still open (verified 2026-08-27)** — no legacy-stored-grant regression test found. --- ## Phase 4: Introspection, Revocation, and Audit -- [ ] T045 [US4] Implement `GET /identity/me/permissions` in `src/modules/Elsa.Identity/Endpoints/Me/Permissions/Endpoint.cs`. Every registered resource is present, including those the caller cannot access, carrying an empty verb list. Wildcard grants are resolved to concrete verbs per covered resource. Contract: `contracts/rest-api.md`. -- [ ] T046 [US6] Lower the default `AccessTokenLifetime` in `src/modules/Elsa.Identity/Options/IdentityTokenOptions.cs` from 1 hour to 15 minutes. Refresh already rotates both tokens and re-reads roles, so no client change is required; leave `RefreshTokenLifetime` at 2 hours. -- [ ] T047 [US6] Add an optional per-principal security stamp: a monotonic value bumped on any role, grant or membership change, carried as a claim and compared against a per-node cached value under a configurable interval. Must not depend on cross-node cache invalidation, which Elsa does not have. -- [ ] T047a [US6] Ship the stamp's persistence with it: if the stamp lives on `User`, this task includes the Identity migrations for all five EF providers, so Phase 4 remains independently shippable and does not depend on T053 in Phase 5. Prefer a store that avoids an entity-schema change if one fits. -- [ ] T048 [US7] Publish typed security notifications for role create, update and delete, and for user role assignment and removal, through `INotificationSender` per ADR 0007. This feature owns no audit persistence. -- [ ] T049 [US1] Promote the deployment grant boundary (`PermissionGrantOptions.AllowedPermissions` / `DeniedPermissions`, currently in `ExternalAuthenticationOptions`) into `Elsa.Identity` so it applies to all grant paths, and make it wildcard-aware. -- [ ] T050 [P] [US4] Integration-test `/me/permissions` for a role holding partial verbs, for a wildcard grant resolving to concrete verbs, and for denied resources appearing with an empty list. -- [ ] T051 [P] [US6] Integration-test that a revoked role stops authorizing on the next token issuance, and immediately within the stamp interval when the stamp is enabled. +- [x] T045 [US4] Implement `GET /identity/me/permissions` in `src/modules/Elsa.Identity/Endpoints/Me/Permissions/Endpoint.cs`. Every registered resource is present, including those the caller cannot access, carrying an empty verb list. Wildcard grants are resolved to concrete verbs per covered resource. Contract: `contracts/rest-api.md`. **Verified done 2026-08-27.** +- [x] T046 [US6] Lower the default `AccessTokenLifetime` in `src/modules/Elsa.Identity/Options/IdentityTokenOptions.cs` from 1 hour to 15 minutes. Refresh already rotates both tokens and re-reads roles, so no client change is required; leave `RefreshTokenLifetime` at 2 hours. **Verified done 2026-08-27.** +- [x] T047 [US6] Add an optional per-principal security stamp: a monotonic value bumped on any role, grant or membership change, carried as a claim and compared against a per-node cached value under a configurable interval. Must not depend on cross-node cache invalidation, which Elsa does not have. **Verified done 2026-08-27.** +- [x] T047a [US6] Ship the stamp's persistence with it: if the stamp lives on `User`, this task includes the Identity migrations for all five EF providers, so Phase 4 remains independently shippable and does not depend on T053 in Phase 5. Prefer a store that avoids an entity-schema change if one fits. **Verified done 2026-08-27.** Note: satisfied without an entity-schema change — the stamp is computed by `PermissionStampCalculator` rather than persisted on `User`, so no Identity migrations were required. +- [x] T048 [US7] Publish typed security notifications for role create, update and delete, and for user role assignment and removal, through `INotificationSender` per ADR 0007. This feature owns no audit persistence. **Verified done 2026-08-27.** +- [ ] T049 [US1] Promote the deployment grant boundary (`PermissionGrantOptions.AllowedPermissions` / `DeniedPermissions`, currently in `ExternalAuthenticationOptions`) into `Elsa.Identity` so it applies to all grant paths, and make it wildcard-aware. **Still open (verified 2026-08-27)** — `PermissionGrantOptions` still lives in `Elsa.ExternalAuthentication`; not promoted to `Elsa.Identity`. +- [ ] T050 [P] [US4] Integration-test `/me/permissions` for a role holding partial verbs, for a wildcard grant resolving to concrete verbs, and for denied resources appearing with an empty list. **Still open (verified 2026-08-27)** — no `/me/permissions` integration test found. +- [ ] T051 [P] [US6] Integration-test that a revoked role stops authorizing on the next token issuance, and immediately within the stamp interval when the stamp is enabled. **Still open (verified 2026-08-27)** — no revocation / stamp-interval integration test found. --- @@ -110,11 +117,11 @@ No migration scaffold is required — the obsolete declaration path (T027) trans **Purpose**: Close the gaps that make "roles are per tenant" unsafe today. Independently justified as latent-defect fixes; sequenced last so the unresolved isolation-boundary question does not block delivery. -- [ ] T052 [US9] Replace the global unique indexes on `User.Name`, `Role.Name`, `Application.ClientId` and `Application.Name` with per-tenant composite indexes in `src/modules/Elsa.Persistence.EFCore/Modules/Identity/Configurations.cs`. -- [ ] T053 [US9] Generate Identity migrations for all five providers — Sqlite, SqlServer, PostgreSql, MySql, Oracle. -- [ ] T054 [US9] Make `src/modules/Elsa.Common/Services/MemoryStore.cs` tenant-aware so the default `IUserStore` and `IRoleStore` isolate, rather than isolation existing only on the Entity Framework path when `TenantsOptions.IsEnabled`. -- [ ] T055 [US9] Apply explicit tenant filters in `Endpoints/Users/List` and `Endpoints/Roles/List`, which currently pass an empty filter, and set `TenantId` explicitly in `UserManager.CreateUserAsync` rather than relying on the saving handler. -- [ ] T056 [P] [US9] Integration-test that two tenants can each hold a role named `Admin`, and that roles and users listed in one tenant are invisible in another, against both the Entity Framework and in-memory stores. +- [x] T052 [US9] Replace the global unique indexes on `User.Name`, `Role.Name`, `Application.ClientId` and `Application.Name` with per-tenant composite indexes in `src/modules/Elsa.Persistence.EFCore/Modules/Identity/Configurations.cs`. **Verified done 2026-08-27.** +- [x] T053 [US9] Generate Identity migrations for all five providers — Sqlite, SqlServer, PostgreSql, MySql, Oracle. **Verified done 2026-08-27.** +- [ ] T054 [US9] Make `src/modules/Elsa.Common/Services/MemoryStore.cs` tenant-aware so the default `IUserStore` and `IRoleStore` isolate, rather than isolation existing only on the Entity Framework path when `TenantsOptions.IsEnabled`. **Still open (verified 2026-08-27)** — `Elsa.Common/Services/MemoryStore.cs` has no tenant awareness (no `TenantId`/`ITenantAccessor` reference). +- [x] T055 [US9] Apply explicit tenant filters in `Endpoints/Users/List` and `Endpoints/Roles/List`, which currently pass an empty filter, and set `TenantId` explicitly in `UserManager.CreateUserAsync` rather than relying on the saving handler. **Verified done 2026-08-27.** +- [ ] T056 [P] [US9] Integration-test that two tenants can each hold a role named `Admin`, and that roles and users listed in one tenant are invisible in another, against both the Entity Framework and in-memory stores. **Still open (verified 2026-08-27)** — no cross-tenant identity isolation test verified for both EF and in-memory stores. --- @@ -126,7 +133,7 @@ No migration scaffold is required — the obsolete declaration path (T027) trans - [x] T060 Record the model in `doc/adr/0025-two-axis-authorization-model.md`: both axes open, wildcards as the only forward reach, no aggregates and no verb implication, and the rejection of a closed verb enumeration. **Done 2026-08-24** — `doc/adr/0025-two-axis-authorization-model.md`. - [x] T061 [P] Update `doc/wiki/identity-tenancy-security.md`, replacing the Secrets-only route table with a pointer to the catalog endpoint as the authoritative source. **Done 2026-08-24** - [x] T062 Resolve the five module-owner questions and fold the answers into the vocabulary. **Done 2026-08-23** — outcomes recorded at the end of `contracts/permissions.md` and as D25/D26 in `research.md`. Produced #7976 and #7977, and added FR-019's third declaration state (T026a). -- [ ] T063 Run `dotnet build Elsa.sln` and the full test suite, confirm T041 passes with zero exemptions beyond documented anonymous endpoints, and verify the quickstart scenario end to end. +- [ ] T063 Run `dotnet build Elsa.sln` and the full test suite, confirm T041 passes with zero exemptions beyond documented anonymous endpoints, and verify the quickstart scenario end to end. **Still open (verified 2026-08-27)** — full `dotnet build Elsa.sln` + full suite run not recorded. --- diff --git a/specs/013-user-tasks/tasks.md b/specs/013-user-tasks/tasks.md index eeed3bfc8..4f8ca2de9 100644 --- a/specs/013-user-tasks/tasks.md +++ b/specs/013-user-tasks/tasks.md @@ -1,5 +1,10 @@ # Tasks: User Tasks +> **Reconciled 2026-08-27** alongside `specs/013-rbac-authorization-model/tasks.md`. Open items were +> re-verified against the code; one had landed and is now ticked, and one (T041) is recorded as partially done. Of those still open, three are +> Studio-side and live in the `elsa-studio` repository, so they cannot be closed from this repo. + + Tasks use `[ID] [P?] [Story] Description with file path`. `[P]` tasks may run in parallel when their file ownership does not overlap. ## Phase 1: Durable specification @@ -23,7 +28,7 @@ Tasks use `[ID] [P?] [Story] Description with file path`. `[P]` tasks may run in - [x] T014 [P] [US3] Implement default claims resolver and default-deny access policy in `src/modules/Elsa.UserTasks/Services/`. - [x] T015 [US1] Implement the blocking `UserTask` activity, materialized bookmark payload, stimulus, and typed output under `src/modules/Elsa.UserTasks/Activities/`. - [x] T016 [US1] Project committed bookmarks and finalize removed bookmarks in `src/modules/Elsa.UserTasks/HostedServices/` and notification handlers. -- [ ] T017 [US5] Add the bounded startup/recurring reconciler for missing projections, stale operations, and orphan records. +- [ ] T017 [US5] Add the bounded startup/recurring reconciler for missing projections, stale operations, and orphan records. **Still open (verified 2026-08-27)** — the reconciler itself exists (`Services/DefaultUserTaskReconciler.cs`, `HostedServices/UserTaskWorkers.cs`), but no test exercises its repair logic, so it stays unchecked under this list's stated bar. See the verification-status note below. - [x] T018 [P] [US5] Add cluster-safe due scanning, idempotent overdue notification, and optional timeout operation. - [x] T019 [P] Add append-only safe audit and mediator lifecycle notification models/dispatch. - [x] T020 [P] Add domain, race, disclosure, identity, projection, reconciliation, and due tests in `test/unit/Elsa.UserTasks.UnitTests/`. @@ -35,8 +40,8 @@ Tasks use `[ID] [P?] [Story] Description with file path`. `[P]` tasks may run in - [x] T023 [P] [US2] Implement claim, release, assignment, and priority/due update endpoints. - [x] T024 [P] [US1] Implement asynchronous complete, cancel, and resolution-retry endpoints with operation idempotency. - [x] T025 [P] [US3] Implement optional participant lookup without Elsa Identity coupling. -- [ ] T026 [US2] Add metadata-free SignalR invalidation and polling-compatible lifecycle notifications. -- [ ] T027 [P] Add endpoint authorization, concealment, validation, cursor, idempotency, and conflict tests. +- [ ] T026 [US2] Add metadata-free SignalR invalidation and polling-compatible lifecycle notifications. **Still open (verified 2026-08-27)** — no SignalR/Hub type exists under `src/modules/Elsa.UserTasks`. +- [ ] T027 [P] Add endpoint authorization, concealment, validation, cursor, idempotency, and conflict tests. **Still open (verified 2026-08-27)** — partial — endpoint **authorization** is covered (`Authorization/EndpointPermissionTests.cs`, `EndpointCoverageTests.cs`, `ActorPermissionMatchingTests.cs`, added with #7999); concealment, validation, cursor, idempotency and conflict tests not found. ## Phase 4: Guest invitations @@ -56,7 +61,7 @@ Tasks use `[ID] [P?] [Story] Description with file path`. `[P]` tasks may run in - [x] T038 [P] [US5] Add MySQL provider configuration, migration, design-time factory, and shell feature. - [x] T039 [P] [US5] Add Oracle provider configuration, migration, design-time factory, and shell feature. - [x] T040 [P] [US5] Add VNext document-store repository and feature. -- [ ] T041 [US5] Add shared persistence conformance and SQLite restart/index/tenant/cursor tests. +- [ ] T041 [US5] Add shared persistence conformance and SQLite restart/index/tenant/cursor tests. **Partially done (verified 2026-08-27)** — the shared conformance half has landed, the SQLite half has not. See the verification-status note below for exactly what exists. ## Phase 6: Elsa Studio @@ -65,21 +70,21 @@ Tasks use `[ID] [P?] [Story] Description with file path`. `[P]` tasks may run in - [x] T044 [US2] Implement URL-backed Assigned to me, Available, History, All, and Needs Attention queue views. - [x] T045 [US2] Implement desktop split detail and mobile detail route with workflow deep link, protected disclosure, timeline, health, and capability actions. - [x] T046 [P] [US2] Implement claim/release/assign/update/complete/cancel/retry interactions with asynchronous and conflict refresh states. -- [ ] T047 [P] [US1] Add User Task activity editor support and optional replaceable participant lookup picker with raw/expression fallback. -- [ ] T048 [US2] Add metadata-free SignalR requery coordinator and polling fallback without disrupting focus or form input. +- [ ] T047 [P] [US1] Add User Task activity editor support and optional replaceable participant lookup picker with raw/expression fallback. **Still open (verified 2026-08-27)** — Studio work — lives in the `elsa-studio` repository, not verifiable here. +- [ ] T048 [US2] Add metadata-free SignalR requery coordinator and polling fallback without disrupting focus or form input. **Still open (verified 2026-08-27)** — Studio work — lives in the `elsa-studio` repository, not verifiable here. - [x] T049 [US4] Add replaceable anonymous guest verification and task completion page. -- [ ] T050 [P] Add Studio client/component tests for tabs, URL filters, disclosure, actions, responsive routing, realtime fallback, and accessibility. +- [ ] T050 [P] Add Studio client/component tests for tabs, URL filters, disclosure, actions, responsive routing, realtime fallback, and accessibility. **Still open (verified 2026-08-27)** — Studio work — lives in the `elsa-studio` repository, not verifiable here. ## Phase 7: Documentation and local gates - [x] T051 Add module configuration, identity integration, forms, invitations, hosting, persistence, and upgrade documentation under `doc/`. -- [ ] T052 Add runnable sample workflows and host adapter examples referenced by `specs/013-user-tasks/quickstart.md`. -- [ ] T053 Run affected Core unit/integration tests and all User Tasks persistence conformance tests. +- [ ] T052 Add runnable sample workflows and host adapter examples referenced by `specs/013-user-tasks/quickstart.md`. **Still open (verified 2026-08-27)** — no sample workflows or host adapter examples found alongside `quickstart.md`. +- [x] T053 Run affected Core unit/integration tests and all User Tasks persistence conformance tests. **Verified done 2026-08-27** — run 2026-08-27 for #7999 — UnitTests 103/103, Persistence.ConformanceTests 123 passed, Persistence.EFCore.UnitTests 4/4, Hosts.SmokeTests 6/6 (net10.0). - [x] T054 Run affected Studio tests and builds. -- [ ] T055 Build both repositories broadly across configured target frameworks without GitHub-dependent gates. -- [ ] T056 Re-run requirement-to-task-to-test traceability analysis and resolve every critical/high finding. -- [ ] T057 Run up to five local self-review passes for correctness, security, API compatibility, accessibility, and maintainability. -- [ ] T058 Confirm no unrelated files were changed and record local verification evidence. +- [ ] T055 Build both repositories broadly across configured target frameworks without GitHub-dependent gates. **Still open (verified 2026-08-27)** — cross-repository build not recorded. +- [ ] T056 Re-run requirement-to-task-to-test traceability analysis and resolve every critical/high finding. **Still open (verified 2026-08-27)** — traceability analysis not recorded. +- [ ] T057 Run up to five local self-review passes for correctness, security, API compatibility, accessibility, and maintainability. **Still open (verified 2026-08-27)** — self-review passes not recorded. +- [ ] T058 Confirm no unrelated files were changed and record local verification evidence. **Still open (verified 2026-08-27)** — not recorded. ## Dependencies @@ -109,15 +114,21 @@ worktree. The following remain open and are deliberately left unchecked: end to end. - **T027** — authorization, concealment, idempotency, and conflict behavior are covered at the service layer. There are no HTTP-level endpoint tests asserting the status-code mapping. -- **T041** — persistence coverage is EF Core/SQLite only. The shared conformance suite across - in-memory, EF, and VNext is not written. +- **T041** — *partially closed 2026-08-27.* The shared conformance half has landed: the suite runs + across in-memory, EF Core (SQLite, SQL Server, PostgreSQL, Oracle) and VNext, declared in + `ProviderConformanceSuites.cs`, with `ConformanceCoverageTests` failing the run when a provider + silently skips. Tenant and cursor behaviour are covered. Still missing are the SQLite **restart** + and **index** tests this task also names — there is no restart/reopen test anywhere in the + persistence suites, and no index-specific assertion. MySQL is additionally uncovered: it is pinned + to net8.0/net9.0 because Pomelo tops out at EF Core 9 — see `doc/migrations/secrets-tenancy.md`. - **T047** — the Studio activity editor contribution and participant-picker integration for the designer are not implemented. - **T050** — Studio tests cover the wire contract, URL state, and error mapping. Component-level tests for tabs, disclosure, responsive routing, realtime fallback, and accessibility are not written. - **T052** — no runnable sample workflows or host adapter examples yet. -- **T053, T055** — targeted Core and Studio test suites and the affected module, provider, and host - builds pass. A full solution-wide build across every configured target framework has not been run. +- **T055** — targeted Core and Studio test suites and the affected module, provider, and host builds + pass, and T053's suites were run on 2026-08-27 (see its task entry). A full solution-wide build + across every configured target framework has not been run. - **T056–T058** — final traceability, self-review, and evidence passes are outstanding. ## Local-only gate note