fix(acl): place opaque-id UNS folders, virtual tags and scripted conditions for every operation (#808)
UNS Area/Line/Equipment folders are minted under their opaque entity ids (s=UA-/UL-/EQ-...), virtual tags under s=EQ-.../Name and scripted conditions (+ #629 children) under s=<ScriptedAlarmId>[/...], while every UNS grant is planted at a NAME path. DataPlaneAuthorizer split those ids, so with Security:Acl:Enabled a narrow (Area/Line/Equipment) grant could not Browse, Read, subscribe or HistoryRead them, and Server-object delivery never reached it — so a narrow key never held the EventId Ack/Confirm/AddComment need. - The generation carries UnsFolderScopes (folderId -> path + id chain), built by DeploymentArtifact from the same AclScopePathResolver as the grants; ids two entity kinds share are dropped; plumbed through IDataPlaneAuthoritySink. - OtOpcUaNodeManager.ResolveUnsAclSubject reports live facts (folder id ancestry / scripted owner binding); attached by the Authorizer setter. - Evaluate places the subject against the CURRENT generation before the decision cache, which now caches the walk keyed on the resolved scope. Folders require live chain == generation chain (a moved equipment is denied under old and new line until the rebuild). Owned nodes (vtags, conditions, children) get owner path + a reserved segment, so Tag grants and their Browse transit never expose them. Conditions share TryPlaceScriptedCondition with the #797 command gate. - Server-object visibility of a scripted condition is decided on the condition alone, so a moved alarm cannot reach its old owner there. Tests: UnsFolderScopeWireTests (opaque ids, end-to-end browse -> EventId -> Acknowledge on an Equipment grant; sibling denied; Tag-only grant; moved alarm and moved equipment stale windows). Rig tests now install folder scopes explicitly (ServerEventRig.FolderScopes); the #797 recreate test deliberately asserts the moved alarm is invisible to the Cluster observer in the window.
This commit is contained in:
+19
-2
@@ -579,8 +579,25 @@ re-binds it. Fail-closed: an unbound condition (owner folder missing), a
|
||||
removed/replaced instance, or an owner the generation cannot place is denied for **every** grant, `Cluster`
|
||||
included — there is no realm-root or id-split fallback. The key, grant and generation are re-read on every call
|
||||
(uncached). NodeIds and `AlarmCommand.AlarmId` are unchanged. With the flag **off** none of this is consulted.
|
||||
⚠️ Read/Browse/Subscribe of UNS folders and scripted conditions, and Server-object event visibility, are still
|
||||
decided on the NodeId's split, and UNS folder ids are opaque entity ids — Gitea #808.
|
||||
|
||||
**Where an opaque-id UNS node is placed for Read / Browse / Subscribe / HistoryRead (Gitea #808).** UNS Area /
|
||||
Line / Equipment folders are minted under their opaque entity ids (`s=UA-…`, `s=UL-…`, `s=EQ-…`), an equipment's
|
||||
virtual tags under `s=EQ-…/Name`, and scripted conditions plus their instance-scoped children under
|
||||
`s=<ScriptedAlarmId>[/…]` — none of which is a grant path, so splitting them reached only `Cluster` / `uns` grants.
|
||||
`DataPlaneAuthorizer` now asks the node manager (`ResolveUnsAclSubject`, attached when the authorizer is set) how
|
||||
the node sits in the LIVE address space and places it against the CURRENT generation: a hierarchy folder at its
|
||||
entity's path from the generation's `UnsFolderScopes` (built by the same `AclScopePathResolver` call that plants
|
||||
`UnsArea`/`UnsLine`/`Equipment` grants), **only while the live folder-id ancestry equals the one the generation
|
||||
deploys** (a moved equipment is denied under its old and new line's grants until the rebuild re-parents it); a
|
||||
virtual tag / its sub-folder at its equipment's path plus a reserved segment; a scripted condition and its children
|
||||
at the owning equipment's path plus that reserved segment, through exactly the #797 owner rule. The reserved segment
|
||||
means only grants on the equipment or an ancestor confer them — a `Tag` grant beneath the equipment never does, and
|
||||
its Browse transit does not expose the equipment's alarms or virtual tags. UNS reference variables
|
||||
(`s=Area/Line/Equipment/Name`) are unchanged: their id IS their path. 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,
|
||||
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** |
|
||||
| HistoryUpdate (insert / replace / update / delete) | role `HistoryUpdate` (from a `Cluster`-scoped grant) + key re-check | `HistoryUpdate` on that node's scope, decided **per node in the batch** |
|
||||
| Read (every attribute, not just Value) | nothing | `Read` on that node's scope, decided per node in the batch |
|
||||
|
||||
Reference in New Issue
Block a user