fix(authz): give HistoryUpdate its own NodePermissions bit (was aliased to HistoryRead) [H2]
This commit is contained in:
@@ -29,6 +29,11 @@ public enum NodePermissions : int
|
|||||||
// OPC UA Part 4 §5.11
|
// OPC UA Part 4 §5.11
|
||||||
MethodCall = 1 << 11,
|
MethodCall = 1 << 11,
|
||||||
|
|
||||||
|
// OPC UA HistoryUpdate (annotation / insert / delete) — separate from HistoryRead so a
|
||||||
|
// read-only grant cannot authorize historian writes. Not included in any composite bundle
|
||||||
|
// until the HistoryUpdate service surface is implemented.
|
||||||
|
HistoryUpdate = 1 << 12,
|
||||||
|
|
||||||
// Bundles (one-click grants in Admin UI)
|
// Bundles (one-click grants in Admin UI)
|
||||||
ReadOnly = Browse | Read | Subscribe | HistoryRead | AlarmRead,
|
ReadOnly = Browse | Read | Subscribe | HistoryRead | AlarmRead,
|
||||||
Operator = ReadOnly | WriteOperate | AlarmAcknowledge | AlarmConfirm,
|
Operator = ReadOnly | WriteOperate | AlarmAcknowledge | AlarmConfirm,
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ public sealed class TriePermissionEvaluator : IPermissionEvaluator
|
|||||||
OpcUaOperation.WriteTune => NodePermissions.WriteTune,
|
OpcUaOperation.WriteTune => NodePermissions.WriteTune,
|
||||||
OpcUaOperation.WriteConfigure => NodePermissions.WriteConfigure,
|
OpcUaOperation.WriteConfigure => NodePermissions.WriteConfigure,
|
||||||
OpcUaOperation.HistoryRead => NodePermissions.HistoryRead,
|
OpcUaOperation.HistoryRead => NodePermissions.HistoryRead,
|
||||||
OpcUaOperation.HistoryUpdate => NodePermissions.HistoryRead, // HistoryUpdate bit not yet in NodePermissions; TODO Stream C follow-up
|
OpcUaOperation.HistoryUpdate => NodePermissions.HistoryUpdate,
|
||||||
OpcUaOperation.CreateMonitoredItems => NodePermissions.Subscribe,
|
OpcUaOperation.CreateMonitoredItems => NodePermissions.Subscribe,
|
||||||
OpcUaOperation.TransferSubscriptions=> NodePermissions.Subscribe,
|
OpcUaOperation.TransferSubscriptions=> NodePermissions.Subscribe,
|
||||||
OpcUaOperation.Call => NodePermissions.MethodCall,
|
OpcUaOperation.Call => NodePermissions.MethodCall,
|
||||||
|
|||||||
@@ -191,6 +191,38 @@ public sealed class TriePermissionEvaluatorTests
|
|||||||
"a session bound to a generation absent from the cache must fail closed");
|
"a session bound to a generation absent from the cache must fail closed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Verifies that a HistoryRead-only grant does NOT authorize HistoryUpdate.</summary>
|
||||||
|
[Fact]
|
||||||
|
public void HistoryRead_grant_does_not_authorize_HistoryUpdate()
|
||||||
|
{
|
||||||
|
// Before the fix HistoryUpdate was mapped to the HistoryRead bit, so a read-only grant
|
||||||
|
// would wrongly authorise a write operation.
|
||||||
|
var evaluator = MakeEvaluator([Row("cn=ops", NodeAclScopeKind.Cluster, null, NodePermissions.HistoryRead)]);
|
||||||
|
|
||||||
|
var decision = evaluator.Authorize(Session(["cn=ops"]), OpcUaOperation.HistoryUpdate, Scope());
|
||||||
|
|
||||||
|
decision.IsAllowed.ShouldBeFalse("HistoryRead grant must NOT imply HistoryUpdate");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Verifies that a HistoryUpdate grant authorizes HistoryUpdate.</summary>
|
||||||
|
[Fact]
|
||||||
|
public void HistoryUpdate_grant_authorizes_HistoryUpdate()
|
||||||
|
{
|
||||||
|
var evaluator = MakeEvaluator([Row("cn=ops", NodeAclScopeKind.Cluster, null, NodePermissions.HistoryUpdate)]);
|
||||||
|
|
||||||
|
var decision = evaluator.Authorize(Session(["cn=ops"]), OpcUaOperation.HistoryUpdate, Scope());
|
||||||
|
|
||||||
|
decision.IsAllowed.ShouldBeTrue("HistoryUpdate grant must authorize HistoryUpdate operation");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Verifies that the HistoryUpdate bit is 1<<12 and does not collide with MethodCall.</summary>
|
||||||
|
[Fact]
|
||||||
|
public void HistoryUpdate_bit_value_and_no_collision()
|
||||||
|
{
|
||||||
|
((int)NodePermissions.HistoryUpdate).ShouldBe(1 << 12);
|
||||||
|
(NodePermissions.HistoryUpdate & NodePermissions.MethodCall).ShouldBe(NodePermissions.None);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Verifies that the operation-to-permission mapping is total.</summary>
|
/// <summary>Verifies that the operation-to-permission mapping is total.</summary>
|
||||||
[Fact]
|
[Fact]
|
||||||
public void OperationToPermission_Mapping_IsTotal()
|
public void OperationToPermission_Mapping_IsTotal()
|
||||||
|
|||||||
Reference in New Issue
Block a user