0859d47f75
Introduce IAuditActorAccessor seam + HttpAuditActorAccessor impl (reads ZbClaimTypes.Username
from IHttpContextAccessor; falls back to Identity.Name / ZbClaimTypes.Name; null when
unauthenticated). Register in DI via DashboardServiceCollectionExtensions.
Wire DashboardApiKeyManagementService: WriteDashboardAuditAsync now accepts the ClaimsPrincipal
user already in scope at each call site; ResolveOperatorActor extracts ZbClaimTypes.Username
(preferred) or Identity.Name. All four dashboard-* events now emit Actor = LDAP operator
username and Target = managed keyId, fixing the semantic gap where both fields held the keyId.
ConstraintEnforcer (gRPC / API-key actor) and CanonicalForwardingApiKeyAuditStore (CLI /
"system"/"cli" fallback) are unchanged.
Tests: DashboardApiKeyManagementServiceTests updated — CreateAuthorizedUser adds ZbClaimTypes.Username
("alice"), all dashboard-* audit assertions updated to Actor = "alice" / Target = "operator01";
new CreateAsync_AuthorizedUser_CanonicalAuditEventHasOperatorAsActorAndKeyIdAsTarget verifies the
canonical AuditEvent directly. New HttpAuditActorAccessorTests (4 cases: username claim, Identity.Name
fallback, unauthenticated → null, no context → null). ConstraintEnforcer tests still assert API-key/anonymous actor.
52 lines
2.1 KiB
C#
52 lines
2.1 KiB
C#
using System.Security.Claims;
|
|
using ZB.MOM.WW.Auth.AspNetCore;
|
|
|
|
namespace ZB.MOM.WW.MxGateway.Server.Security.Audit;
|
|
|
|
/// <summary>
|
|
/// HTTP-context-backed implementation of <see cref="IAuditActorAccessor"/> that reads the
|
|
/// dashboard operator's identity from the current <see cref="IHttpContextAccessor"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Claim resolution order:
|
|
/// <list type="number">
|
|
/// <item><see cref="ZbClaimTypes.Username"/> ("zb:username") — the canonical LDAP login name.</item>
|
|
/// <item><see cref="ClaimsPrincipal.Identity"/>.<see cref="System.Security.Principal.IIdentity.Name"/> — framework fallback (= <see cref="ZbClaimTypes.Name"/> = <see cref="ClaimTypes.Name"/> = display name).</item>
|
|
/// <item><see cref="ZbClaimTypes.Name"/> — explicit fallback matching the claim emitted by <c>DashboardAuthenticator.CreatePrincipal</c>.</item>
|
|
/// </list>
|
|
/// Returns <see langword="null"/> when there is no HTTP context or the user is not authenticated.
|
|
/// </remarks>
|
|
public sealed class HttpAuditActorAccessor(IHttpContextAccessor httpContextAccessor) : IAuditActorAccessor
|
|
{
|
|
/// <inheritdoc />
|
|
public string? CurrentActor
|
|
{
|
|
get
|
|
{
|
|
ClaimsPrincipal? user = httpContextAccessor.HttpContext?.User;
|
|
if (user?.Identity?.IsAuthenticated != true)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// Prefer the canonical login-username claim (set by DashboardAuthenticator).
|
|
string? username = user.FindFirstValue(ZbClaimTypes.Username);
|
|
if (!string.IsNullOrWhiteSpace(username))
|
|
{
|
|
return username;
|
|
}
|
|
|
|
// Framework fallback: Identity.Name is driven by the ClaimsIdentity nameClaimType,
|
|
// which DashboardAuthenticator sets to ZbClaimTypes.Name (= ClaimTypes.Name = display name).
|
|
string? identityName = user.Identity?.Name;
|
|
if (!string.IsNullOrWhiteSpace(identityName))
|
|
{
|
|
return identityName;
|
|
}
|
|
|
|
// Final explicit fallback — ZbClaimTypes.Name claim value directly.
|
|
return user.FindFirstValue(ZbClaimTypes.Name);
|
|
}
|
|
}
|
|
}
|