Files
lmxopcua/tests/ZB.MOM.WW.LmxOpcUa.Tests/Integration/PermissionEnforcementTests.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

189 lines
7.6 KiB
C#

using System.Threading.Tasks;
using Opc.Ua;
using Shouldly;
using Xunit;
using ZB.MOM.WW.LmxOpcUa.Host.Configuration;
using ZB.MOM.WW.LmxOpcUa.Host.Domain;
using ZB.MOM.WW.LmxOpcUa.Tests.Helpers;
namespace ZB.MOM.WW.LmxOpcUa.Tests.Integration
{
public class PermissionEnforcementTests
{
private static FakeAuthenticationProvider CreateTestAuthProvider()
{
return new FakeAuthenticationProvider()
.AddUser("readonly", "readonly123", AppRoles.ReadOnly)
.AddUser("writeop", "writeop123", AppRoles.WriteOperate)
.AddUser("writetune", "writetune123", AppRoles.WriteTune)
.AddUser("writeconfig", "writeconfig123", AppRoles.WriteConfigure)
.AddUser("alarmack", "alarmack123", AppRoles.AlarmAck)
.AddUser("admin", "admin123", AppRoles.ReadOnly, AppRoles.WriteOperate, AppRoles.WriteTune, AppRoles.WriteConfigure, AppRoles.AlarmAck);
}
private static AuthenticationConfiguration CreateAuthConfig(bool anonymousCanWrite = false)
{
return new AuthenticationConfiguration
{
AllowAnonymous = true,
AnonymousCanWrite = anonymousCanWrite
};
}
[Fact]
public async Task AnonymousRead_Allowed()
{
var mxClient = new FakeMxAccessClient();
mxClient.TagValues["TestMachine_001.MachineID"] = Vtq.Good("hello");
var fixture = OpcUaServerFixture.WithFakeMxAccessClient(
mxClient: mxClient,
authConfig: CreateAuthConfig(),
authProvider: CreateTestAuthProvider());
await fixture.InitializeAsync();
try
{
using var client = new OpcUaTestClient();
await client.ConnectAsync(fixture.EndpointUrl);
var result = client.Read(client.MakeNodeId("TestMachine_001.MachineID"));
result.StatusCode.ShouldNotBe(StatusCodes.BadUserAccessDenied);
}
finally { await fixture.DisposeAsync(); }
}
[Fact]
public async Task AnonymousWrite_Denied_WhenAnonymousCanWriteFalse()
{
var fixture = OpcUaServerFixture.WithFakeMxAccessClient(
authConfig: CreateAuthConfig(anonymousCanWrite: false),
authProvider: CreateTestAuthProvider());
await fixture.InitializeAsync();
try
{
using var client = new OpcUaTestClient();
await client.ConnectAsync(fixture.EndpointUrl);
var status = client.Write(client.MakeNodeId("TestMachine_001.MachineID"), "test");
status.Code.ShouldBe(StatusCodes.BadUserAccessDenied);
}
finally { await fixture.DisposeAsync(); }
}
[Fact]
public async Task AnonymousWrite_Allowed_WhenAnonymousCanWriteTrue()
{
var mxClient = new FakeMxAccessClient();
mxClient.TagValues["TestMachine_001.MachineID"] = Vtq.Good("initial");
var fixture = OpcUaServerFixture.WithFakeMxAccessClient(
mxClient: mxClient,
authConfig: CreateAuthConfig(anonymousCanWrite: true),
authProvider: CreateTestAuthProvider());
await fixture.InitializeAsync();
try
{
using var client = new OpcUaTestClient();
await client.ConnectAsync(fixture.EndpointUrl);
var status = client.Write(client.MakeNodeId("TestMachine_001.MachineID"), "test");
status.Code.ShouldNotBe(StatusCodes.BadUserAccessDenied);
}
finally { await fixture.DisposeAsync(); }
}
[Fact]
public async Task ReadOnlyUser_Write_Denied()
{
var fixture = OpcUaServerFixture.WithFakeMxAccessClient(
authConfig: CreateAuthConfig(),
authProvider: CreateTestAuthProvider());
await fixture.InitializeAsync();
try
{
using var client = new OpcUaTestClient();
await client.ConnectAsync(fixture.EndpointUrl, username: "readonly", password: "readonly123");
var status = client.Write(client.MakeNodeId("TestMachine_001.MachineID"), "test");
status.Code.ShouldBe(StatusCodes.BadUserAccessDenied);
}
finally { await fixture.DisposeAsync(); }
}
[Fact]
public async Task WriteOperateUser_Write_Allowed()
{
var mxClient = new FakeMxAccessClient();
mxClient.TagValues["TestMachine_001.MachineID"] = Vtq.Good("initial");
var fixture = OpcUaServerFixture.WithFakeMxAccessClient(
mxClient: mxClient,
authConfig: CreateAuthConfig(),
authProvider: CreateTestAuthProvider());
await fixture.InitializeAsync();
try
{
using var client = new OpcUaTestClient();
await client.ConnectAsync(fixture.EndpointUrl, username: "writeop", password: "writeop123");
var status = client.Write(client.MakeNodeId("TestMachine_001.MachineID"), "test");
status.Code.ShouldNotBe(StatusCodes.BadUserAccessDenied);
}
finally { await fixture.DisposeAsync(); }
}
[Fact]
public async Task AlarmAckOnlyUser_Write_Denied()
{
var fixture = OpcUaServerFixture.WithFakeMxAccessClient(
authConfig: CreateAuthConfig(),
authProvider: CreateTestAuthProvider());
await fixture.InitializeAsync();
try
{
using var client = new OpcUaTestClient();
await client.ConnectAsync(fixture.EndpointUrl, username: "alarmack", password: "alarmack123");
var status = client.Write(client.MakeNodeId("TestMachine_001.MachineID"), "test");
status.Code.ShouldBe(StatusCodes.BadUserAccessDenied);
}
finally { await fixture.DisposeAsync(); }
}
[Fact]
public async Task AdminUser_Write_Allowed()
{
var mxClient = new FakeMxAccessClient();
mxClient.TagValues["TestMachine_001.MachineID"] = Vtq.Good("initial");
var fixture = OpcUaServerFixture.WithFakeMxAccessClient(
mxClient: mxClient,
authConfig: CreateAuthConfig(),
authProvider: CreateTestAuthProvider());
await fixture.InitializeAsync();
try
{
using var client = new OpcUaTestClient();
await client.ConnectAsync(fixture.EndpointUrl, username: "admin", password: "admin123");
var status = client.Write(client.MakeNodeId("TestMachine_001.MachineID"), "test");
status.Code.ShouldNotBe(StatusCodes.BadUserAccessDenied);
}
finally { await fixture.DisposeAsync(); }
}
[Fact]
public async Task InvalidPassword_ConnectionRejected()
{
var fixture = OpcUaServerFixture.WithFakeMxAccessClient(
authConfig: CreateAuthConfig(),
authProvider: CreateTestAuthProvider());
await fixture.InitializeAsync();
try
{
using var client = new OpcUaTestClient();
await Should.ThrowAsync<ServiceResultException>(async () =>
await client.ConnectAsync(fixture.EndpointUrl, username: "readonly", password: "wrongpassword"));
}
finally { await fixture.DisposeAsync(); }
}
}
}