refactor(adminui): replace authorization string literals with AdminUiPolicies constants

This commit is contained in:
Joseph Doherty
2026-07-13 09:58:42 -04:00
parent b5bf4b73ff
commit aac3285468
7 changed files with 11 additions and 11 deletions
@@ -13,7 +13,7 @@
{ "id": "T9", "subject": "Converge Deployments/Scripts/ScriptEdit from Roles-string to ConfigEditor policy", "status": "completed", "blockedBy": ["T4", "T5"] }, { "id": "T9", "subject": "Converge Deployments/Scripts/ScriptEdit from Roles-string to ConfigEditor policy", "status": "completed", "blockedBy": ["T4", "T5"] },
{ "id": "T10", "subject": "Read pages to AuthenticatedRead (16 incl. Home insert) + RoleGrants to FleetAdmin constant", "status": "completed", "blockedBy": ["T4", "T5"] }, { "id": "T10", "subject": "Read pages to AuthenticatedRead (16 incl. Home insert) + RoleGrants to FleetAdmin constant", "status": "completed", "blockedBy": ["T4", "T5"] },
{ "id": "T11", "subject": "Guard GREEN checkpoint + full AdminUI.Tests; commit the gate", "status": "completed", "blockedBy": ["T3", "T6", "T7", "T8", "T9", "T10"] }, { "id": "T11", "subject": "Guard GREEN checkpoint + full AdminUI.Tests; commit the gate", "status": "completed", "blockedBy": ["T3", "T6", "T7", "T8", "T9", "T10"] },
{ "id": "T12", "subject": "Converge non-page literals to constants (ScriptAnalysisEndpoints, imperative DriverOperator x4, Certificates FleetAdmin x2)", "status": "pending", "blockedBy": ["T11"] }, { "id": "T12", "subject": "Converge non-page literals to constants (ScriptAnalysisEndpoints, imperative DriverOperator x4, Certificates FleetAdmin x2)", "status": "completed", "blockedBy": ["T11"] },
{ "id": "T13", "subject": "Docs: CLAUDE.md ScriptAnalysis gate sentence, docs/security.md, STATUS.md R2-05 row", "status": "pending", "blockedBy": ["T11"] }, { "id": "T13", "subject": "Docs: CLAUDE.md ScriptAnalysis gate sentence, docs/security.md, STATUS.md R2-05 row", "status": "pending", "blockedBy": ["T11"] },
{ "id": "T14", "subject": "Full-solution test sweep (dotnet test ZB.MOM.WW.OtOpcUa.slnx)", "status": "pending", "blockedBy": ["T12", "T13"] }, { "id": "T14", "subject": "Full-solution test sweep (dotnet test ZB.MOM.WW.OtOpcUa.slnx)", "status": "pending", "blockedBy": ["T12", "T13"] },
{ "id": "T15", "subject": "Live /run positive pass on docker-dev :9200 (rebuild BOTH centrals; auto-admin drives all page tiers; negative proof stays in T2/T3)", "status": "pending", "blockedBy": ["T14"] }, { "id": "T15", "subject": "Live /run positive pass on docker-dev :9200 (rebuild BOTH centrals; auto-admin drives all page tiers; negative proof stays in T2/T3)", "status": "pending", "blockedBy": ["T14"] },
@@ -161,7 +161,7 @@ else
// permitted users. The username is re-read at click time (GetCurrentUserNameAsync) so a // permitted users. The username is re-read at click time (GetCurrentUserNameAsync) so a
// mid-session token refresh lands in the published command + audit accurately. // mid-session token refresh lands in the published command + audit accurately.
var auth = await AuthState.GetAuthenticationStateAsync(); var auth = await AuthState.GetAuthenticationStateAsync();
var authResult = await AuthorizationService.AuthorizeAsync(auth.User, null, "DriverOperator"); var authResult = await AuthorizationService.AuthorizeAsync(auth.User, null, AdminUiPolicies.DriverOperator);
_canOperate = authResult.Succeeded; _canOperate = authResult.Succeeded;
} }
@@ -87,7 +87,7 @@ else
@if (store.Kind is StoreKind.Trusted or StoreKind.Rejected) @if (store.Kind is StoreKind.Trusted or StoreKind.Rejected)
{ {
<td> <td>
<AuthorizeView Policy="FleetAdmin"> <AuthorizeView Policy="@AdminUiPolicies.FleetAdmin">
<Authorized> <Authorized>
@if (store.Kind == StoreKind.Rejected) @if (store.Kind == StoreKind.Rejected)
{ {
@@ -183,7 +183,7 @@ else
// Defense-in-depth: the action buttons are FleetAdmin-gated in markup, but this handler // Defense-in-depth: the action buttons are FleetAdmin-gated in markup, but this handler
// runs on the server circuit — re-check the policy before mutating the trust store. // runs on the server circuit — re-check the policy before mutating the trust store.
var authState = await AuthState.GetAuthenticationStateAsync(); var authState = await AuthState.GetAuthenticationStateAsync();
if (!(await AuthorizationService.AuthorizeAsync(authState.User, null, "FleetAdmin")).Succeeded) if (!(await AuthorizationService.AuthorizeAsync(authState.User, null, AdminUiPolicies.FleetAdmin)).Succeeded)
{ {
_statusError = true; _statusError = true;
_statusMsg = "Unauthorized — FleetAdmin required."; _statusMsg = "Unauthorized — FleetAdmin required.";
@@ -163,7 +163,7 @@
// The username for audit logging is re-read at button-click time (not captured here) // The username for audit logging is re-read at button-click time (not captured here)
// so token-refreshes mid-session land in audit entries accurately. // so token-refreshes mid-session land in audit entries accurately.
var auth = await AuthState.GetAuthenticationStateAsync(); var auth = await AuthState.GetAuthenticationStateAsync();
var authResult = await AuthorizationService.AuthorizeAsync(auth.User, null, "DriverOperator"); var authResult = await AuthorizationService.AuthorizeAsync(auth.User, null, AdminUiPolicies.DriverOperator);
_canOperate = authResult.Succeeded; _canOperate = authResult.Succeeded;
if (!Enabled) if (!Enabled)
@@ -121,7 +121,7 @@
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
var auth = await AuthState.GetAuthenticationStateAsync(); var auth = await AuthState.GetAuthenticationStateAsync();
var authResult = await AuthorizationService.AuthorizeAsync(auth.User, null, "DriverOperator"); var authResult = await AuthorizationService.AuthorizeAsync(auth.User, null, AdminUiPolicies.DriverOperator);
_canOperate = authResult.Succeeded; _canOperate = authResult.Succeeded;
_built = Build(); _built = Build();
await CurrentAddressChanged.InvokeAsync(_built); await CurrentAddressChanged.InvokeAsync(_built);
@@ -72,7 +72,7 @@
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
var auth = await AuthState.GetAuthenticationStateAsync(); var auth = await AuthState.GetAuthenticationStateAsync();
var authResult = await AuthorizationService.AuthorizeAsync(auth.User, null, "DriverOperator"); var authResult = await AuthorizationService.AuthorizeAsync(auth.User, null, AdminUiPolicies.DriverOperator);
_canOperate = authResult.Succeeded; _canOperate = authResult.Succeeded;
_built = _nodeId; _built = _nodeId;
await CurrentAddressChanged.InvokeAsync(_built); await CurrentAddressChanged.InvokeAsync(_built);
@@ -1,21 +1,21 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using ZB.MOM.WW.OtOpcUa.Security.Auth;
namespace ZB.MOM.WW.OtOpcUa.AdminUI.ScriptAnalysis; namespace ZB.MOM.WW.OtOpcUa.AdminUI.ScriptAnalysis;
public static class ScriptAnalysisEndpoints public static class ScriptAnalysisEndpoints
{ {
/// <summary>Maps the <c>/api/script-analysis</c> endpoint group (diagnostics, completions, hover, signature help, format), gated to the Administrator/Designer roles.</summary> /// <summary>Maps the <c>/api/script-analysis</c> endpoint group (diagnostics, completions, hover, signature help, format), gated by the ConfigEditor policy (Administrator or Designer).</summary>
/// <param name="endpoints">The route builder to map the endpoints onto.</param> /// <param name="endpoints">The route builder to map the endpoints onto.</param>
/// <returns>The same route builder, for chaining.</returns> /// <returns>The same route builder, for chaining.</returns>
public static IEndpointRouteBuilder MapScriptAnalysisEndpoints(this IEndpointRouteBuilder endpoints) public static IEndpointRouteBuilder MapScriptAnalysisEndpoints(this IEndpointRouteBuilder endpoints)
{ {
// Require Administrator or Designer — matches the Script page gate and the /deployments gate. // ConfigEditor policy (Administrator or Designer) — matches the Script page gate and the /deployments gate.
var group = endpoints.MapGroup("/api/script-analysis") var group = endpoints.MapGroup("/api/script-analysis")
.RequireAuthorization(new AuthorizeAttribute { Roles = "Administrator,Designer" }); .RequireAuthorization(AdminUiPolicies.ConfigEditor);
group.MapPost("/diagnostics", (DiagnoseRequest r, ScriptAnalysisService s) => Results.Ok(s.Diagnose(r))); group.MapPost("/diagnostics", (DiagnoseRequest r, ScriptAnalysisService s) => Results.Ok(s.Diagnose(r)));
group.MapPost("/completions", async (CompletionsRequest r, ScriptAnalysisService s) => Results.Ok(await s.CompleteAsync(r))); group.MapPost("/completions", async (CompletionsRequest r, ScriptAnalysisService s) => Results.Ok(await s.CompleteAsync(r)));
group.MapPost("/hover", async (HoverRequest r, ScriptAnalysisService s) => Results.Ok(await s.Hover(r))); group.MapPost("/hover", async (HoverRequest r, ScriptAnalysisService s) => Results.Ok(await s.Hover(r)));