feat(security): LdapAuthResult.IsSystemFailure distinguishes directory outage from credential deny (03/S2)
This commit is contained in:
@@ -121,6 +121,56 @@ public sealed class OtOpcUaLdapAuthServiceTests
|
||||
inner.Called.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>A system-side library failure (<c>ServiceAccountBindFailed</c> — the directory-unreachable
|
||||
/// bucket) adapts to <c>IsSystemFailure == true</c> so the OPC UA authenticator's outage circuit can
|
||||
/// distinguish it from a user-credential deny (03/S2).</summary>
|
||||
[Fact]
|
||||
public async Task ServiceAccountBindFailed_adapts_to_IsSystemFailure_true()
|
||||
{
|
||||
var inner = new RecordingLibService(LibLdapAuthResult.Fail(LdapAuthFailure.ServiceAccountBindFailed));
|
||||
var sut = Build(
|
||||
new LdapOptions { Enabled = true, DevStubMode = false, Transport = LdapTransport.Ldaps },
|
||||
inner);
|
||||
|
||||
var result = await sut.AuthenticateAsync("alice", "secret", CancellationToken.None);
|
||||
|
||||
result.Success.ShouldBeFalse();
|
||||
result.IsSystemFailure.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>User-side library failures and success adapt to <c>IsSystemFailure == false</c> — they must
|
||||
/// NOT trip the outage circuit (03/S2).</summary>
|
||||
[Theory]
|
||||
[InlineData(LdapAuthFailure.BadCredentials)]
|
||||
[InlineData(LdapAuthFailure.UserNotFound)]
|
||||
public async Task User_side_failures_adapt_to_IsSystemFailure_false(LdapAuthFailure failure)
|
||||
{
|
||||
var inner = new RecordingLibService(LibLdapAuthResult.Fail(failure));
|
||||
var sut = Build(
|
||||
new LdapOptions { Enabled = true, DevStubMode = false, Transport = LdapTransport.Ldaps },
|
||||
inner);
|
||||
|
||||
var result = await sut.AuthenticateAsync("alice", "wrong", CancellationToken.None);
|
||||
|
||||
result.Success.ShouldBeFalse();
|
||||
result.IsSystemFailure.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>A successful bind is never a system failure.</summary>
|
||||
[Fact]
|
||||
public async Task Success_is_not_a_system_failure()
|
||||
{
|
||||
var inner = new RecordingLibService(LibLdapAuthResult.Success("alice", "Alice", new[] { "ReadOnly" }));
|
||||
var sut = Build(
|
||||
new LdapOptions { Enabled = true, DevStubMode = false, Transport = LdapTransport.Ldaps },
|
||||
inner);
|
||||
|
||||
var result = await sut.AuthenticateAsync("alice", "secret", CancellationToken.None);
|
||||
|
||||
result.Success.ShouldBeTrue();
|
||||
result.IsSystemFailure.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Records whether the library service was invoked and returns a canned result.</summary>
|
||||
private sealed class RecordingLibService(LibLdapAuthResult result) : LibILdapAuthService
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user