Files
lmxopcua/tests/ZB.MOM.WW.LmxOpcUa.Tests/Authentication/UserAuthenticationTests.cs
Joseph Doherty 50b85d41bd Consolidate LDAP roles into OPC UA session roles with granular write permissions
Map LDAP groups to custom OPC UA role NodeIds on RoleBasedIdentity.GrantedRoleIds
during authentication, replacing the username-to-role side cache. Split ReadWrite
into WriteOperate/WriteTune/WriteConfigure so write access is gated per Galaxy
security classification. AnonymousCanWrite now behaves consistently regardless
of LDAP state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 01:50:16 -04:00

238 lines
7.9 KiB
C#

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 AuthenticationConfiguration_Defaults()
{
var config = new AuthenticationConfiguration();
config.AllowAnonymous.ShouldBeTrue();
config.AnonymousCanWrite.ShouldBeTrue();
}
[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.WriteOperateGroup.ShouldBe("WriteOperate");
config.Ldap.WriteTuneGroup.ShouldBe("WriteTune");
config.Ldap.WriteConfigureGroup.ShouldBe("WriteConfigure");
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("WriteOperate");
roles.ShouldNotContain("AlarmAck");
}
catch (System.Exception)
{
return; // GLAuth not running
}
}
[Fact]
public void LdapAuthenticationProvider_WriteOperateUser_HasWriteOperateRole()
{
var ldapConfig = CreateGlAuthConfig();
var provider = new LdapAuthenticationProvider(ldapConfig);
try
{
provider.ValidateCredentials("writeop", "writeop123").ShouldBeTrue();
var roles = provider.GetUserRoles("writeop");
roles.ShouldContain("WriteOperate");
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("WriteOperate");
}
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("WriteOperate");
roles.ShouldContain("WriteTune");
roles.ShouldContain("WriteConfigure");
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 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",
WriteOperateGroup = "WriteOperate",
WriteTuneGroup = "WriteTune",
WriteConfigureGroup = "WriteConfigure",
AlarmAckGroup = "AlarmAck"
};
}
}
}