Files
scadaproj/ZB.MOM.WW.Auth/src/ZB.MOM.WW.Auth.Ldap/LdapOptionsValidator.cs
T
Joseph Doherty aecc106657 fix(auth.ldap): skip LdapOptionsValidator when Enabled=false; bump 0.1.1
A disabled LDAP provider's connection fields are inert — don't require
Server/SearchBase/ServiceAccountDn at startup when Enabled=false. Surfaced
by the MxGateway 1.2 review (dashboard LDAP can be disabled). +1 test.
2026-06-02 01:17:53 -04:00

74 lines
3.2 KiB
C#

using Microsoft.Extensions.Options;
using ZB.MOM.WW.Auth.Abstractions.Ldap;
namespace ZB.MOM.WW.Auth.Ldap;
/// <summary>
/// Validates <see cref="LdapOptions"/> at startup so a misconfiguration fails fast at
/// boot with a clear, field-naming message — rather than surfacing later as an opaque
/// low-level error on the first real login attempt.
/// </summary>
/// <remarks>
/// Validation is skipped entirely when <see cref="LdapOptions.Enabled"/> is <c>false</c>
/// (a disabled provider's connection fields are inert). When enabled, four conditions
/// are enforced:
/// <list type="bullet">
/// <item>plaintext transport (<see cref="LdapTransport.None"/>) is rejected unless
/// <see cref="LdapOptions.AllowInsecure"/> is explicitly set (dev/test only);</item>
/// <item><see cref="LdapOptions.Server"/> must be specified (no sane default host);</item>
/// <item><see cref="LdapOptions.SearchBase"/> must be specified (the DN root every
/// search runs against);</item>
/// <item><see cref="LdapOptions.ServiceAccountDn"/> must be specified — an empty value
/// would bind anonymously, defeating the search-then-bind authentication flow.</item>
/// </list>
/// </remarks>
public sealed class LdapOptionsValidator : IValidateOptions<LdapOptions>
{
/// <inheritdoc />
public ValidateOptionsResult Validate(string? name, LdapOptions options)
{
ArgumentNullException.ThrowIfNull(options);
// When LDAP is disabled, its connection fields are inert — do not require them.
// A consumer that turns LDAP off should not have to supply a server/search-base/
// service-account just to satisfy startup validation.
if (!options.Enabled)
{
return ValidateOptionsResult.Success;
}
if (options.Transport == LdapTransport.None && !options.AllowInsecure)
{
return ValidateOptionsResult.Fail(
$"{nameof(LdapOptions.Transport)} is {nameof(LdapTransport.None)} (insecure/plaintext) " +
$"but {nameof(LdapOptions.AllowInsecure)} is false. Enable TLS " +
$"({nameof(LdapTransport.Ldaps)} or {nameof(LdapTransport.StartTls)}) " +
$"or set {nameof(LdapOptions.AllowInsecure)} for dev/test.");
}
if (string.IsNullOrWhiteSpace(options.Server))
{
return ValidateOptionsResult.Fail(
$"{nameof(LdapOptions.Server)} is required but was empty or whitespace — " +
"set it to the LDAP server hostname or IP (e.g. \"ldap.example.com\").");
}
if (string.IsNullOrWhiteSpace(options.SearchBase))
{
return ValidateOptionsResult.Fail(
$"{nameof(LdapOptions.SearchBase)} is required but was empty or whitespace — " +
"set it to the search-base DN (e.g. \"dc=example,dc=com\").");
}
if (string.IsNullOrWhiteSpace(options.ServiceAccountDn))
{
return ValidateOptionsResult.Fail(
$"{nameof(LdapOptions.ServiceAccountDn)} is required but was empty or whitespace — " +
"an empty value would bind anonymously. Set it to the service-account DN " +
"(e.g. \"cn=svc,dc=example,dc=com\").");
}
return ValidateOptionsResult.Success;
}
}