Files
scadaproj/ZB.MOM.WW.Auth/tests/ZB.MOM.WW.Auth.Ldap.Tests/LdapOptionsValidatorTests.cs
T
2026-10-01 00:47:04 -04:00

154 lines
5.1 KiB
C#

using ZB.MOM.WW.Auth.Abstractions.Ldap;
using ZB.MOM.WW.Auth.Ldap;
namespace ZB.MOM.WW.Auth.Ldap.Tests;
public class LdapOptionsValidatorTests
{
private static LdapOptions Opts() => new()
{
Enabled = true,
Server = "x",
Transport = LdapTransport.None,
AllowInsecure = true,
SearchBase = "dc=x",
ServiceAccountDn = "cn=svc,dc=x",
};
[Fact]
public void Validator_Fails_PlainTransport_WhenNotAllowInsecure() =>
Assert.True(new LdapOptionsValidator()
.Validate(null, Opts() with { Transport = LdapTransport.None, AllowInsecure = false })
.Failed);
[Fact]
public void Validator_Fails_WhenServerEmpty() =>
Assert.True(new LdapOptionsValidator()
.Validate(null, Opts() with { Server = " " })
.Failed);
[Fact]
public void Validator_Fails_WhenSearchBaseEmpty() =>
Assert.True(new LdapOptionsValidator()
.Validate(null, Opts() with { SearchBase = "" })
.Failed);
[Fact]
public void Validator_FailureMessage_NamesOffendingField()
{
var result = new LdapOptionsValidator()
.Validate(null, Opts() with { Server = "" });
Assert.True(result.Failed);
Assert.Contains(nameof(LdapOptions.Server), result.FailureMessage);
}
[Fact]
public void Validator_Fails_WhenServiceAccountDnEmpty()
{
// I5: an empty ServiceAccountDn risks an anonymous bind, so it must be rejected
// and the failure message must name the offending key.
var result = new LdapOptionsValidator()
.Validate(null, Opts() with { ServiceAccountDn = " " });
Assert.True(result.Failed);
Assert.Contains(nameof(LdapOptions.ServiceAccountDn), result.FailureMessage);
}
[Fact]
public void Validator_Succeeds_OnValidSecureConfig() =>
Assert.False(new LdapOptionsValidator()
.Validate(null, Opts() with
{
Transport = LdapTransport.Ldaps,
AllowInsecure = false,
Server = "s",
SearchBase = "dc=x",
})
.Failed);
[Fact]
public void Validator_Succeeds_OnInsecureWhenAllowed() =>
Assert.False(new LdapOptionsValidator()
.Validate(null, Opts())
.Failed);
[Fact]
public void Validator_Skips_AllChecks_WhenDisabled() =>
// When LDAP is disabled its connection fields are inert; an otherwise-invalid
// config (plaintext + blank Server/SearchBase/ServiceAccountDn) must still pass.
Assert.False(new LdapOptionsValidator()
.Validate(null, new LdapOptions
{
Enabled = false,
Transport = LdapTransport.None,
AllowInsecure = false,
Server = "",
SearchBase = "",
ServiceAccountDn = "",
})
.Failed);
[Fact]
public void Validator_Succeeds_WithValidFallbackServers() =>
Assert.False(new LdapOptionsValidator()
.Validate(null, Opts() with { FallbackServers = new[] { "dc2", "dc3:3894" } })
.Failed);
[Theory]
[InlineData("")]
[InlineData(":636")]
[InlineData("dc2:0")]
[InlineData("dc2:notaport")]
public void Validator_Fails_OnMalformedFallbackServer(string entry)
{
var result = new LdapOptionsValidator()
.Validate(null, Opts() with { FallbackServers = new[] { entry } });
Assert.True(result.Failed);
Assert.Contains(nameof(LdapOptions.FallbackServers), result.FailureMessage);
}
[Fact]
public void Validator_Skips_FallbackServers_WhenDisabled() =>
// Same inert-when-disabled rule as every other connection field: a disabled
// provider never opens a connection, so its fallback list is never walked.
Assert.False(new LdapOptionsValidator()
.Validate(null, Opts() with { Enabled = false, FallbackServers = new[] { ":::" } })
.Failed);
private static LdapOptions UserOpts() => Opts() with
{
BindMode = LdapBindMode.User,
UserBindTemplate = "{0}@corp.test",
ServiceAccountDn = "",
ServiceAccountPassword = "",
};
[Fact]
public void Defaults_KeepServiceAccountMode()
{
var o = new LdapOptions();
Assert.Equal(LdapBindMode.ServiceAccount, o.BindMode);
Assert.Equal("", o.UserBindTemplate);
Assert.True(o.AcceptDomainQualifiedNames);
}
[Fact]
public void Validator_UserMode_DoesNotRequireServiceAccount() =>
Assert.True(new LdapOptionsValidator().Validate(null, UserOpts()).Succeeded);
[Theory]
[InlineData("")]
[InlineData(" ")]
[InlineData("jdoe@corp.test")]
[InlineData("{0}{0}@corp.test")]
public void Validator_UserMode_RequiresExactlyOnePlaceholder(string template)
{
var result = new LdapOptionsValidator().Validate(null, UserOpts() with { UserBindTemplate = template });
Assert.True(result.Failed);
Assert.Contains(nameof(LdapOptions.UserBindTemplate), result.FailureMessage, StringComparison.Ordinal);
}
}