fix: tighten MxGateway Ldap:Port to 1-65535; catch IOException in path validation

Defect 1: ValidateLdap used AddIfNotPositive for Port, accepting any value
> 0 including 70000. Replaced with builder.Port() from the shared
ZB.MOM.WW.Configuration library, which enforces the 1-65535 TCP range and
emits "MxGateway:Ldap:Port must be between 1 and 65535 (was {value})".

Defect 2: AddIfInvalidPath only caught ArgumentException, NotSupportedException,
and PathTooLongException from Path.GetFullPath. On macOS/Linux a path containing
an embedded null throws IOException, which escaped the catch block and caused
Validate() to throw instead of returning a failure. Added catch (IOException).

Tests: added Validate_Fails_WhenLdapPortIsZero, Validate_Fails_WhenLdapPortExceedsMaximum,
and Validate_Succeeds_WhenLdapEnabledWithValidPort to cover the new range boundary.
This commit is contained in:
Joseph Doherty
2026-06-01 22:45:16 -04:00
parent 459a88b3e7
commit 3ca2799c90
2 changed files with 58 additions and 1 deletions
@@ -80,7 +80,7 @@ public sealed class GatewayOptionsValidator : OptionsValidatorBase<GatewayOption
options.GroupAttribute,
"MxGateway:Ldap:GroupAttribute is required when LDAP login is enabled.",
builder);
AddIfNotPositive(options.Port, "MxGateway:Ldap:Port must be greater than zero.", builder);
builder.Port(options.Port, "MxGateway:Ldap:Port");
if (!options.UseTls && !options.AllowInsecureLdap)
{
@@ -339,5 +339,9 @@ public sealed class GatewayOptionsValidator : OptionsValidatorBase<GatewayOption
{
builder.Add(message);
}
catch (IOException)
{
builder.Add(message);
}
}
}
@@ -9,6 +9,29 @@ public sealed class GatewayOptionsValidatorTests
// design-default values; those defaults are validated separately in GatewayOptionsTests.
private static GatewayOptions ValidOptions() => new();
// Returns enabled LDAP options that pass all checks except Port.
// The class defaults already satisfy the blank-field checks; we only
// override Enabled (must be true to exercise the port check) and Port.
private static LdapOptions LdapOptionsWithPort(int port) => new()
{
Enabled = true,
Port = port,
};
private static GatewayOptions CloneWithLdap(GatewayOptions source, LdapOptions ldap)
=> new()
{
Authentication = source.Authentication,
Ldap = ldap,
Worker = source.Worker,
Sessions = source.Sessions,
Events = source.Events,
Dashboard = source.Dashboard,
Protocol = source.Protocol,
Alarms = source.Alarms,
Tls = source.Tls,
};
private static GatewayOptions CloneWithTls(GatewayOptions source, TlsOptions tls)
=> new()
{
@@ -65,4 +88,34 @@ public sealed class GatewayOptionsValidatorTests
Assert.True(result.Failed);
Assert.Contains(result.Failures!, f => f.Contains("MxGateway:Tls:SelfSignedCertPath must not be blank."));
}
[Fact]
public void Validate_Fails_WhenLdapPortIsZero()
{
GatewayOptions options = CloneWithLdap(ValidOptions(), LdapOptionsWithPort(0));
ValidateOptionsResult result = new GatewayOptionsValidator().Validate(null, options);
Assert.True(result.Failed);
Assert.Contains(
result.Failures!,
f => f.Contains("MxGateway:Ldap:Port must be between 1 and 65535 (was 0)"));
}
[Fact]
public void Validate_Fails_WhenLdapPortExceedsMaximum()
{
GatewayOptions options = CloneWithLdap(ValidOptions(), LdapOptionsWithPort(70000));
ValidateOptionsResult result = new GatewayOptionsValidator().Validate(null, options);
Assert.True(result.Failed);
Assert.Contains(
result.Failures!,
f => f.Contains("MxGateway:Ldap:Port must be between 1 and 65535 (was 70000)"));
}
[Fact]
public void Validate_Succeeds_WhenLdapEnabledWithValidPort()
{
GatewayOptions options = CloneWithLdap(ValidOptions(), LdapOptionsWithPort(389));
ValidateOptionsResult result = new GatewayOptionsValidator().Validate(null, options);
Assert.True(result.Succeeded);
}
}