135 lines
4.6 KiB
C#
135 lines
4.6 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
using ZB.MOM.WW.OtOpcUa.Server.Security;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Server.Tests;
|
|
|
|
[Trait("Category", "Unit")]
|
|
public sealed class WriteAuthzPolicyTests
|
|
{
|
|
// --- FreeAccess and ViewOnly special-cases ---
|
|
|
|
[Fact]
|
|
public void FreeAccess_allows_write_even_for_empty_role_set()
|
|
{
|
|
WriteAuthzPolicy.IsAllowed(SecurityClassification.FreeAccess, []).ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void FreeAccess_allows_write_for_arbitrary_roles()
|
|
{
|
|
WriteAuthzPolicy.IsAllowed(SecurityClassification.FreeAccess, ["SomeOtherRole"]).ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void ViewOnly_denies_write_even_with_every_role()
|
|
{
|
|
var allRoles = new[] { "WriteOperate", "WriteTune", "WriteConfigure", "AlarmAck" };
|
|
WriteAuthzPolicy.IsAllowed(SecurityClassification.ViewOnly, allRoles).ShouldBeFalse();
|
|
}
|
|
|
|
// --- Operate tier ---
|
|
|
|
[Fact]
|
|
public void Operate_requires_WriteOperate_role()
|
|
{
|
|
WriteAuthzPolicy.IsAllowed(SecurityClassification.Operate, ["WriteOperate"]).ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void Operate_role_match_is_case_insensitive()
|
|
{
|
|
WriteAuthzPolicy.IsAllowed(SecurityClassification.Operate, ["writeoperate"]).ShouldBeTrue();
|
|
WriteAuthzPolicy.IsAllowed(SecurityClassification.Operate, ["WRITEOPERATE"]).ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void Operate_denies_empty_role_set()
|
|
{
|
|
WriteAuthzPolicy.IsAllowed(SecurityClassification.Operate, []).ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void Operate_denies_wrong_role()
|
|
{
|
|
WriteAuthzPolicy.IsAllowed(SecurityClassification.Operate, ["ReadOnly"]).ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void SecuredWrite_maps_to_same_WriteOperate_requirement_as_Operate()
|
|
{
|
|
WriteAuthzPolicy.IsAllowed(SecurityClassification.SecuredWrite, ["WriteOperate"]).ShouldBeTrue();
|
|
WriteAuthzPolicy.IsAllowed(SecurityClassification.SecuredWrite, ["WriteTune"]).ShouldBeFalse();
|
|
}
|
|
|
|
// --- Tune tier ---
|
|
|
|
[Fact]
|
|
public void Tune_requires_WriteTune_role()
|
|
{
|
|
WriteAuthzPolicy.IsAllowed(SecurityClassification.Tune, ["WriteTune"]).ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void Tune_denies_WriteOperate_only_session()
|
|
{
|
|
// Important: role roles do NOT cascade — a session with WriteOperate can't write a Tune
|
|
// attribute. Operators escalate by adding WriteTune to the session's roles, not by a
|
|
// hierarchy the policy infers on its own.
|
|
WriteAuthzPolicy.IsAllowed(SecurityClassification.Tune, ["WriteOperate"]).ShouldBeFalse();
|
|
}
|
|
|
|
// --- Configure tier ---
|
|
|
|
[Fact]
|
|
public void Configure_requires_WriteConfigure_role()
|
|
{
|
|
WriteAuthzPolicy.IsAllowed(SecurityClassification.Configure, ["WriteConfigure"]).ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void VerifiedWrite_maps_to_same_WriteConfigure_requirement_as_Configure()
|
|
{
|
|
WriteAuthzPolicy.IsAllowed(SecurityClassification.VerifiedWrite, ["WriteConfigure"]).ShouldBeTrue();
|
|
WriteAuthzPolicy.IsAllowed(SecurityClassification.VerifiedWrite, ["WriteOperate"]).ShouldBeFalse();
|
|
}
|
|
|
|
// --- Multi-role sessions ---
|
|
|
|
[Fact]
|
|
public void Session_with_multiple_roles_is_allowed_when_any_matches()
|
|
{
|
|
var roles = new[] { "ReadOnly", "WriteTune", "AlarmAck" };
|
|
WriteAuthzPolicy.IsAllowed(SecurityClassification.Tune, roles).ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void Session_with_only_unrelated_roles_is_denied()
|
|
{
|
|
var roles = new[] { "ReadOnly", "AlarmAck", "SomeCustomRole" };
|
|
WriteAuthzPolicy.IsAllowed(SecurityClassification.Configure, roles).ShouldBeFalse();
|
|
}
|
|
|
|
// --- Mapping table ---
|
|
|
|
[Theory]
|
|
[InlineData(SecurityClassification.Operate, WriteAuthzPolicy.RoleWriteOperate)]
|
|
[InlineData(SecurityClassification.SecuredWrite, WriteAuthzPolicy.RoleWriteOperate)]
|
|
[InlineData(SecurityClassification.Tune, WriteAuthzPolicy.RoleWriteTune)]
|
|
[InlineData(SecurityClassification.VerifiedWrite, WriteAuthzPolicy.RoleWriteConfigure)]
|
|
[InlineData(SecurityClassification.Configure, WriteAuthzPolicy.RoleWriteConfigure)]
|
|
public void RequiredRole_returns_expected_role_for_classification(SecurityClassification c, string expected)
|
|
{
|
|
WriteAuthzPolicy.RequiredRole(c).ShouldBe(expected);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(SecurityClassification.FreeAccess)]
|
|
[InlineData(SecurityClassification.ViewOnly)]
|
|
public void RequiredRole_returns_null_for_special_classifications(SecurityClassification c)
|
|
{
|
|
WriteAuthzPolicy.RequiredRole(c).ShouldBeNull();
|
|
}
|
|
}
|