feat(audit): OtOpcUa IAuditActorAccessor seam + HTTP impl (audit Actor from Auth principal) (Phase 3)
v2-ci / build (push) Failing after 40s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
v2-ci / build (push) Failing after 40s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Introduces the IAuditActorAccessor seam and HttpAuditActorAccessor impl so the ZB.MOM.WW.Audit.AuditEvent Actor field can be sourced from the authenticated Blazor cookie principal (ZbClaimTypes.Username) when structured emitters are added. Adds the AuditActor.Resolve static helper (accessor value → SystemFallback/"system") as the canonical pattern for future emit sites. Wires DI in AddOtOpcUaAuth (TryAddScoped) with AddHttpContextAccessor(). The structured AuditEvent path remains DORMANT — no live emit sites exist; seam is forward-looking. SP-based audit path left untouched. 9 new unit tests all green; Security (54) and ControlPlane (45) test suites fully pass.
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Security.Audit;
|
||||
|
||||
/// <summary>
|
||||
/// Default-resolution helpers for the <c>Actor</c> field of a canonical
|
||||
/// <c>ZB.MOM.WW.Audit.AuditEvent</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Usage pattern — call <see cref="Resolve"/> when constructing an <c>AuditEvent</c>:
|
||||
/// <code>
|
||||
/// new AuditEvent
|
||||
/// {
|
||||
/// Actor = AuditActor.Resolve(auditActorAccessor),
|
||||
/// ...
|
||||
/// }
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Note:</b> OtOpcUa has no live structured <c>AuditEvent</c> emit sites as of Phase 3
|
||||
/// (all production audit flows through the bespoke stored-procedure path). This helper is
|
||||
/// forward-looking — it is tested and ready so that future emit sites pick up the correct
|
||||
/// Actor automatically.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static class AuditActor
|
||||
{
|
||||
/// <summary>The fallback actor string used when no authenticated principal is available.</summary>
|
||||
public const string SystemFallback = "system";
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current principal's actor string from <paramref name="accessor"/>, or
|
||||
/// <see cref="SystemFallback"/> when the accessor returns <see langword="null"/>
|
||||
/// (no HTTP context, unauthenticated, or in a background/non-HTTP execution context).
|
||||
/// </summary>
|
||||
/// <param name="accessor">The audit-actor accessor. May be <see langword="null"/>
|
||||
/// (e.g. in a background context where DI did not wire the accessor).</param>
|
||||
/// <returns>The actor string — never <see langword="null"/>.</returns>
|
||||
public static string Resolve(IAuditActorAccessor? accessor) =>
|
||||
Resolve(accessor, SystemFallback);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current principal's actor string from <paramref name="accessor"/>, or
|
||||
/// <paramref name="fallback"/> when the accessor returns <see langword="null"/>.
|
||||
/// </summary>
|
||||
/// <param name="accessor">The audit-actor accessor. May be <see langword="null"/>.</param>
|
||||
/// <param name="fallback">The explicit fallback value.</param>
|
||||
/// <returns>The actor string — never <see langword="null"/>.</returns>
|
||||
public static string Resolve(IAuditActorAccessor? accessor, string fallback) =>
|
||||
accessor?.CurrentActor ?? fallback;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using ZB.MOM.WW.Auth.AspNetCore;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Security.Audit;
|
||||
|
||||
/// <summary>
|
||||
/// HTTP-context–backed <see cref="IAuditActorAccessor"/> for the OtOpcUa control-plane.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Reads the authenticated principal from <see cref="IHttpContextAccessor"/>:
|
||||
/// <list type="number">
|
||||
/// <item>If there is no current <c>HttpContext</c> or the user is not authenticated,
|
||||
/// returns <see langword="null"/>.</item>
|
||||
/// <item>Otherwise, returns the <see cref="ZbClaimTypes.Username"/> claim value (the
|
||||
/// canonical directory login name set at sign-in by <c>AuthEndpoints</c>).</item>
|
||||
/// <item>Falls back to the <see cref="ZbClaimTypes.Name"/> claim, then to
|
||||
/// <see cref="System.Security.Principal.IIdentity.Name"/>, in that order.</item>
|
||||
/// </list>
|
||||
/// <para>
|
||||
/// Registered as <b>scoped</b> in <see cref="ZB.MOM.WW.OtOpcUa.Security.ServiceCollectionExtensions.AddOtOpcUaAuth"/>
|
||||
/// so that it correctly follows the request scope used by Blazor Server interactive components
|
||||
/// and minimal-API endpoints. <c>IHttpContextAccessor</c> is registered by
|
||||
/// <c>AddOtOpcUaAuth</c> via <c>services.AddHttpContextAccessor()</c>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class HttpAuditActorAccessor : IAuditActorAccessor
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
/// <summary>Initializes the accessor with the ASP.NET Core HTTP context accessor.</summary>
|
||||
/// <param name="httpContextAccessor">The HTTP context accessor.</param>
|
||||
public HttpAuditActorAccessor(IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? CurrentActor
|
||||
{
|
||||
get
|
||||
{
|
||||
var user = _httpContextAccessor.HttpContext?.User;
|
||||
if (user?.Identity?.IsAuthenticated != true)
|
||||
return null;
|
||||
|
||||
// Prefer the canonical login-name claim; fall back to the Name claim or
|
||||
// Identity.Name (both of which map to ClaimTypes.Name / ZbClaimTypes.Name).
|
||||
return user.FindFirst(ZbClaimTypes.Username)?.Value
|
||||
?? user.FindFirst(ZbClaimTypes.Name)?.Value
|
||||
?? user.Identity.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Security.Audit;
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the current HTTP principal's actor string for inclusion in a canonical
|
||||
/// <c>ZB.MOM.WW.Audit.AuditEvent</c> as the <c>Actor</c> field.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The seam abstracts the identity source so that:
|
||||
/// <list type="bullet">
|
||||
/// <item>production code uses <see cref="HttpAuditActorAccessor"/> (reads the
|
||||
/// authenticated Blazor cookie principal from <c>IHttpContextAccessor</c>); and</item>
|
||||
/// <item>unit tests or non-HTTP contexts can substitute a stub or return
|
||||
/// <see langword="null"/> (which triggers the <c>"system"</c> fallback in
|
||||
/// <see cref="AuditActor.Resolve"/>).</item>
|
||||
/// </list>
|
||||
/// <para>
|
||||
/// <b>Note:</b> OtOpcUa has no live structured <c>AuditEvent</c> emit sites as of Phase 3
|
||||
/// (all production audit flows through the bespoke stored-procedure path). This seam is
|
||||
/// forward-looking — wired and tested so that future emit sites can call
|
||||
/// <see cref="AuditActor.Resolve"/> and get the Auth principal automatically.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public interface IAuditActorAccessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the authenticated principal's actor string, or <see langword="null"/> when
|
||||
/// there is no current HTTP context or the user is not authenticated.
|
||||
/// </summary>
|
||||
string? CurrentActor { get; }
|
||||
}
|
||||
Reference in New Issue
Block a user