3d1b86b451
Scripted condition NodeIds are opaque ScriptedAlarmIds, so splitting them as a topology path could never match an Equipment grant: a narrow equipment grant was denied while a Cluster grant passed. - OtOpcUaNodeManager binds each LIVE scripted condition instance to the EquipmentId it was materialised under (only when it hangs under that folder); dropped on remove / drop-recreate / equipment-subtree removal / rebuild; a same-id re-materialise under another equipment re-parents and re-binds. - DataPlaneAuthoritySnapshot carries EquipmentScopePaths (EquipmentId -> [uns, Area, Line, Equipment]) decoded from the artifact by the same AclScopePathResolver call that plants Equipment grants. - DataPlaneAuthorizer.AuthorizeScriptedCondition re-reads key, grant and generation per call (uncached) and evaluates at the owner path; unbound, replaced or unplaceable owners deny under every grant. No id-split or realm-root fallback. - Native conditions (RawPath) and the ACL-off role gate are unchanged. NodeIds and AlarmCommand.AlarmId are unchanged. Sdk2OpaqueScriptedScopeBaselineTests deliberately flipped: it pinned the denial and now pins owner-grant allow / sibling deny / cluster allow. Follow-up #808 (folder ids + Server-object event visibility for narrow UNS grants).
62 lines
3.5 KiB
C#
62 lines
3.5 KiB
C#
// Gitea #797: this test USED to pin the denial (an Equipment grant could not authorize an opaque scripted
|
|
// alarm id; only a Cluster grant could). The owner binding fixed that deliberately, so it now pins the fixed
|
|
// behaviour: the owning equipment's grant allows, the sibling equipment's grant denies, a cluster grant still
|
|
// allows. The SDK 1.5 baseline probe under scripts/validation/opcua-scripted-alarm-baseline records the
|
|
// pre-fix denial on the original application commit and is unaffected.
|
|
using Opc.Ua;
|
|
using Opc.Ua.Client;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
|
|
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
|
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
|
using static ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests.ServerEventRig;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests;
|
|
|
|
public sealed class Sdk2OpaqueScriptedScopeBaselineTests
|
|
{
|
|
[Fact]
|
|
public async Task Opaque_scripted_ID_is_allowed_by_its_equipment_grant_denied_by_a_sibling_grant_and_allowed_by_a_cluster_grant()
|
|
{
|
|
var ct = TestContext.Current.CancellationToken;
|
|
await using var rig = await StartAsync("BaselineScriptedScope", encrypted: false, ct);
|
|
rig.KeyRoles = [];
|
|
const NodePermissions permissions = NodePermissions.ReadOnly | NodePermissions.AlarmAcknowledge | NodePermissions.MethodCall;
|
|
NodeAcl Narrow(string equipment) => new()
|
|
{
|
|
NodeAclId = $"narrow-{equipment}", ClusterId = ServerEventRig.Cluster, Principal = KeyPrincipal,
|
|
ScopeKind = NodeAclScopeKind.Equipment, ScopeId = $"eq-{equipment}", PermissionFlags = permissions,
|
|
};
|
|
|
|
// Sibling first: a grant on EqB must not authorize EqA's alarm.
|
|
rig.Enforce(true, [Key()], Narrow(EqB));
|
|
rig.MaterialiseScripted();
|
|
rig.Nm.ScriptedAlarmOwnerOf(ScriptedAlarmId).ShouldBe(EqA);
|
|
var routed = 0;
|
|
rig.Nm.AlarmCommandRouter = _ => routed++;
|
|
using var session = await rig.ConnectAsync(anonymous: false, ct);
|
|
rig.Project(false, new AlarmConditionSnapshot(true, false, false, true, AlarmShelvingKind.Unshelved, 500, "scope"));
|
|
var condition = rig.Nm.TryGetAlarmCondition(ScriptedAlarmId, AddressSpaceRealm.Uns).ShouldNotBeNull();
|
|
condition.NodeId.IdentifierAsString.ShouldBe(ScriptedAlarmId, "the opaque NodeId is unchanged");
|
|
var eventId = condition.EventId.ShouldNotBeNull().Value;
|
|
var denied = await Should.ThrowAsync<ServiceResultException>(() => session.CallAsync(condition.NodeId,
|
|
MethodIds.AcknowledgeableConditionType_Acknowledge, ct, new Variant[] { Variant.From(eventId), Variant.From(new LocalizedText("sibling")) }));
|
|
denied.StatusCode.ShouldBe(StatusCodes.BadUserAccessDenied);
|
|
routed.ShouldBe(0);
|
|
|
|
// The owning equipment's narrow grant now authorizes the same opaque id (#797 fixed).
|
|
rig.Enforce(true, [Key()], Narrow(EqA));
|
|
await session.CallAsync(condition.NodeId, MethodIds.AcknowledgeableConditionType_Acknowledge, ct,
|
|
new Variant[] { Variant.From(eventId), Variant.From(new LocalizedText("owner")) });
|
|
routed.ShouldBe(1);
|
|
|
|
// The existing cluster contract is preserved.
|
|
rig.Enforce(true, [Key()], ClusterGrant(KeyPrincipal, "cluster-positive", permissions));
|
|
await session.CallAsync(condition.NodeId, MethodIds.AcknowledgeableConditionType_Acknowledge, ct,
|
|
new Variant[] { Variant.From(condition.EventId!.Value), Variant.From(new LocalizedText("cluster")) });
|
|
routed.ShouldBe(2);
|
|
await session.CloseAsync(ct);
|
|
}
|
|
}
|