fix(security): resolve Security-009,010,011 — LDAP connection timeout, design-doc correction, security-path test coverage; Security-008 deferred

This commit is contained in:
Joseph Doherty
2026-05-16 22:24:03 -04:00
parent a9bd017c88
commit 84a696b0e4
5 changed files with 160 additions and 10 deletions

View File

@@ -672,6 +672,81 @@ public class SecurityReviewRegressionTests2
#endregion
#region Code Review Regression Tests Security-009/011
/// <summary>
/// Regression tests for Security-009 (no LDAP connection timeout — a hung server can
/// pin a thread-pool thread indefinitely because <c>ct</c> only guards work-item
/// scheduling) and the remaining Security-011 coverage gaps.
/// </summary>
public class SecurityReviewRegressionTests3
{
// --- Security-009: LDAP connection timeout must be configurable and bounded ---
[Fact]
public void SecurityOptions_LdapConnectionTimeout_HasSaneDefault()
{
var options = new SecurityOptions();
// A positive, finite default so a hung LDAP server cannot pin a thread forever.
Assert.True(options.LdapConnectionTimeoutMs > 0);
Assert.True(options.LdapConnectionTimeoutMs <= 60_000,
"Default LDAP connection timeout should be bounded (<= 60s).");
}
[Fact]
public async Task AuthenticateAsync_UnreachableHost_FailsWithinConfiguredTimeout()
{
// A routable-but-non-responsive address would otherwise hang for the OS default
// (often minutes). With LdapConnectionTimeoutMs applied to the LdapConnection the
// call must give up promptly. 198.51.100.0/24 (TEST-NET-2, RFC 5737) is reserved
// and not routed, so the connect attempt stalls until the timeout fires.
var options = new SecurityOptions
{
LdapServer = "198.51.100.1",
LdapPort = 389,
LdapTransport = LdapTransport.None,
AllowInsecureLdap = true,
LdapSearchBase = "dc=example,dc=com",
LdapConnectionTimeoutMs = 2_000
};
var service = new LdapAuthService(Options.Create(options), NullLogger<LdapAuthService>.Instance);
var sw = System.Diagnostics.Stopwatch.StartNew();
var result = await service.AuthenticateAsync("user", "password");
sw.Stop();
Assert.False(result.Success);
// Generous ceiling: the 2s timeout plus scheduling/CI overhead, far below the
// multi-minute OS default that an unconfigured connection would incur.
Assert.True(sw.Elapsed < TimeSpan.FromSeconds(30),
$"Authentication did not honour the LDAP connection timeout: took {sw.Elapsed}.");
}
// --- Security-011: additional coverage for the no-service-account / DN paths ---
[Fact]
public void BuildFallbackUserDn_NoSearchBase_ReturnsBareRdn()
{
var dn = LdapAuthService.BuildFallbackUserDn("alice", "", "uid");
Assert.Equal("uid=alice", dn);
}
[Fact]
public void EscapeLdapDn_LeadingHash_IsEscaped()
{
Assert.Equal(@"\#admin", LdapAuthService.EscapeLdapDn("#admin"));
}
[Fact]
public void EscapeLdapDn_NullOrEmpty_ReturnedUnchanged()
{
Assert.Equal("", LdapAuthService.EscapeLdapDn(""));
Assert.Null(LdapAuthService.EscapeLdapDn(null!));
}
}
#endregion
#region WP-9: Authorization Policy Tests
public class AuthorizationPolicyTests