feat(security): wire DisableLogin flag — auto-login scheme + startup warning

This commit is contained in:
Joseph Doherty
2026-06-16 08:47:19 -04:00
parent 0926ce4dda
commit e89604298d
4 changed files with 78 additions and 6 deletions
+9 -1
View File
@@ -121,7 +121,15 @@ try
builder.Services.AddZbLdapAuth(
builder.Configuration,
ZB.MOM.WW.ScadaBridge.Security.ServiceCollectionExtensions.LdapSectionPath);
builder.Services.AddSecurity();
// Dev disable-login flag (config-coupled, so read + bound here at the composition root,
// mirroring AddZbLdapAuth). Default false. See AuthDisableLoginOptions / disable-login design doc.
builder.Services.AddOptions<ZB.MOM.WW.ScadaBridge.Security.Auth.AuthDisableLoginOptions>()
.Bind(builder.Configuration.GetSection(
ZB.MOM.WW.ScadaBridge.Security.Auth.AuthDisableLoginOptions.SectionName));
var disableLogin = builder.Configuration
.GetSection(ZB.MOM.WW.ScadaBridge.Security.Auth.AuthDisableLoginOptions.SectionName)
.GetValue<bool>(nameof(ZB.MOM.WW.ScadaBridge.Security.Auth.AuthDisableLoginOptions.DisableLogin));
builder.Services.AddSecurity(disableLogin);
builder.Services.AddCentralUI();
builder.Services.AddInboundAPI();
@@ -36,8 +36,15 @@ public static class ServiceCollectionExtensions
/// <c>IConfiguration</c>) and component libraries must not accept <c>IConfiguration</c>.
/// </remarks>
/// <param name="services">The service collection to register into.</param>
/// <param name="disableLogin">
/// Dev/test flag (bound from <see cref="Auth.AuthDisableLoginOptions"/> by the Host
/// composition root). When true the cookie handler is replaced — under the same cookie
/// scheme name — by an always-succeeding <see cref="Auth.AutoLoginAuthenticationHandler"/>
/// that authenticates every request as the configured dev user with ALL roles, and a loud
/// startup warning is emitted. Default false. Never enable in production.
/// </param>
/// <returns>The same <paramref name="services"/> instance for chaining.</returns>
public static IServiceCollection AddSecurity(this IServiceCollection services)
public static IServiceCollection AddSecurity(this IServiceCollection services, bool disableLogin = false)
{
// Task 1.2 cutover: ScadaBridge's bespoke LdapAuthService was replaced by the
// shared ZB.MOM.WW.Auth.Ldap implementation (ScadaBridge was the donor for its
@@ -104,8 +111,27 @@ public static class ServiceCollectionExtensions
// timeout, HTTPS policy) are applied via the SecurityOptions-bound PostConfigure
// below (cookie name through SecurityOptions.CookieName, the rest through
// ZbCookieDefaults.Apply).
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
var authBuilder = services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);
if (disableLogin)
{
// DEV/TEST ONLY: replace the cookie handler with an always-succeeding handler registered
// UNDER the cookie scheme name, so every authorization policy (which names this scheme)
// authenticates through it with all roles — zero policy changes. No cookie is written;
// OnValidatePrincipal (idle/refresh) does not apply in this mode. See AuthDisableLoginOptions.
authBuilder.AddScheme<AuthenticationSchemeOptions, Auth.AutoLoginAuthenticationHandler>(
CookieAuthenticationDefaults.AuthenticationScheme, _ => { });
services.AddOptions<Auth.AuthDisableLoginOptions>()
.PostConfigure<ILoggerFactory>((opts, lf) =>
lf.CreateLogger("ZB.MOM.WW.ScadaBridge.Security").LogWarning(
"AUTH DISABLED (ScadaBridge:Security:Auth:DisableLogin=true) — every request is " +
"authenticated as '{User}' with FULL permissions ({Roles}) across ALL sites. This " +
"is a SCADA control surface; dev/test ONLY — never enable in production.",
opts.User, string.Join(",", Roles.All)));
}
else
{
authBuilder.AddCookie(options =>
{
options.LoginPath = "/login";
options.LogoutPath = "/auth/logout";
@@ -126,6 +152,7 @@ public static class ServiceCollectionExtensions
// keeps the session (mirrors "LDAP failure: active sessions continue").
options.Events.OnValidatePrincipal = OnValidatePrincipalAsync;
});
}
// CentralUI-005: configure the cookie session as a sliding window so the
// code matches the documented policy ("sliding refresh, 30-minute idle
@@ -147,6 +174,8 @@ public static class ServiceCollectionExtensions
// the shared ZbCookieDefaults.Apply, with requireHttps + idleTimeout driven
// by SecurityOptions so behaviour (30-min sliding idle window, HTTPS-only
// unless explicitly opted out) is preserved.
// Harmless/unused when disableLogin is true: the cookie handler is not registered,
// so this CookieAuthenticationOptions PostConfigure has no scheme to configure.
services.AddOptions<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme)
.Configure<IOptions<SecurityOptions>, ILoggerFactory>((cookieOptions, securityOptions, loggerFactory) =>
{