Merge R2-05 AdminUI authorization (arch-review round 2) [PR #428]
Finding 04/C-1 (High): explicit named-policy gating on all 38 AdminUI pages via new AdminUiPolicies (ConfigEditor + AuthenticatedRead) + anti-drift reflection guard. T15 live pass deferred (docker-dev auto-admin can't observe deny). STATUS.md conflict resolved additively. Build clean.
This commit is contained in:
@@ -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
|
||||
@@ -162,7 +161,7 @@ else
|
||||
// permitted users. The username is re-read at click time (GetCurrentUserNameAsync) so a
|
||||
// mid-session token refresh lands in the published command + audit accurately.
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -87,7 +87,7 @@ else
|
||||
@if (store.Kind is StoreKind.Trusted or StoreKind.Rejected)
|
||||
{
|
||||
<td>
|
||||
<AuthorizeView Policy="FleetAdmin">
|
||||
<AuthorizeView Policy="@AdminUiPolicies.FleetAdmin">
|
||||
<Authorized>
|
||||
@if (store.Kind == StoreKind.Rejected)
|
||||
{
|
||||
@@ -183,7 +183,7 @@ else
|
||||
// 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.
|
||||
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;
|
||||
_statusMsg = "Unauthorized — FleetAdmin required.";
|
||||
|
||||
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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
-1
@@ -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 · <span class="mono">@ClusterId</span></h4>
|
||||
|
||||
+1
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -163,7 +163,7 @@
|
||||
// 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.
|
||||
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;
|
||||
|
||||
if (!Enabled)
|
||||
|
||||
+1
-1
@@ -121,7 +121,7 @@
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
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;
|
||||
_built = Build();
|
||||
await CurrentAddressChanged.InvokeAsync(_built);
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
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;
|
||||
_built = _nodeId;
|
||||
await CurrentAddressChanged.InvokeAsync(_built);
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZB.MOM.WW.OtOpcUa.Security.Auth;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.AdminUI.ScriptAnalysis;
|
||||
|
||||
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>
|
||||
/// <returns>The same route builder, for chaining.</returns>
|
||||
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")
|
||||
.RequireAuthorization(new AuthorizeAttribute { Roles = "Administrator,Designer" });
|
||||
.RequireAuthorization(AdminUiPolicies.ConfigEditor);
|
||||
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("/hover", async (HoverRequest r, ScriptAnalysisService s) => Results.Ok(await s.Hover(r)));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Security.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// Names of the AdminUI authorization policies registered by <c>AddOtOpcUaAuth</c>.
|
||||
/// Single source of truth — pages, AuthorizeView blocks, imperative checks, and endpoint
|
||||
/// groups must reference these constants, never string literals (guarded by
|
||||
/// PageAuthorizationGuardTests). Policy → role mapping lives in ServiceCollectionExtensions.
|
||||
/// </summary>
|
||||
public static class AdminUiPolicies
|
||||
{
|
||||
/// <summary>Fleet-wide admin actions (RoleGrants page, certificate mutations). Roles: Administrator.</summary>
|
||||
public const string FleetAdmin = "FleetAdmin";
|
||||
|
||||
/// <summary>Live driver operations (reconnect/restart, alarm ack/shelve, live address browse). Roles: Operator, Administrator.</summary>
|
||||
public const string DriverOperator = "DriverOperator";
|
||||
|
||||
/// <summary>Config authoring: UNS/equipment/cluster/driver/ACL/namespace/script editors + deploy + script analysis. Roles: Administrator, Designer.</summary>
|
||||
public const string ConfigEditor = "ConfigEditor";
|
||||
|
||||
/// <summary>Read-only AdminUI access (dashboards, lists, live tails). Any authenticated user.</summary>
|
||||
public const string AuthenticatedRead = "AuthenticatedRead";
|
||||
}
|
||||
@@ -152,12 +152,24 @@ public static class ServiceCollectionExtensions
|
||||
// are the canonical control-plane roles: Operator (was DriverOperator) and
|
||||
// Administrator (was FleetAdmin). Map LDAP group → role via GroupToRole in appsettings
|
||||
// (e.g. "ot-driver-operator": "Operator").
|
||||
o.AddPolicy("DriverOperator", policy =>
|
||||
policy.RequireRole("Operator", "Administrator"));
|
||||
o.AddPolicy(AdminUiPolicies.DriverOperator, policy =>
|
||||
policy.RequireRole(DevAuthRoles.Operator, "Administrator"));
|
||||
|
||||
// FleetAdmin (policy NAME kept stable): full administrative access; gates fleet-wide pages
|
||||
// such as RoleGrants. Requires the canonical Administrator role (was FleetAdmin).
|
||||
o.AddPolicy("FleetAdmin", policy => policy.RequireRole("Administrator"));
|
||||
o.AddPolicy(AdminUiPolicies.FleetAdmin, policy => policy.RequireRole("Administrator"));
|
||||
|
||||
// ConfigEditor: may author configuration (UNS, equipment, clusters, drivers incl. live
|
||||
// ResilienceConfig, ACLs, namespaces, scripts) and trigger deploys. Same semantics the
|
||||
// Deployments/Scripts/ScriptEdit pages previously spelled as Roles="Administrator,Designer".
|
||||
o.AddPolicy(AdminUiPolicies.ConfigEditor, policy =>
|
||||
policy.RequireRole("Administrator", "Designer"));
|
||||
|
||||
// AuthenticatedRead: explicit name for "any authenticated user" so read-only pages can carry
|
||||
// a non-bare attribute and the page-authorization guard can ban bare [Authorize] outright.
|
||||
// Semantically identical to the FallbackPolicy.
|
||||
o.AddPolicy(AdminUiPolicies.AuthenticatedRead, policy =>
|
||||
policy.RequireAuthenticatedUser());
|
||||
});
|
||||
|
||||
return services;
|
||||
|
||||
Reference in New Issue
Block a user