feat(auth): MxGateway dashboard adopt ZbClaimTypes + ZbCookieDefaults, keep cookie name (Task 1.5)

- DashboardAuthenticator.CreatePrincipal: emit ZbClaimTypes.Username ("zb:username") with
  the login username, ZbClaimTypes.DisplayName ("zb:displayname") with the display name,
  ZbClaimTypes.Name (== ClaimTypes.Name) for Identity.Name resolution, ZbClaimTypes.Role
  (== ClaimTypes.Role) for IsInRole/[Authorize]. Keep ClaimTypes.NameIdentifier for back-compat
  read-sites; keep mxgateway:ldap_group unchanged (MxGateway-specific, no ZbClaimType for groups).
  ClaimsIdentity built with nameType=ZbClaimTypes.Name, roleType=ZbClaimTypes.Role.
- DashboardServiceCollectionExtensions.AddGatewayDashboard: route cookie hardening through
  ZbCookieDefaults.Apply(requireHttps:true, idleTimeout:8h); set cookie name/path/redirects
  after Apply; PostConfigure still overrides SecurePolicy per RequireHttpsCookie setting.
- DashboardAuthenticatorTests: add AuthenticateAsync_Success_EmitsCanonicalZbClaims asserting
  zb:username, zb:displayname, ZbClaimTypes.Role per role, Identity.Name, and ldap_group preserved.
This commit is contained in:
Joseph Doherty
2026-06-02 06:10:48 -04:00
parent 05009d7370
commit 7e1af37eb1
3 changed files with 85 additions and 13 deletions
@@ -1,6 +1,7 @@
using System.Security.Claims;
using ZB.MOM.WW.Auth.Abstractions.Ldap;
using ZB.MOM.WW.Auth.Abstractions.Roles;
using ZB.MOM.WW.Auth.AspNetCore;
namespace ZB.MOM.WW.MxGateway.Server.Dashboard;
@@ -75,8 +76,15 @@ public sealed class DashboardAuthenticator(
/// <summary>
/// Builds the dashboard <see cref="ClaimsPrincipal"/> from the LDAP outcome.
/// </summary>
/// <param name="username">The (trimmed) login name → <see cref="ClaimTypes.NameIdentifier"/>.</param>
/// <param name="displayName">The user's display name → <see cref="ClaimTypes.Name"/>.</param>
/// <param name="username">
/// The (trimmed) login name. Emitted as <see cref="ClaimTypes.NameIdentifier"/> (kept for
/// back-compat reads) and as the canonical <see cref="ZbClaimTypes.Username"/> ("zb:username").
/// </param>
/// <param name="displayName">
/// The user's display name. Emitted as <see cref="ZbClaimTypes.Name"/> (= <see cref="ClaimTypes.Name"/>
/// so <c>Identity.Name</c> resolves) and as <see cref="ZbClaimTypes.DisplayName"/> ("zb:displayname")
/// for cross-app consistency.
/// </param>
/// <param name="groups">
/// The user's LDAP groups, as returned by <see cref="ILdapAuthService"/>. NOTE
/// (review C1): these are <b>already-normalized short RDN names</b> (e.g.
@@ -97,13 +105,21 @@ public sealed class DashboardAuthenticator(
{
List<Claim> claims =
[
// Keep NameIdentifier so any existing read-site that uses it continues to work.
new Claim(ClaimTypes.NameIdentifier, username),
new Claim(ClaimTypes.Name, displayName),
// Canonical login-username claim (Task 1.5).
new Claim(ZbClaimTypes.Username, username),
// ZbClaimTypes.Name == ClaimTypes.Name — drives Identity.Name resolution.
new Claim(ZbClaimTypes.Name, displayName),
// Canonical display-name claim for cross-app consistency (Task 1.5).
new Claim(ZbClaimTypes.DisplayName, displayName),
];
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
// ZbClaimTypes.Role == ClaimTypes.Role — drives IsInRole and [Authorize(Roles=...)].
claims.AddRange(roles.Select(role => new Claim(ZbClaimTypes.Role, role)));
// Groups are short RDN names from ILdapAuthService (see param doc above), so
// this claim value is the short group name, not the original DN.
// LdapGroupClaimType is MxGateway-specific ("mxgateway:ldap_group") — no ZbClaimType for groups.
claims.AddRange(groups.Select(group => new Claim(
DashboardAuthenticationDefaults.LdapGroupClaimType,
group)));
@@ -111,8 +127,8 @@ public sealed class DashboardAuthenticator(
ClaimsIdentity claimsIdentity = new(
claims,
DashboardAuthenticationDefaults.AuthenticationScheme,
ClaimTypes.Name,
ClaimTypes.Role);
ZbClaimTypes.Name,
ZbClaimTypes.Role);
return new ClaimsPrincipal(claimsIdentity);
}