fix(acl): per-event visibility for direct folder event items; generation-checked native wiring (#826)
Event items on an equipment / raw device folder were fed by the SDK's MonitoredNode2 fan-out with no ACL decision, so after a deploy moved a scripted alarm (or dropped the reference projecting a native alarm into an equipment) the OLD folder's subscribers kept receiving it - EventId included - until the rebuild landed, indefinitely after a failed apply. - OtOpcUaNodeManager re-implements INodeManager3 so ValidateEventRolePermissions (the SDK's per-event, per-item hook; base not virtual) adds IsNotifierEventVisibleTo: live condition instance, notifier still a legitimate notifier of it, and the #586/#808 Server-object rule (IsConditionVisibleTo over ConditionVisibilityScopes). - The SDK memoizes that verdict per (item, event type, SourceNode): InvalidateFolderEventVerdicts runs on the authority/authorizer setters, RevalidateMonitoredItems (deploy + timer) and CompleteAddressSpaceApply. - ConditionRefresh on direct items goes through RefreshDirectItems (base loop + the same decision). - New generation field TagReferencingEquipment (RawPath -> EquipmentIds, from the artifact's UnsTagReferences via AclScopePathResolver.ResolveTagRawPath): a native alarm's wired equipment folder only counts (folder delivery AND Server-object scopes) while the current generation still references it. - Secondary #808 edge: UNS variables wired UNS->Raw Organizes by AddReference are tracked and always placed as reference variables (an Area named like its equipment's id no longer narrows them). Tests: UnsFolderScopeWireTests (moved alarm on a folder item + ConditionRefresh + rebuild; area named like equipment id), NativeAlarmFolderEventAclWireTests (dropped reference: folder + Server object + refresh + restore), DeploymentArtifactScopePathTests (projection). Rig/event tests now supply tag references.
This commit is contained in:
+28
-2
@@ -600,9 +600,13 @@ and Write under old and new names until the address space re-points them. An id
|
||||
folder / virtual tag / condition, a probe, a NodeDeleted model change) is decided only by a `Cluster` or `uns`
|
||||
`Namespace` grant — so narrow grantees do not receive NodeDeleted for removed opaque-id nodes, and a removed id can
|
||||
never match an unrelated narrow grant that shares its name; a four-segment (reference-shaped) id keeps its split.
|
||||
Identifiers containing NUL are refused. Server-object (`i=2253`) delivery of a
|
||||
Identifiers containing NUL are refused. A UNS variable the reference pass wired to its raw node (the `Organizes`
|
||||
edge) is placed as a reference variable whatever its id spells (Gitea #826 — the id rule alone made an equipment's
|
||||
references folder-owned when an Area was named exactly like that equipment's id, losing their `Tag`-grant match).
|
||||
Server-object (`i=2253`) delivery of a
|
||||
**scripted** condition is now decided on the condition alone (its folders add nothing in steady state and would
|
||||
leak a moved alarm to its old owner). So a key holding only an `Equipment` grant can navigate to its equipment,
|
||||
leak a moved alarm to its old owner). Direct event items on a folder get the same per-event decision (Gitea #826,
|
||||
below). So a key holding only an `Equipment` grant can navigate to its equipment,
|
||||
read its alarm children, receive its scripted alarm events through the Server object and Acknowledge / Confirm /
|
||||
AddComment with the EventId it received. Fail-closed throughout: an unplaceable node is denied under every grant.
|
||||
| HistoryRead (all four variants) | **nothing** | `HistoryRead` on each node's scope, decided **per node in the batch** |
|
||||
@@ -709,6 +713,28 @@ Behaviours worth knowing before enabling it:
|
||||
condition) runs on removal, rebuild and Server-object subscribe/delete. A deploy of N alarms under a
|
||||
live Server-object item is therefore N bookkeeping steps, not N²/2 under the node-manager lock.
|
||||
|
||||
**Direct (per-folder) event items are filtered per event too** (Gitea #826). An item on an equipment or raw
|
||||
device folder is fed by the SDK's own per-notifier fan-out (`MonitoredNode2`), which the #586 dispatcher never
|
||||
touches — so after a deployment moved a scripted alarm (or dropped the reference projecting a native alarm into
|
||||
an equipment), the OLD folder's subscribers kept receiving it, EventId included, until the rebuild landed, and
|
||||
indefinitely after a failed apply (commands were already denied). With the flag **on**, each event to such an
|
||||
item now also needs, against the CURRENT state:
|
||||
- the event's condition to be the **live** instance (a torn-down one is withheld);
|
||||
- the folder to still be a legitimate notifier of it — a node it hangs beneath, or a folder it is wired into
|
||||
**that the current generation still projects its raw tag into** (`DataPlaneAuthoritySnapshot.TagReferencingEquipment`,
|
||||
`RawPath → EquipmentIds`, decoded from the artifact's `UnsTagReferences` by the same resolver as the grants);
|
||||
- and the condition to pass exactly the Server-object rule above for the item's session.
|
||||
|
||||
The generation check also narrows the **Server-object** rule: a wired folder whose reference the generation
|
||||
dropped no longer admits the native alarm there either. **`ConditionRefresh`** on a folder item is filtered the
|
||||
same way. The hook is `INodeManager3.ValidateEventRolePermissions`, which the node manager re-implements (the
|
||||
base method is not virtual); the SDK memoizes that verdict per `(item, event type, SourceNode)`, so the node
|
||||
manager invalidates it on every new generation or authorizer, on each `AclRevalidationTimer` tick, and at the
|
||||
end of every apply. Withheld events are not replayed and the client is not told; restoring the reference or
|
||||
grant resumes the feed on the next event. ⚠️ A native alarm reaches an equipment folder's items under
|
||||
enforcement only while the installed generation carries that reference — a hand-built authority (tests) must
|
||||
supply `tagReferencingEquipment`. Inert with the flag off.
|
||||
|
||||
**Model-change and audit events on the Server object are gated too** (Gitea #588). The node manager's
|
||||
own server-wide events — `GeneralModelChangeEvent` (NodeAdded / NodeDeleted / DataTypeChanged) and the
|
||||
`AuditWriteUpdateEvent` of a rejected device write — used to go straight to `Server.ReportEvent`, where
|
||||
|
||||
Reference in New Issue
Block a user