Resolve all high-severity architecture review findings

This commit is contained in:
Joseph Doherty
2026-09-09 16:13:58 -04:00
parent 8bfe65512e
commit 146a13238f
192 changed files with 3160 additions and 753 deletions
@@ -1,3 +1,4 @@
using ZB.MOM.WW.ScadaBridge.CentralUI.Auth;
using Microsoft.AspNetCore.Components.Authorization;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
using ZB.MOM.WW.ScadaBridge.Communication;
@@ -39,7 +40,7 @@ public sealed class BindingTester : IBindingTester
// roles, so the Designer check must happen here before any cross-cluster
// traffic. Use HasClaim against JwtTokenService.RoleClaimType (not
// IsInRole, per c1e16cf).
var state = await _auth.GetAuthenticationStateAsync();
var state = await _auth.GetAuthoritativeStateAsync();
if (!state.User.HasClaim(JwtTokenService.RoleClaimType, Roles.Designer))
{
return new ReadTagValuesResult(
@@ -1,3 +1,4 @@
using ZB.MOM.WW.ScadaBridge.CentralUI.Auth;
using Microsoft.AspNetCore.Components.Authorization;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Protocol;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
@@ -45,7 +46,7 @@ public sealed class BrowseService : IBrowseService
{
// CentralUI-side role guard — sites don't enforce envelope-level roles,
// so the Designer check must happen here before any cross-cluster traffic.
var state = await _auth.GetAuthenticationStateAsync();
var state = await _auth.GetAuthoritativeStateAsync();
if (!state.User.HasClaim(JwtTokenService.RoleClaimType, Roles.Designer))
{
return new BrowseNodeResult(
@@ -100,7 +101,7 @@ public sealed class BrowseService : IBrowseService
// Same CentralUI-side role guard as BrowseChildrenAsync — sites don't
// enforce envelope-level roles, so the Designer check must happen here
// before any cross-cluster traffic.
var state = await _auth.GetAuthenticationStateAsync();
var state = await _auth.GetAuthoritativeStateAsync();
if (!state.User.HasClaim(JwtTokenService.RoleClaimType, Roles.Designer))
{
return new SearchAddressSpaceResult(
@@ -1,3 +1,4 @@
using ZB.MOM.WW.ScadaBridge.CentralUI.Auth;
using Microsoft.AspNetCore.Components.Authorization;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
using ZB.MOM.WW.ScadaBridge.Communication;
@@ -142,7 +143,7 @@ public sealed class CertManagementService : ICertManagementService
private async Task<bool> HasRoleAsync(string role)
{
var state = await _auth.GetAuthenticationStateAsync();
var state = await _auth.GetAuthoritativeStateAsync();
return state.User.HasClaim(JwtTokenService.RoleClaimType, role);
}
}
@@ -0,0 +1,57 @@
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;
using ZB.MOM.WW.ScadaBridge.CentralUI.Auth;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
using ZB.MOM.WW.ScadaBridge.ManagementService;
using ZB.MOM.WW.ScadaBridge.Security;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Services;
/// <summary>Fresh caller authorization and audited configuration commands for page event handlers.</summary>
public sealed class ConfigurationCommandService(IServiceProvider services, AuthenticationStateProvider auth)
{
/// <summary>Runs an existing audited instance operation with fresh scope and an atomic audit commit.</summary>
public async Task<T> ExecuteInstanceAsync<T>(int? instanceId, int? createSiteId,
Func<ZB.MOM.WW.ScadaBridge.TemplateEngine.Services.InstanceService, string, Task<T>> operation)
{
var username = await auth.RequireRoleAsync(Roles.Deployer);
var sites = new SiteScopeService(auth);
try
{
return (T)(await services.GetRequiredService<IConfigurationCommandTransaction>().ExecuteAsync(async sp =>
{
var siteId = createSiteId;
if (instanceId.HasValue)
{
var instance = await sp.GetRequiredService<ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories.ITemplateEngineRepository>().GetInstanceByIdAsync(instanceId.Value);
siteId = instance?.SiteId;
}
if (!siteId.HasValue || !await sites.IsSiteAllowedAsync(siteId.Value))
throw new UnauthorizedAccessException("Instance site access was revoked.");
return await operation(sp.GetRequiredService<ZB.MOM.WW.ScadaBridge.TemplateEngine.Services.InstanceService>(), username);
}))!;
}
finally { services.GetService<IConfigurationReadSession>()?.Reset(); }
}
/// <summary>Executes one command through the same guarded boundary as the management actor.</summary>
public async Task<object?> ExecuteAsync(object command)
{
var principal = (await auth.GetAuthoritativeStateAsync()).User;
if (principal.Identity?.IsAuthenticated != true) throw new UnauthorizedAccessException("Session expired.");
var user = new AuthenticatedUser(principal.GetUsername(), principal.GetDisplayName() ?? principal.GetUsername(),
principal.FindAll(JwtTokenService.RoleClaimType).Select(c => c.Value).ToArray(),
principal.FindAll(JwtTokenService.SiteIdClaimType).Select(c => c.Value).ToArray());
try { return await ManagementActor.ExecuteConfigurationCommandAsync(services, command, user, services.GetService<CircuitClientAddressProvider>()?.ClientAddress); }
finally { services.GetService<IConfigurationReadSession>()?.Reset(); }
}
}
/// <summary>Resolves the mutation facade only when a page acts, keeping read-only rendering independent.</summary>
public static class ConfigurationCommandExtensions
{
/// <summary>Submits a database-only configuration mutation with current server-side authority.</summary>
public static Task<object?> ExecuteConfigurationAsync(this IServiceProvider services, object command)
=> services.GetRequiredService<ConfigurationCommandService>().ExecuteAsync(command);
}
@@ -1,3 +1,4 @@
using ZB.MOM.WW.ScadaBridge.CentralUI.Auth;
using System.Security.Claims;
using Akka.Actor;
using Microsoft.AspNetCore.Components.Authorization;
@@ -106,7 +107,7 @@ public sealed class DataConnectionMoveService : IDataConnectionMoveService
/// </summary>
private async Task<AuthenticatedUser> BuildAuthenticatedUserAsync()
{
var state = await _auth.GetAuthenticationStateAsync();
var state = await _auth.GetAuthoritativeStateAsync();
var principal = state.User;
var username = principal.FindFirst(JwtTokenService.UsernameClaimType)?.Value ?? "unknown";
@@ -1,3 +1,4 @@
using ZB.MOM.WW.ScadaBridge.CentralUI.Auth;
using Microsoft.AspNetCore.Components.Authorization;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
using ZB.MOM.WW.ScadaBridge.Commons.Serialization;
@@ -47,7 +48,7 @@ public sealed class EndpointVerificationService : IEndpointVerificationService
{
// CentralUI-side role guard — sites don't enforce envelope-level roles, so the
// Designer check must happen here before any cross-cluster traffic.
var state = await _auth.GetAuthenticationStateAsync();
var state = await _auth.GetAuthoritativeStateAsync();
if (!state.User.HasClaim(JwtTokenService.RoleClaimType, Roles.Designer))
{
return new VerifyEndpointResult(
@@ -299,7 +299,7 @@ public sealed class FlowDebugService : IFlowDebugService
var shortId = c.ToString("N")[..12];
try
{
var requestedBy = await _auth.GetCurrentUsernameAsync();
var requestedBy = await _auth.RequireRoleAsync(Roles.Deployer);
var corr = Guid.NewGuid().ToString("N");
if (isNotify)
@@ -580,7 +580,7 @@ public sealed class FlowDebugService : IFlowDebugService
private async Task<AuthenticatedUser> BuildAuthenticatedUserAsync()
{
var state = await _auth.GetAuthenticationStateAsync();
var state = await _auth.GetAuthoritativeStateAsync();
var principal = state.User;
var username = principal.FindFirst(JwtTokenService.UsernameClaimType)?.Value ?? "unknown";
@@ -1,3 +1,4 @@
using ZB.MOM.WW.ScadaBridge.CentralUI.Auth;
using System.Security.Claims;
using System.Text.Json;
using Akka.Actor;
@@ -144,7 +145,7 @@ public sealed class SchemaLibraryService : ISchemaLibraryService
/// </summary>
private async Task<AuthenticatedUser> BuildAuthenticatedUserAsync()
{
var state = await _auth.GetAuthenticationStateAsync();
var state = await _auth.GetAuthoritativeStateAsync();
var principal = state.User;
var username = principal.FindFirst(JwtTokenService.UsernameClaimType)?.Value ?? "unknown";
@@ -1,3 +1,4 @@
using ZB.MOM.WW.ScadaBridge.CentralUI.Auth;
using System.Security.Claims;
using System.Text.Json;
using Akka.Actor;
@@ -165,7 +166,7 @@ public sealed class SecuredWriteService : ISecuredWriteService
/// </summary>
private async Task<AuthenticatedUser> BuildAuthenticatedUserAsync()
{
var state = await _auth.GetAuthenticationStateAsync();
var state = await _auth.GetAuthoritativeStateAsync();
var principal = state.User;
var username = principal.FindFirst(JwtTokenService.UsernameClaimType)?.Value ?? "unknown";
@@ -1,3 +1,4 @@
using ZB.MOM.WW.ScadaBridge.CentralUI.Auth;
using System.Security.Claims;
using System.Text.Json;
using Akka.Actor;
@@ -112,7 +113,7 @@ public sealed class TemplateInheritanceQueryService : ITemplateInheritanceQueryS
/// </summary>
private async Task<AuthenticatedUser> BuildAuthenticatedUserAsync()
{
var state = await _auth.GetAuthenticationStateAsync();
var state = await _auth.GetAuthoritativeStateAsync();
var principal = state.User;
var username = principal.FindFirst(JwtTokenService.UsernameClaimType)?.Value ?? "unknown";