diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Security/ServiceCollectionExtensions.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Security/ServiceCollectionExtensions.cs index f62b7e5f..2eba6ddd 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Security/ServiceCollectionExtensions.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Security/ServiceCollectionExtensions.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.DataProtection; @@ -11,6 +12,7 @@ using ZB.MOM.WW.Auth.AspNetCore; using ZB.MOM.WW.Auth.Abstractions.Roles; using ZB.MOM.WW.OtOpcUa.Configuration; using ZB.MOM.WW.OtOpcUa.Security.Audit; +using ZB.MOM.WW.OtOpcUa.Security.Auth; using ZB.MOM.WW.OtOpcUa.Security.Jwt; using ZB.MOM.WW.OtOpcUa.Security.Ldap; @@ -36,6 +38,7 @@ public static class ServiceCollectionExtensions services.AddOptions().Bind(configuration.GetSection(JwtOptions.SectionName)); services.AddOptions().Bind(configuration.GetSection(OtOpcUaCookieOptions.SectionName)); services.AddOptions().Bind(configuration.GetSection(LdapOptions.SectionName)); + services.AddOptions().Bind(configuration.GetSection(AuthDisableLoginOptions.SectionName)); services.AddSingleton(); @@ -69,8 +72,32 @@ public static class ServiceCollectionExtensions .PersistKeysToDbContext() .SetApplicationName("OtOpcUa"); - services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) - .AddCookie(o => + var disableLogin = configuration + .GetSection(AuthDisableLoginOptions.SectionName) + .GetValue(nameof(AuthDisableLoginOptions.DisableLogin)); + + 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 FallbackPolicy + FleetAdmin + DriverOperator (which all + // name this scheme) authenticate through it and pass with all roles — zero policy changes. + authBuilder.AddScheme( + CookieAuthenticationDefaults.AuthenticationScheme, _ => { }); + + // Loud, once-at-first-resolve warning (mirrors the cookie RequireHttps warning idiom below). + services.AddOptions() + .PostConfigure((opts, lf) => + { + lf.CreateLogger("ZB.MOM.WW.OtOpcUa.Security").LogWarning( + "AdminUI LOGIN DISABLED (Security:Auth:DisableLogin=true) — every request is " + + "authenticated as '{User}' with FULL permissions ({Roles}). Dev/test only; never " + + "enable in production.", opts.User, string.Join(",", DevAuthRoles.All)); + }); + } + else + { + authBuilder.AddCookie(o => { // Static fields only — Name / ExpireTimeSpan / SecurePolicy / SlidingExpiration / // HttpOnly / SameSite are applied from OtOpcUaCookieOptions via ZbCookieDefaults @@ -80,6 +107,7 @@ public static class ServiceCollectionExtensions // No OnRedirectToLogin / OnRedirectToAccessDenied overrides — let the framework's // built-in IsAjaxRequest heuristic do its thing (302 for browsers, 401 for AJAX). }); + } // Externalised cookie config — mirrors ScadaBridge's PostConfigure pattern. Fixes a // pre-existing latent bug where OtOpcUaCookieOptions was bound but ignored. diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests/AddOtOpcUaAuthWiringTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests/AddOtOpcUaAuthWiringTests.cs new file mode 100644 index 00000000..2ec9c00c --- /dev/null +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests/AddOtOpcUaAuthWiringTests.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Xunit; +using ZB.MOM.WW.OtOpcUa.Security; +using ZB.MOM.WW.OtOpcUa.Security.Auth; + +namespace ZB.MOM.WW.OtOpcUa.Security.Tests; + +public class AddOtOpcUaAuthWiringTests +{ + private static async Task CookieHandlerTypeAsync(bool disableLogin) + { + var config = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary + { + ["Security:Auth:DisableLogin"] = disableLogin ? "true" : "false", + }).Build(); + + var services = new ServiceCollection(); + services.AddLogging(); + services.AddOtOpcUaAuth(config); + + await using var sp = services.BuildServiceProvider(); + var provider = sp.GetRequiredService(); + var scheme = await provider.GetSchemeAsync(CookieAuthenticationDefaults.AuthenticationScheme); + return scheme!.HandlerType; + } + + [Fact] + public async Task DisableLogin_true_registers_autologin_handler_for_cookie_scheme() + => (await CookieHandlerTypeAsync(true)).ShouldBe(typeof(AutoLoginAuthenticationHandler)); + + [Fact] + public async Task DisableLogin_false_registers_cookie_handler() + => (await CookieHandlerTypeAsync(false)).ShouldBe(typeof(CookieAuthenticationHandler)); +}