using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using ZB.MOM.WW.Auth.Abstractions.Roles; using ZB.MOM.WW.Auth.AspNetCore; using ZB.MOM.WW.MxGateway.Server.Configuration; using ZB.MOM.WW.MxGateway.Server.Security.Audit; namespace ZB.MOM.WW.MxGateway.Server.Dashboard; /// /// Extension methods for configuring the gateway dashboard services. /// public static class DashboardServiceCollectionExtensions { /// /// Registers all dashboard services, authentication, and Razor components. /// /// Service collection to register services. /// /// Application configuration, used to bind the shared LDAP provider's options /// from the MxGateway:Ldap section. Also read to select the dashboard /// authentication scheme via the MxGateway:Dashboard:DisableLogin dev flag. /// public static IServiceCollection AddGatewayDashboard( this IServiceCollection services, IConfiguration configuration) { // Dashboard logins delegate bind/search to the shared ZB.MOM.WW.Auth.Ldap // provider. Its LdapOptions bind straight from MxGateway:Ldap (the gateway's // LdapOptions field names match the shared options: Transport / AllowInsecure / // SearchBase / ServiceAccount* / *Attribute). AddZbLdapAuth also adds a // ValidateOnStart() so an insecure-transport misconfiguration fails fast at boot. services.AddZbLdapAuth(configuration, "MxGateway:Ldap"); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton, DashboardGroupRoleMapper>(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddScoped(); services.AddScoped(); services.AddSingleton(); services.AddHostedService(); services.AddHostedService(); services.AddHttpContextAccessor(); services.AddSingleton(); services.AddAntiforgery(); services.AddCascadingAuthenticationState(); services.AddRazorComponents() .AddInteractiveServerComponents(); services.AddSignalR(); // DEV/TEST ONLY. Read directly from configuration here because authentication scheme // registration runs before options binding. Key mirrors DashboardOptions.DisableLogin. bool disableLogin = configuration.GetValue("MxGateway:Dashboard:DisableLogin"); AuthenticationBuilder authentication = services.AddAuthentication(DashboardAuthenticationDefaults.AuthenticationScheme); if (disableLogin) { // Register an always-authenticating handler UNDER the cookie scheme name, so the // Viewer/Admin/HubClients policies (which all resolve this scheme) authenticate // through it as the multi-role dev user — zero policy or page changes. authentication.AddScheme( DashboardAuthenticationDefaults.AuthenticationScheme, _ => { }); // Loud warning, emitted on first resolution of GatewayOptions (i.e. on the first // request/options access, not guaranteed at process start). Dev-only safety notice. services.AddOptions().PostConfigure((gatewayOptions, loggerFactory) => loggerFactory .CreateLogger("ZB.MOM.WW.MxGateway.Server.Dashboard.DisableLogin") .LogWarning( "DASHBOARD LOGIN DISABLED (MxGateway:Dashboard:DisableLogin=true) — every request is " + "authenticated as '{User}' with full permissions ({Roles}). Dev/test only; never " + "enable in production.", string.IsNullOrWhiteSpace(gatewayOptions.Dashboard.AutoLoginUser) ? DashboardAutoLoginAuthenticationHandler.DefaultUser : gatewayOptions.Dashboard.AutoLoginUser!.Trim(), $"{DashboardRoles.Admin}, {DashboardRoles.Viewer}")); } else { authentication.AddCookie(DashboardAuthenticationDefaults.AuthenticationScheme, cookieOptions => { // Hardened defaults (HttpOnly, SameSite=Strict, SecurePolicy, SlidingExpiration, // ExpireTimeSpan) via the shared ZbCookieDefaults.Apply. requireHttps is set to // its default (true / Always) here and overridden per-environment by the // PostConfigure below; the 8-hour idle timeout is preserved (not the 30-min default). ZbCookieDefaults.Apply(cookieOptions, requireHttps: true, idleTimeout: TimeSpan.FromHours(8)); // Cookie name, path, and redirect paths are MxGateway-specific — set after Apply // so they are never overwritten by the shared helper (Apply intentionally skips name). // This is the canonical default; it is overridden per-environment from // DashboardOptions.CookieName by the PostConfigure below. cookieOptions.Cookie.Name = DashboardAuthenticationDefaults.CookieName; cookieOptions.Cookie.Path = "/"; cookieOptions.LoginPath = "/login"; cookieOptions.LogoutPath = "/logout"; cookieOptions.AccessDeniedPath = "/denied"; }); } authentication.AddScheme( DashboardAuthenticationDefaults.HubAuthenticationScheme, _ => { }); // Honour DashboardOptions.RequireHttpsCookie (default true / Always; set false for dev // HTTP deployments → SameAsRequest) and the optional per-environment cookie-name // override. Both run after the inline AddCookie config above, so they win. services.AddOptions(DashboardAuthenticationDefaults.AuthenticationScheme) .Configure>((cookieOptions, gatewayOptions) => { cookieOptions.Cookie.SecurePolicy = gatewayOptions.Value.Dashboard.RequireHttpsCookie ? CookieSecurePolicy.Always : CookieSecurePolicy.SameAsRequest; // Config-driven cookie name (MxGateway:Dashboard:CookieName). Null/blank keeps // the canonical default set above, so a misconfiguration cannot unname the cookie. var cookieName = gatewayOptions.Value.Dashboard.CookieName; if (!string.IsNullOrWhiteSpace(cookieName)) { cookieOptions.Cookie.Name = cookieName; } }); services.AddAuthorization(authorization => { authorization.AddPolicy( DashboardAuthenticationDefaults.ViewerPolicy, policy => policy.AddRequirements(DashboardAuthorizationRequirement.AnyDashboardRole)); authorization.AddPolicy( DashboardAuthenticationDefaults.AdminPolicy, policy => policy.AddRequirements(DashboardAuthorizationRequirement.AdminOnly)); authorization.AddPolicy( DashboardAuthenticationDefaults.HubClientsPolicy, policy => policy .AddAuthenticationSchemes( DashboardAuthenticationDefaults.AuthenticationScheme, DashboardAuthenticationDefaults.HubAuthenticationScheme) .AddRequirements(DashboardAuthorizationRequirement.AnyDashboardRole)); }); services.AddSingleton(); return services; } }