fix: honor LdapOptions.Enabled at runtime; dedupe ILdapAuthService registration; +SearchBase test, doc fix
v2-ci / build (push) Failing after 41s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
v2-ci / build (push) Failing after 41s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
This commit is contained in:
@@ -10,8 +10,9 @@ namespace ZB.MOM.WW.OtOpcUa.Host.Configuration;
|
|||||||
/// TCP port; when disabled — or when <c>DevStubMode</c> bypasses the real bind — all checks are
|
/// TCP port; when disabled — or when <c>DevStubMode</c> bypasses the real bind — all checks are
|
||||||
/// skipped. <c>ServiceAccountDn</c>/<c>Password</c> are
|
/// skipped. <c>ServiceAccountDn</c>/<c>Password</c> are
|
||||||
/// intentionally not required — an empty pair selects the direct-bind path (see
|
/// intentionally not required — an empty pair selects the direct-bind path (see
|
||||||
/// <see cref="LdapOptions.ServiceAccountDn"/>). Failure messages carry the real <c>"Ldap:"</c>
|
/// <see cref="LdapOptions.ServiceAccountDn"/>). Failure messages use <c>"Ldap:"</c> as a
|
||||||
/// section prefix matching the bound configuration section.
|
/// human-readable field prefix — not the literal bound section path, which is
|
||||||
|
/// <c>Security:Ldap</c> (see <see cref="LdapOptions.SectionName"/>).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class LdapOptionsValidator : OptionsValidatorBase<LdapOptions>
|
public sealed class LdapOptionsValidator : OptionsValidatorBase<LdapOptions>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Akka.Hosting;
|
using Akka.Hosting;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using ZB.MOM.WW.OtOpcUa.AdminUI;
|
using ZB.MOM.WW.OtOpcUa.AdminUI;
|
||||||
using ZB.MOM.WW.OtOpcUa.AdminUI.Clients;
|
using ZB.MOM.WW.OtOpcUa.AdminUI.Clients;
|
||||||
@@ -100,7 +101,9 @@ if (hasDriver)
|
|||||||
builder.Services.AddSingleton<IScriptedAlarmEvaluator>(sp => sp.GetRequiredService<RoslynScriptedAlarmEvaluator>());
|
builder.Services.AddSingleton<IScriptedAlarmEvaluator>(sp => sp.GetRequiredService<RoslynScriptedAlarmEvaluator>());
|
||||||
|
|
||||||
builder.Services.AddValidatedOptions<LdapOptions, LdapOptionsValidator>(builder.Configuration, LdapOptions.SectionName);
|
builder.Services.AddValidatedOptions<LdapOptions, LdapOptionsValidator>(builder.Configuration, LdapOptions.SectionName);
|
||||||
builder.Services.AddSingleton<ILdapAuthService, LdapAuthService>();
|
// TryAdd so a fused admin+driver node (where AddOtOpcUaAuth also registers this) ends up
|
||||||
|
// with exactly one descriptor; on a driver-only node this is the sole registration.
|
||||||
|
builder.Services.TryAddSingleton<ILdapAuthService, LdapAuthService>();
|
||||||
builder.Services.AddSingleton<IOpcUaUserAuthenticator, LdapOpcUaUserAuthenticator>();
|
builder.Services.AddSingleton<IOpcUaUserAuthenticator, LdapOpcUaUserAuthenticator>();
|
||||||
|
|
||||||
// Bind + validate the OPC UA host options the same way (fail-fast at start via ValidateOnStart)
|
// Bind + validate the OPC UA host options the same way (fail-fast at start via ValidateOnStart)
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ public sealed class LdapAuthService(IOptions<LdapOptions> options, ILogger<LdapA
|
|||||||
/// <param name="ct">A cancellation token to observe while waiting for the operation to complete.</param>
|
/// <param name="ct">A cancellation token to observe while waiting for the operation to complete.</param>
|
||||||
public async Task<LdapAuthResult> AuthenticateAsync(string username, string password, CancellationToken ct = default)
|
public async Task<LdapAuthResult> AuthenticateAsync(string username, string password, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
// Enabled is the master switch and wins over DevStubMode — when LDAP auth is turned off,
|
||||||
|
// refuse to authenticate at all (no bind, no dev-stub bypass).
|
||||||
|
if (!_options.Enabled)
|
||||||
|
return new(false, null, null, [], [], "LDAP authentication is disabled.");
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(username))
|
if (string.IsNullOrWhiteSpace(username))
|
||||||
return new(false, null, null, [], [], "Username is required");
|
return new(false, null, null, [], [], "Username is required");
|
||||||
if (string.IsNullOrWhiteSpace(password))
|
if (string.IsNullOrWhiteSpace(password))
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.DataProtection;
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using ZB.MOM.WW.OtOpcUa.Configuration;
|
using ZB.MOM.WW.OtOpcUa.Configuration;
|
||||||
@@ -36,7 +37,9 @@ public static class ServiceCollectionExtensions
|
|||||||
services.AddSingleton<JwtTokenService>();
|
services.AddSingleton<JwtTokenService>();
|
||||||
// Singleton — LdapAuthService is stateless (creates an LdapConnection per call) and
|
// Singleton — LdapAuthService is stateless (creates an LdapConnection per call) and
|
||||||
// must be consumable by the Singleton LdapOpcUaUserAuthenticator on driver-role nodes.
|
// must be consumable by the Singleton LdapOpcUaUserAuthenticator on driver-role nodes.
|
||||||
services.AddSingleton<ILdapAuthService, LdapAuthService>();
|
// TryAdd so a fused admin+driver node (which also registers it in Program.cs for the
|
||||||
|
// driver path) ends up with exactly one descriptor regardless of registration order.
|
||||||
|
services.TryAddSingleton<ILdapAuthService, LdapAuthService>();
|
||||||
|
|
||||||
services.AddDataProtection()
|
services.AddDataProtection()
|
||||||
.PersistKeysToDbContext<OtOpcUaConfigDbContext>()
|
.PersistKeysToDbContext<OtOpcUaConfigDbContext>()
|
||||||
|
|||||||
@@ -83,6 +83,24 @@ public sealed class LdapOptionsValidatorTests
|
|||||||
result.Failures.ShouldContain("Ldap:Server is required when LDAP login is enabled.");
|
result.Failures.ShouldContain("Ldap:Server is required when LDAP login is enabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Enabled with a blank search base reports the required-search-base failure.</summary>
|
||||||
|
[Fact]
|
||||||
|
public void Enabled_with_blank_search_base_fails()
|
||||||
|
{
|
||||||
|
var options = new LdapOptions
|
||||||
|
{
|
||||||
|
Enabled = true,
|
||||||
|
Server = "ldap",
|
||||||
|
SearchBase = string.Empty,
|
||||||
|
Port = 389,
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = Sut.Validate(null, options);
|
||||||
|
|
||||||
|
result.Failed.ShouldBeTrue();
|
||||||
|
result.Failures.ShouldContain("Ldap:SearchBase is required when LDAP login is enabled.");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Enabled with port 0 reports the port-range failure using the shared primitive wording.</summary>
|
/// <summary>Enabled with port 0 reports the port-range failure using the shared primitive wording.</summary>
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Enabled_with_zero_port_fails()
|
public void Enabled_with_zero_port_fails()
|
||||||
|
|||||||
Reference in New Issue
Block a user