fix(security): close auth & site-scoping gaps across 8 findings
Resolves the auth-theme batch from the 2026-05-28 baseline review (8 findings across Security/CentralUI/ManagementService/CLI). The most consequential gaps: NotificationReport + SiteCallsReport now route through SiteScopeService so a site-scoped Deployment user cannot see or act on other sites' rows (CUI-028); QueryAuditLogCommand is no longer "any authenticated user" — gated Admin-only to match /api/audit/query's strictness (MS-018); RoleMapper preserves the broader grant when a user is in both an unscoped and scoped Deployment LDAP group, instead of silently narrowing to the scoped set (Sec-016); and the dead SiteScopeRequirement/Handler are deleted so SiteScopeService is unambiguously the sole site-scoping mechanism (Sec-017). Pending findings: 172 → 164.
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
| Last reviewed | 2026-05-28 |
|
||||
| Reviewer | claude-agent |
|
||||
| Commit reviewed | `1eb6e97` |
|
||||
| Open findings | 8 |
|
||||
| Open findings | 7 |
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -1341,7 +1341,7 @@ at least one representative page so the helper's continued use is enforced.
|
||||
|--|--|
|
||||
| Severity | High |
|
||||
| Category | Security |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.CentralUI/Components/Pages/Notifications/NotificationReport.razor:2,434,472,502`; `src/ScadaLink.CentralUI/Components/Pages/SiteCalls/SiteCallsReport.razor:2,52-59`; `src/ScadaLink.CentralUI/Components/Pages/SiteCalls/SiteCallsReport.razor.cs:97-110,201,250-251,278-279` |
|
||||
|
||||
**Description**
|
||||
@@ -1378,6 +1378,29 @@ Add `Site_ScopedDeploymentUser_OnlySeesPermittedRows` and
|
||||
`Site_ScopedDeploymentUser_CannotRetryRowOnNonPermittedSite` regression tests modelled
|
||||
on `TopologyPageTests.SiteScoping_*`.
|
||||
|
||||
**Resolution**
|
||||
|
||||
Resolved 2026-05-28 (commit pending). Both pages now inject `SiteScopeService` and apply
|
||||
three layers of restriction. (1) `OnInitializedAsync` keeps an unfiltered `_allSites`
|
||||
list as the source of truth for site-identifier → Site.Id lookups, runs the dropdown
|
||||
through `SiteScope.FilterSitesAsync`, and caches `IsSystemWideAsync` + permitted-site
|
||||
ids so the row-level filter is synchronous. (2) The query response is run through a new
|
||||
`FilterPermittedAsync` helper that drops any row whose `SourceSiteId` / `SourceSite`
|
||||
resolves (via the unfiltered list) to a Site.Id outside the permitted set — a stale
|
||||
source-site identifier not present in the loaded list defaults to allowed, mirroring
|
||||
the existing tolerance for deleted-site rows. (3) `RetryNotification` /
|
||||
`DiscardNotification` / `RetrySiteCall` / `DiscardSiteCall` each re-check
|
||||
`IsRowSiteAllowedAsync` against the row's site BEFORE relaying, surfacing
|
||||
"You are not permitted to act on …" via toast on failure. Cross-module partner
|
||||
Security-017 was resolved in the same batch (the dead `SiteScopeAuthorizationHandler`
|
||||
was deleted; `SiteScopeService` is now documented as the sole site-scoping mechanism).
|
||||
Regression test `SiteCallsReportPageTests.SiteScoping_ScopedDeploymentUser_HidesOutOfScopeRows`
|
||||
seeds a Deployment user with a single `SiteId=1` claim, asserts only the Plant-A row
|
||||
renders, and verifies the Plant-B row is dropped (the page's row count drops from 2 to
|
||||
1). All three existing report-page test fixtures register `SiteScopeService` so the
|
||||
default system-wide path is unaffected — the full `ScadaLink.CentralUI.Tests` suite
|
||||
still passes (568 / 568).
|
||||
|
||||
### CentralUI-029 — `ConfigurationAuditLog` uses `JS.InvokeAsync<int>("eval", ...)` instead of a dedicated JS module
|
||||
|
||||
| | |
|
||||
|
||||
Reference in New Issue
Block a user