Files
lmxopcua/src/Server/ZB.MOM.WW.OtOpcUa.Host/Configuration/LdapOptionsValidator.cs
T
Joseph Doherty 826312befe docs: fill missing XML docs and strip tracking IDs from src comments
Clears CommentChecker findings in src/ (3,280 -> 209, the remainder being false
positives like PLC-5/IEEE-754/M0.0) by adding missing summaries/params/returns,
normalising inheritdoc usage, and removing Gitea/review tracking IDs while keeping
the rationale they annotated. Comment-only: comment-stripped sources are identical
to the parent commit.
2026-10-15 06:14:58 -04:00

60 lines
3.7 KiB
C#

using ZB.MOM.WW.Configuration;
using ZB.MOM.WW.OtOpcUa.Security.Ldap;
using LdapTransport = ZB.MOM.WW.Auth.Abstractions.Ldap.LdapTransport;
namespace ZB.MOM.WW.OtOpcUa.Host.Configuration;
/// <summary>
/// Fail-fast startup validator for <see cref="LdapOptions"/>, built on the shared
/// <c>ZB.MOM.WW.Configuration</c> <see cref="OptionsValidatorBase{TOptions}"/>. When LDAP login
/// is enabled, <c>Server</c> and <c>SearchBase</c> must be set and <c>Port</c> must be a valid
/// TCP port; when disabled — or when <c>DevStubMode</c> bypasses the real bind — all checks are
/// skipped. <c>ServiceAccountDn</c>/<c>Password</c> are
/// intentionally not required — an empty pair selects the direct-bind path (see
/// <see cref="LdapOptions.ServiceAccountDn"/>). Failure messages use <c>"Ldap:"</c> as a
/// human-readable field prefix — not the literal bound section path, which is
/// <c>Security:Ldap</c> (see <see cref="LdapOptions.SectionName"/>).
/// </summary>
/// <remarks>
/// Insecure-transport guard (review fix): a real-LDAP config that selects plaintext transport
/// (<see cref="LdapTransport.None"/>) without opting in via <see cref="LdapOptions.AllowInsecure"/>
/// now FAILS startup validation, so an insecure-by-accident production overlay never boots.
/// This mirrors the login-time fail-closed guard in <see cref="OtOpcUaLdapAuthService"/> and is
/// gated on the same conditions (<see cref="LdapOptions.Enabled"/> AND not
/// <see cref="LdapOptions.DevStubMode"/>): a disabled or dev-stub config is exempt, exactly as it
/// is exempt from the real bind. The login-time guard remains as defence in depth.
/// </remarks>
public sealed class LdapOptionsValidator : OptionsValidatorBase<LdapOptions>
{
/// <inheritdoc />
protected override void Validate(ValidationBuilder builder, LdapOptions options)
{
// Skip the real-LDAP field checks when LDAP login is disabled, or when the dev stub is
// active — DevStubMode bypasses the real bind entirely, so Server/SearchBase/Port are
// irrelevant and would otherwise force dev configs to carry meaningless placeholders.
if (!options.Enabled || options.DevStubMode) return;
builder.RequireThat(!string.IsNullOrWhiteSpace(options.Server),
"Ldap:Server is required when LDAP login is enabled.");
builder.RequireThat(!string.IsNullOrWhiteSpace(options.SearchBase),
"Ldap:SearchBase is required when LDAP login is enabled.");
builder.Port(options.Port, "Ldap:Port");
// Fail closed at startup on a plaintext transport unless explicitly opted in — same
// condition the login-time guard in OtOpcUaLdapAuthService enforces, lifted to boot so an
// insecure-by-accident production overlay refuses to start rather than silently failing
// every bind at login.
builder.RequireThat(
!(options.Transport == LdapTransport.None && !options.AllowInsecure),
"LDAP transport is None (plaintext) but AllowInsecure is false — set Transport to Ldaps/StartTls or set AllowInsecure for dev.");
// A non-positive connect/search timeout would leave the shared library unbounded against a hung
// directory, so fail fast. The other four S2 knobs validated here (AuthTimeoutMs,
// MaxConcurrentAuthentications, OutageFailureThreshold, OutageCooldownSeconds) went with
// LdapOpcUaUserAuthenticator — they guarded an LDAP bind running inline on OPC UA
// session activation, and no remaining LDAP consumer runs on that path.
builder.RequireThat(options.ConnectionTimeoutMs > 0,
"Ldap:ConnectionTimeoutMs must be > 0.");
}
}