fix(flow-debugger): refuse method-entry Live runs for site-scoped users (#55)
Owner decision (b), 2026-11-09. An inbound API method has no site affinity, so the F2 route-time scope only bounded routed SITE traffic; the method's central-side effects (Database.*, ExternalSystem, notifications) still ran for a scoped Deployer. FlowDebugRunner now refuses FlowDebugRunMethodCommand for a site-scoped caller (site ids, not Administrator) before the script executes, audits the refusal as a failed InboundRequest under the operator's name, and raises SiteScopeViolationException -> ManagementUnauthorized, so the Central UI and the CLI (same /management command) share the gate. The page disables the "Inbound API method" entry for scoped Deployers with the reason (tooltip + inline note), starts them on script entry, and ignores ?methodId= / a stored method choice. Replay and the instance-scoped script / value-change Live runs are unchanged. The route-time scope is kept as defence in depth. Tests: scoped refused (in- and out-of-scope routing; script never runs, refusal audited), unscoped/Administrator allowed, scoped Replay allowed, scoped in-scope value-change Live still runs; bUnit for the disabled entry, the drill-in, and the unscoped control. Mutation-proven (gate and UI flag reverted -> 4 red). Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
@@ -26,6 +26,7 @@ Central cluster only. Sites have no user interface.
|
||||
|
||||
- Cookie login validates an antiforgery token before reading credentials or calling LDAP; the login form supplies the token. The JSON token endpoint retains its non-cookie client contract.
|
||||
- Manual failover goes through a scoped `AuthorizedManualFailoverService` that refreshes the circuit's Administrator grant before calling the host singleton. The component's visibility policy is an additional presentation guard.
|
||||
- Flow Debugger method-entry Live runs are unavailable to a site-scoped Deployer (Gitea #55): the *Inbound API method* entry is disabled with the reason as a tooltip and an inline note, the page starts on script entry, a `?methodId=` drill-in or a stored method choice is ignored, and the server refuses the command regardless. Script entry on a permitted instance and Replay stay available.
|
||||
- Flow Debugger retry/discard validates the current Deployer grant and resolves site scope inside its service. Scoped callers cannot relay an unknown site. Refresh merges preserve Replay's event ordering and append only rows known exclusively to the live run.
|
||||
- Health and Alarm Summary observe the whole posted refresh task and release their poll gate even when renderer dispatch fails during disposal.
|
||||
- The Topology initially loads permitted sites **and their area metadata** (Gitea #39 — every permitted site's areas are read at open, out-of-scope sites never) and opens with each site expanded to its top-level areas (`TreeView.InitiallyExpanded`; a browser's saved expansion state always wins). Instances stay lazy: select **Load instances** to load the first root-instance page of a site; each expanded area has its own loader. **Previous** and **Next** replace that branch's 50-instance page. Queries are untracked and omit member collections; templates are not loaded. Live refresh and staleness checks cover loaded pages. Search and counts explicitly describe loaded data. **Deploy all…** (Gitea #40): the Site and Area node context menus offer a subtree bulk deploy (Deployer role + current site scope re-checked at click). The page asks `DeploymentService.PreviewSubtreeDeployAsync` for the instance count and Disabled count (a cheap read, no staleness probe); a zero-instance subtree shows an info toast and no dialog; otherwise `DeployAllDialog` (via `IDialogService.ShowAsync`) shows the counts and a **Force redeploy up-to-date instances** checkbox with confirm label **Deploy all**. Confirm calls `DeploySiteAsync` (site node) or `DeployAreaAsync` (area node — the area plus all descendant areas) with the Force choice — same rules as the CLI/management bulk deploy (Disabled always skipped, up-to-date skipped unless forced, not all-or-nothing) — then toasts `N deployed, M skipped, K failed` (an error toast naming the first three failures when any failed) and reloads the tree. The menu item is disabled while another page action is in flight.
|
||||
|
||||
@@ -249,7 +249,7 @@ All four commands return `FlowDebugRunResult`: the chain root execution ID, comp
|
||||
|
||||
| Command | Behaviour | Required role |
|
||||
|---|---|---|
|
||||
| `FlowDebugRunMethodCommand` | Validates an inbound API method's parameters and runs it with tracing; routed calls enforce the caller's site scope. | Deployer |
|
||||
| `FlowDebugRunMethodCommand` | Validates an inbound API method's parameters and runs it with tracing; routed calls enforce the caller's site scope. **Refused for a site-scoped caller** (a Deployer carrying site ids, not an Administrator) with `ManagementUnauthorized` before the script executes — a method has no site, so its central-side effects cannot be scoped (Gitea #55); the refusal is audited as a failed `InboundRequest` under the operator's name. | Deployer (system-wide, or Administrator) |
|
||||
| `FlowDebugRunValueChangeCommand` | Writes an instance attribute through the real site path and polls for the triggered execution tree. | Deployer |
|
||||
| `FlowDebugRunScriptCommand` | Resolves the effective script; writes its trigger attribute for write-trigger kinds, otherwise invokes it directly with validated parameters. | Deployer |
|
||||
| `FlowDebugReplayCommand` | Reads the stored execution tree for any execution ID, enforcing caller scope and retaining the repository's chain anchor. | Any of Deployer, Administrator, Viewer |
|
||||
|
||||
Reference in New Issue
Block a user