fix(adminui): gate every page with named policies — ConfigEditor on the mutating surface (R2-05, 04/C-1)

This commit is contained in:
Joseph Doherty
2026-07-13 09:56:08 -04:00
parent 183b72b7cb
commit b5bf4b73ff
40 changed files with 232 additions and 45 deletions
@@ -3,7 +3,7 @@
grants in favour of fleet-wide LDAP-group → role mapping (Q4 of the AdminUI rebuild plan), so
this version only shows identity + the resolved fleet roles + raw LDAP groups for
troubleshooting. *@
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.AuthenticatedRead)]
@using System.Security.Claims
<div class="d-flex justify-content-between align-items-center mb-3">
@@ -1,7 +1,7 @@
@page "/alarms-historian"
@* Live status of the local node's IAlarmHistorianSink (queue depth, drain state) via the
HistorianAdapterActor.GetStatus query landed in F11. *@
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.AuthenticatedRead)]
@rendermode RenderMode.InteractiveServer
@using Akka.Actor
@using Akka.Hosting
@@ -2,9 +2,8 @@
@* Live alarm tail via SignalR. Subscribes to /hubs/alerts and shows the most-recent
AlarmTransitionEvent entries published by ScriptedAlarmActor (Runtime/ScriptedAlarms)
and the AB CIP ALMD bridge. *@
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.AuthenticatedRead)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.AspNetCore.Authorization
@using ZB.MOM.WW.OtOpcUa.AdminUI.Hubs
@using ZB.MOM.WW.OtOpcUa.Commons.Interfaces
@using ZB.MOM.WW.OtOpcUa.Commons.Messages.Admin
@@ -1,5 +1,5 @@
@page "/certificates"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.AuthenticatedRead)]
@rendermode RenderMode.InteractiveServer
@using System.Security.Cryptography.X509Certificates
@using Microsoft.Extensions.Configuration
@@ -1,6 +1,6 @@
@page "/clusters/{ClusterId}/acls/new"
@page "/clusters/{ClusterId}/acls/{NodeAclId}"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.EntityFrameworkCore
@@ -1,5 +1,5 @@
@page "/clusters/{ClusterId}/acls"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.AuthenticatedRead)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.EntityFrameworkCore
@using ZB.MOM.WW.OtOpcUa.Configuration
@@ -1,5 +1,5 @@
@page "/clusters/{ClusterId}/audit"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.AuthenticatedRead)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.EntityFrameworkCore
@using ZB.MOM.WW.OtOpcUa.Configuration
@@ -1,5 +1,5 @@
@page "/clusters/{ClusterId}/drivers"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.AuthenticatedRead)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.EntityFrameworkCore
@using ZB.MOM.WW.OtOpcUa.Configuration
@@ -1,7 +1,7 @@
@page "/clusters/{ClusterId}/edit"
@* Edit page for an existing ServerCluster. The /clusters/new route lives in NewCluster.razor;
this page handles only the update case so the form can disable ClusterId (immutable). *@
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.EntityFrameworkCore
@@ -1,5 +1,5 @@
@page "/clusters/{ClusterId}/namespaces"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.AuthenticatedRead)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.EntityFrameworkCore
@using ZB.MOM.WW.OtOpcUa.Configuration
@@ -1,5 +1,5 @@
@page "/clusters/{ClusterId}"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.AuthenticatedRead)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.EntityFrameworkCore
@using ZB.MOM.WW.OtOpcUa.Configuration
@@ -1,5 +1,5 @@
@page "/clusters/{ClusterId}/redundancy"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.AuthenticatedRead)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.EntityFrameworkCore
@using ZB.MOM.WW.OtOpcUa.Configuration
@@ -1,5 +1,5 @@
@page "/clusters"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.AuthenticatedRead)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.EntityFrameworkCore
@using ZB.MOM.WW.OtOpcUa.Configuration
@@ -1,5 +1,5 @@
@page "/clusters/{ClusterId}/drivers/new/abcip"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.EntityFrameworkCore
@@ -1,5 +1,5 @@
@page "/clusters/{ClusterId}/drivers/new/ablegacy"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.EntityFrameworkCore
@@ -2,7 +2,7 @@
via <DynamicComponent> using _componentMap. Shows an error panel when the driver type has
no registered typed page. *@
@page "/clusters/{ClusterId}/drivers/{DriverInstanceId}"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.EntityFrameworkCore
@using ZB.MOM.WW.OtOpcUa.Configuration
@@ -1,7 +1,7 @@
@* Driver type picker — presents a card grid of registered driver types and links to the
per-type new-driver creation page (/clusters/{ClusterId}/drivers/new/{slug}). *@
@page "/clusters/{ClusterId}/drivers/new"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)]
<div class="d-flex justify-content-between align-items-center mb-3">
<h4 class="mb-0">New driver &middot; <span class="mono">@ClusterId</span></h4>
@@ -1,5 +1,5 @@
@page "/clusters/{ClusterId}/drivers/new/focas"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.EntityFrameworkCore
@@ -1,5 +1,5 @@
@page "/clusters/{ClusterId}/drivers/new/galaxy"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.EntityFrameworkCore
@@ -1,5 +1,5 @@
@page "/clusters/{ClusterId}/drivers/new/modbustcp"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.EntityFrameworkCore
@@ -1,5 +1,5 @@
@page "/clusters/{ClusterId}/drivers/new/opcuaclient"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.EntityFrameworkCore
@@ -1,5 +1,5 @@
@page "/clusters/{ClusterId}/drivers/new/s7"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.EntityFrameworkCore
@@ -1,5 +1,5 @@
@page "/clusters/{ClusterId}/drivers/new/twincat"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.EntityFrameworkCore
@@ -3,7 +3,7 @@
@* Live-edit form pattern — one page handles both create (NamespaceId is null) and update.
RowVersion is preserved across post-back so EF Core enforces last-write-wins; concurrency
conflicts surface as a toast and reload the current row. *@
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.EntityFrameworkCore
@@ -1,5 +1,5 @@
@page "/clusters/new"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.EntityFrameworkCore
@@ -2,7 +2,7 @@
@page "/clusters/{ClusterId}/nodes/{NodeId}"
@* ClusterNode CRUD. ApplicationUri is fleet-wide unique — the EF unique index enforces this
at SaveChanges. ServiceLevelBase defaults: 200 primary, 150 secondary. *@
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.EntityFrameworkCore
@@ -1,5 +1,4 @@
@page "/deployments"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.EntityFrameworkCore
@using ZB.MOM.WW.OtOpcUa.Commons.Interfaces
@@ -9,7 +8,7 @@
@using ZB.MOM.WW.OtOpcUa.Configuration.Enums
@using ZB.MOM.WW.OtOpcUa.ControlPlane.AdminOperations
@attribute [Authorize(Roles = "Administrator,Designer")]
@attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)]
@inject IDbContextFactory<OtOpcUaConfigDbContext> DbFactory
@inject IAdminOperationsClient AdminOps
@@ -3,7 +3,7 @@
progress row owned by each DriverHostActor) and projects the most-recent row per node. The
Akka cluster topology comes from IClusterRoleInfo so we can show nodes that haven't applied
anything yet alongside nodes that have. *@
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.AuthenticatedRead)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.EntityFrameworkCore
@using ZB.MOM.WW.OtOpcUa.Commons.Interfaces
@@ -1,4 +1,5 @@
@page "/"
@attribute [Authorize(Policy = AdminUiPolicies.AuthenticatedRead)]
<PageTitle>OtOpcUa</PageTitle>
@@ -4,7 +4,7 @@
cluster. The health feed (DriverHealthChanged) carries no per-Akka-member identity, so the rows
are cluster-scoped (keyed per driver instance across the cluster, not per member). The section
reads the in-process driver-health snapshot store directly + reloads its config from the ConfigDB. *@
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.AuthenticatedRead)]
@rendermode RenderMode.InteractiveServer
@using Akka.Actor
@using Akka.Cluster
@@ -1,5 +1,5 @@
@page "/reservations"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.AuthenticatedRead)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.EntityFrameworkCore
@using ZB.MOM.WW.OtOpcUa.Configuration
@@ -1,5 +1,5 @@
@page "/role-grants"
@attribute [Microsoft.AspNetCore.Authorization.Authorize(Policy = "FleetAdmin")]
@attribute [Authorize(Policy = AdminUiPolicies.FleetAdmin)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.Extensions.Options
@using ZB.MOM.WW.OtOpcUa.Configuration.Entities
@@ -2,7 +2,7 @@
@page "/scripts/{ScriptId}"
@* Script CRUD. SourceHash is computed automatically from SourceCode on save so the
integrity check in v2's deployment pipeline doesn't require operator action. *@
@attribute [Microsoft.AspNetCore.Authorization.Authorize(Roles = "Administrator,Designer")]
@attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.EntityFrameworkCore
@@ -1,7 +1,7 @@
@page "/script-log"
@* Live script-log tail via SignalR. Subscribes to /hubs/script-log and shows entries from
VirtualTagActor / ScriptedAlarmActor script execution. Engine emit lands with F8 + F9. *@
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.AuthenticatedRead)]
@rendermode RenderMode.InteractiveServer
@using ZB.MOM.WW.OtOpcUa.AdminUI.Hubs
@using ZB.MOM.WW.OtOpcUa.Commons.Messages.Logging
@@ -1,5 +1,5 @@
@page "/scripts"
@attribute [Microsoft.AspNetCore.Authorization.Authorize(Roles = "Administrator,Designer")]
@attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)]
@rendermode RenderMode.InteractiveServer
@using Microsoft.EntityFrameworkCore
@using ZB.MOM.WW.OtOpcUa.Configuration
@@ -4,7 +4,7 @@
the shell + the Details tab (lifted from EquipmentModal's EditForm) + the create→edit redirect; the
Tags / Virtual Tags / Alarms tabs render placeholders wired in later tasks. On a successful create the
page redirects to /uns/equipment/{newId} so the other tabs (disabled while new) become available. *@
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)]
@rendermode RenderMode.InteractiveServer
@using System.ComponentModel.DataAnnotations
@using Microsoft.AspNetCore.Components.Forms
@@ -1,5 +1,5 @@
@page "/uns"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@attribute [Authorize(Policy = AdminUiPolicies.ConfigEditor)]
@rendermode RenderMode.InteractiveServer
@using ZB.MOM.WW.OtOpcUa.AdminUI.Uns
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Uns
@@ -1,3 +1,4 @@
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@@ -7,4 +8,5 @@
@using Microsoft.JSInterop
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Layout
@using ZB.MOM.WW.OtOpcUa.Security.Auth
@using ZB.MOM.WW.Theme