diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAutoLoginAuthenticationHandler.cs b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAutoLoginAuthenticationHandler.cs new file mode 100644 index 0000000..bb0adb9 --- /dev/null +++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAutoLoginAuthenticationHandler.cs @@ -0,0 +1,89 @@ +using System.Security.Claims; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using ZB.MOM.WW.Auth.AspNetCore; +using ZB.MOM.WW.MxGateway.Server.Configuration; + +namespace ZB.MOM.WW.MxGateway.Server.Dashboard; + +/// +/// Authentication handler used ONLY when MxGateway:Dashboard:DisableLogin is true. +/// Registered under the dashboard cookie scheme name +/// (), it authenticates +/// EVERY request as the configured dev user with both dashboard roles — no credential check, +/// no cookie, no LDAP bind. The minted principal mirrors the shape the real login +/// () produces, so policies and the UI cannot tell it +/// apart. DEV/TEST ONLY; never enable in production. +/// +public sealed class DashboardAutoLoginAuthenticationHandler + : AuthenticationHandler, IAuthenticationSignInHandler +{ + /// Username used when AutoLoginUser is null or blank. + public const string DefaultUser = "multi-role"; + + private readonly string _user; + + /// Initializes the handler with scheme plumbing and the dashboard options. + /// The per-scheme authentication options monitor. + /// The logger factory the base handler uses. + /// The URL encoder the base handler uses. + /// Gateway options carrying the dashboard auto-login user. + public DashboardAutoLoginAuthenticationHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + IOptions gatewayOptions) + : base(options, logger, encoder) + => _user = gatewayOptions.Value.Dashboard.AutoLoginUser ?? DefaultUser; + + /// No-op: auto-login writes no cookie, so a sign-in has nothing to persist. + /// Ignored. + /// Ignored. + /// A completed task. + public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties) => Task.CompletedTask; + + /// No-op: there is no auth cookie to clear; the next request re-authenticates. + /// Ignored. + /// A completed task. + public Task SignOutAsync(AuthenticationProperties? properties) => Task.CompletedTask; + + /// + protected override Task HandleAuthenticateAsync() + { + ClaimsPrincipal principal = CreatePrincipal(_user); + AuthenticationTicket ticket = new(principal, Scheme.Name); + + return Task.FromResult(AuthenticateResult.Success(ticket)); + } + + /// + /// Builds the multi-role dev principal. Null/blank falls back to + /// . Claim shape mirrors . + /// + /// The configured auto-login username (may be null/blank). + /// An authenticated principal holding both dashboard roles. + internal static ClaimsPrincipal CreatePrincipal(string? user) + { + string name = string.IsNullOrWhiteSpace(user) ? DefaultUser : user.Trim(); + + Claim[] claims = + [ + new Claim(ClaimTypes.NameIdentifier, name), + new Claim(ZbClaimTypes.Username, name), + new Claim(ZbClaimTypes.Name, name), + new Claim(ZbClaimTypes.DisplayName, name), + new Claim(ZbClaimTypes.Role, DashboardRoles.Admin), + new Claim(ZbClaimTypes.Role, DashboardRoles.Viewer), + ]; + + ClaimsIdentity identity = new( + claims, + DashboardAuthenticationDefaults.AuthenticationScheme, + ZbClaimTypes.Name, + ZbClaimTypes.Role); + + return new ClaimsPrincipal(identity); + } +} diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardAutoLoginAuthenticationHandlerTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardAutoLoginAuthenticationHandlerTests.cs new file mode 100644 index 0000000..41b9e78 --- /dev/null +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardAutoLoginAuthenticationHandlerTests.cs @@ -0,0 +1,37 @@ +using System.Security.Claims; +using ZB.MOM.WW.MxGateway.Server.Dashboard; + +namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard; + +public sealed class DashboardAutoLoginAuthenticationHandlerTests +{ + [Fact] + public void CreatePrincipal_MintsAuthenticatedMultiRoleUser() + { + ClaimsPrincipal principal = DashboardAutoLoginAuthenticationHandler.CreatePrincipal("multi-role"); + + Assert.True(principal.Identity!.IsAuthenticated); + Assert.Equal("multi-role", principal.Identity!.Name); + Assert.True(principal.IsInRole(DashboardRoles.Admin)); + Assert.True(principal.IsInRole(DashboardRoles.Viewer)); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void CreatePrincipal_BlankUser_FallsBackToDefault(string? user) + { + ClaimsPrincipal principal = DashboardAutoLoginAuthenticationHandler.CreatePrincipal(user); + + Assert.Equal(DashboardAutoLoginAuthenticationHandler.DefaultUser, principal.Identity!.Name); + } + + [Fact] + public void CreatePrincipal_TrimsUser() + { + ClaimsPrincipal principal = DashboardAutoLoginAuthenticationHandler.CreatePrincipal(" multi-role "); + + Assert.Equal("multi-role", principal.Identity!.Name); + } +}