Files
lmxopcua/tests/ZB.MOM.WW.LmxOpcUa.Tests/Authentication/UserAuthenticationTests.cs
Joseph Doherty 74107ea95e Add LDAP authentication with role-based OPC UA permissions
Replace static user list with GLAuth LDAP authentication. Group
membership (ReadOnly, ReadWrite, AlarmAck) maps to granular OPC UA
permissions for write and alarm-ack operations. Anonymous can still
browse and read but not write.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 18:57:30 -04:00

294 lines
9.6 KiB
C#

using System.Collections.Generic;
using Shouldly;
using Xunit;
using ZB.MOM.WW.LmxOpcUa.Host.Configuration;
using ZB.MOM.WW.LmxOpcUa.Host.Domain;
namespace ZB.MOM.WW.LmxOpcUa.Tests.Authentication
{
public class UserAuthenticationTests
{
[Fact]
public void ValidCredentials_ReturnsTrue()
{
var provider = new ConfigUserAuthenticationProvider(new List<UserCredential>
{
new UserCredential { Username = "operator", Password = "op123" }
});
provider.ValidateCredentials("operator", "op123").ShouldBeTrue();
}
[Fact]
public void WrongPassword_ReturnsFalse()
{
var provider = new ConfigUserAuthenticationProvider(new List<UserCredential>
{
new UserCredential { Username = "operator", Password = "op123" }
});
provider.ValidateCredentials("operator", "wrong").ShouldBeFalse();
}
[Fact]
public void UnknownUsername_ReturnsFalse()
{
var provider = new ConfigUserAuthenticationProvider(new List<UserCredential>
{
new UserCredential { Username = "operator", Password = "op123" }
});
provider.ValidateCredentials("unknown", "op123").ShouldBeFalse();
}
[Fact]
public void Username_IsCaseInsensitive()
{
var provider = new ConfigUserAuthenticationProvider(new List<UserCredential>
{
new UserCredential { Username = "Operator", Password = "op123" }
});
provider.ValidateCredentials("operator", "op123").ShouldBeTrue();
provider.ValidateCredentials("OPERATOR", "op123").ShouldBeTrue();
}
[Fact]
public void EmptyUserList_RejectsAll()
{
var provider = new ConfigUserAuthenticationProvider(new List<UserCredential>());
provider.ValidateCredentials("anyone", "anything").ShouldBeFalse();
}
[Fact]
public void AuthenticationConfiguration_Defaults()
{
var config = new AuthenticationConfiguration();
config.AllowAnonymous.ShouldBeTrue();
config.AnonymousCanWrite.ShouldBeTrue();
config.Users.ShouldBeEmpty();
}
[Fact]
public void AuthenticationConfiguration_LdapDefaults()
{
var config = new AuthenticationConfiguration();
config.Ldap.ShouldNotBeNull();
config.Ldap.Enabled.ShouldBeFalse();
config.Ldap.Host.ShouldBe("localhost");
config.Ldap.Port.ShouldBe(3893);
config.Ldap.BaseDN.ShouldBe("dc=lmxopcua,dc=local");
config.Ldap.ReadOnlyGroup.ShouldBe("ReadOnly");
config.Ldap.ReadWriteGroup.ShouldBe("ReadWrite");
config.Ldap.AlarmAckGroup.ShouldBe("AlarmAck");
config.Ldap.TimeoutSeconds.ShouldBe(5);
}
[Fact]
public void LdapConfiguration_BindDnTemplate_Default()
{
var config = new LdapConfiguration();
config.BindDnTemplate.ShouldBe("cn={username},dc=lmxopcua,dc=local");
}
[Fact]
public void LdapAuthenticationProvider_ValidBind_ReturnsTrue()
{
// This test requires GLAuth running on localhost:3893
// Skip if not available
var ldapConfig = CreateGlAuthConfig();
var provider = new LdapAuthenticationProvider(ldapConfig);
try
{
provider.ValidateCredentials("readonly", "readonly123").ShouldBeTrue();
}
catch (System.Exception)
{
// GLAuth not running - skip gracefully
return;
}
}
[Fact]
public void LdapAuthenticationProvider_InvalidPassword_ReturnsFalse()
{
var ldapConfig = CreateGlAuthConfig();
var provider = new LdapAuthenticationProvider(ldapConfig);
try
{
provider.ValidateCredentials("readonly", "wrongpassword").ShouldBeFalse();
}
catch (System.Exception)
{
return; // GLAuth not running
}
}
[Fact]
public void LdapAuthenticationProvider_UnknownUser_ReturnsFalse()
{
var ldapConfig = CreateGlAuthConfig();
var provider = new LdapAuthenticationProvider(ldapConfig);
try
{
provider.ValidateCredentials("nonexistent", "anything").ShouldBeFalse();
}
catch (System.Exception)
{
return; // GLAuth not running
}
}
[Fact]
public void LdapAuthenticationProvider_ReadOnlyUser_HasReadOnlyRole()
{
var ldapConfig = CreateGlAuthConfig();
var provider = new LdapAuthenticationProvider(ldapConfig);
try
{
provider.ValidateCredentials("readonly", "readonly123").ShouldBeTrue();
var roles = provider.GetUserRoles("readonly");
roles.ShouldContain("ReadOnly");
roles.ShouldNotContain("ReadWrite");
roles.ShouldNotContain("AlarmAck");
}
catch (System.Exception)
{
return; // GLAuth not running
}
}
[Fact]
public void LdapAuthenticationProvider_ReadWriteUser_HasReadWriteRole()
{
var ldapConfig = CreateGlAuthConfig();
var provider = new LdapAuthenticationProvider(ldapConfig);
try
{
provider.ValidateCredentials("readwrite", "readwrite123").ShouldBeTrue();
var roles = provider.GetUserRoles("readwrite");
roles.ShouldContain("ReadWrite");
roles.ShouldNotContain("AlarmAck");
}
catch (System.Exception)
{
return; // GLAuth not running
}
}
[Fact]
public void LdapAuthenticationProvider_AlarmAckUser_HasAlarmAckRole()
{
var ldapConfig = CreateGlAuthConfig();
var provider = new LdapAuthenticationProvider(ldapConfig);
try
{
provider.ValidateCredentials("alarmack", "alarmack123").ShouldBeTrue();
var roles = provider.GetUserRoles("alarmack");
roles.ShouldContain("AlarmAck");
roles.ShouldNotContain("ReadWrite");
}
catch (System.Exception)
{
return; // GLAuth not running
}
}
[Fact]
public void LdapAuthenticationProvider_AdminUser_HasAllRoles()
{
var ldapConfig = CreateGlAuthConfig();
var provider = new LdapAuthenticationProvider(ldapConfig);
try
{
provider.ValidateCredentials("admin", "admin123").ShouldBeTrue();
var roles = provider.GetUserRoles("admin");
roles.ShouldContain("ReadOnly");
roles.ShouldContain("ReadWrite");
roles.ShouldContain("AlarmAck");
}
catch (System.Exception)
{
return; // GLAuth not running
}
}
[Fact]
public void LdapAuthenticationProvider_ImplementsIRoleProvider()
{
var ldapConfig = CreateGlAuthConfig();
var provider = new LdapAuthenticationProvider(ldapConfig);
(provider is IRoleProvider).ShouldBeTrue();
}
[Fact]
public void ConfigUserAuthenticationProvider_DoesNotImplementIRoleProvider()
{
var provider = new ConfigUserAuthenticationProvider(new List<UserCredential>());
(provider is IRoleProvider).ShouldBeFalse();
}
[Fact]
public void LdapAuthenticationProvider_ConnectionFailure_ReturnsFalse()
{
var ldapConfig = new LdapConfiguration
{
Enabled = true,
Host = "localhost",
Port = 19999, // no server here
TimeoutSeconds = 1
};
var provider = new LdapAuthenticationProvider(ldapConfig);
provider.ValidateCredentials("anyone", "anything").ShouldBeFalse();
}
[Fact]
public void LdapAuthenticationProvider_ConnectionFailure_GetUserRoles_FallsBackToReadOnly()
{
var ldapConfig = new LdapConfiguration
{
Enabled = true,
Host = "localhost",
Port = 19999, // no server here
TimeoutSeconds = 1,
ServiceAccountDn = "cn=svc,dc=test",
ServiceAccountPassword = "test"
};
var provider = new LdapAuthenticationProvider(ldapConfig);
var roles = provider.GetUserRoles("anyone");
roles.ShouldContain("ReadOnly");
}
private static LdapConfiguration CreateGlAuthConfig()
{
return new LdapConfiguration
{
Enabled = true,
Host = "localhost",
Port = 3893,
BaseDN = "dc=lmxopcua,dc=local",
BindDnTemplate = "cn={username},dc=lmxopcua,dc=local",
ServiceAccountDn = "cn=serviceaccount,dc=lmxopcua,dc=local",
ServiceAccountPassword = "serviceaccount123",
TimeoutSeconds = 5,
ReadOnlyGroup = "ReadOnly",
ReadWriteGroup = "ReadWrite",
AlarmAckGroup = "AlarmAck"
};
}
}
}