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(); } }