From a0be76b5f04cc11c0e644c4f4a1e1f29453e09bd Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 16 Jul 2026 17:26:54 -0400 Subject: [PATCH] feat(secrets): mount /admin/secrets UI + secrets authorization (Task 5, G-6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mount the shared ZB.MOM.WW.Secrets.Ui RCL page at /admin/secrets on admin nodes: - Register secrets:manage/secrets:reveal policies additively via Configure(o => o.AddSecretsAuthorization()) in AddAdminUI (the admin-only composition layer that already references the RCL — avoids forcing an RCL dependency into the core Security lib; mirrors HistorianGateway) - Register the RCL routable assembly in BOTH the SSR endpoint (AddAdditionalAssemblies) and the interactive Router (App.razor AdditionalAssemblies) or the route 404s - Add a Secrets nav item; the page's own [Authorize(Policy=...)] gates access Claim-type MATCH: AdminRole=Administrator reads the same ClaimTypes.Role as FleetAdmin, so existing Administrators are authorized with no new role mapping. Full clean boot verified; interactive reveal deferred to the live gate (shared UI already proven). --- .../Components/App.razor | 2 +- .../Components/Layout/MainLayout.razor | 1 + .../EndpointRouteBuilderExtensions.cs | 18 +++++++++++++++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/App.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/App.razor index 7255b31d..4697fe81 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/App.razor +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/App.razor @@ -19,7 +19,7 @@ - + diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/MainLayout.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/MainLayout.razor index 50b5ecdf..29be26aa 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/MainLayout.razor +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Layout/MainLayout.razor @@ -20,6 +20,7 @@ + diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/EndpointRouteBuilderExtensions.cs b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/EndpointRouteBuilderExtensions.cs index 72a33228..8bff8987 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/EndpointRouteBuilderExtensions.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/EndpointRouteBuilderExtensions.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; @@ -10,6 +11,7 @@ using ZB.MOM.WW.OtOpcUa.Commons.Browsing; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browser; using ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser; +using ZB.MOM.WW.Secrets.Ui; namespace ZB.MOM.WW.OtOpcUa.AdminUI; @@ -28,8 +30,13 @@ public static class EndpointRouteBuilderExtensions // Razor class library static assets (_content/ZB.MOM.WW.OtOpcUa.AdminUI/**) are // served via the Host's app.UseStaticFiles() middleware which must run BEFORE // UseAuthentication() — see Program.cs. + // The /admin/secrets management page lives in the external ZB.MOM.WW.Secrets.Ui RCL, so its + // routable component must be discovered at the endpoint layer (AddAdditionalAssemblies) — + // the interactive Router's AdditionalAssemblies (App.razor) alone is not enough for SSR + // endpoint discovery. Both registrations are required or /admin/secrets 404s. app.MapRazorComponents() - .AddInteractiveServerRenderMode(); + .AddInteractiveServerRenderMode() + .AddAdditionalAssemblies(typeof(ZB.MOM.WW.Secrets.Ui.SecretsPage).Assembly); return app; } @@ -43,6 +50,15 @@ public static class EndpointRouteBuilderExtensions services.AddRazorComponents().AddInteractiveServerComponents(); services.AddOtOpcUaDriverStatusServices(); + // Secrets-management UI (ZB.MOM.WW.Secrets.Ui RCL, mounted at /admin/secrets). Register its + // "secrets:manage"/"secrets:reveal" authorization policies additively onto the AuthorizationOptions + // that AddOtOpcUaAuth (called just before AddAdminUI on admin nodes) sets up — Configure stacks, + // so this ADDS the two policies without disturbing FleetAdmin/DriverOperator/ConfigEditor. The + // policies' AdminRole = "Administrator" reads the same ClaimTypes.Role claim as FleetAdmin, so an + // existing Administrator satisfies them with no new group→role mapping. Registered here (the AdminUI + // composition layer that already references the RCL) rather than in the core Security lib. + services.Configure(o => o.AddSecretsAuthorization()); + // Browse pipeline — see docs/plans/2026-05-28-driver-browsers-design.md services.AddSingleton(); services.AddHostedService();