fix(acl): close the #808 review findings — reference variables, owner folder chain, NUL keys, unknown ids (#808)
Review of af5c9744:
1. UNS reference variables (s=Area/Line/Equip/Eff) of a moved or renamed
equipment stayed readable/writable under the OLD names' grants: they now
resolve as ReferenceVariable and are authorized at their split path only
while it equals their live equipment folder's placement (live chain ==
generation chain) + one name; otherwise denied (Read and Write).
2. TryPlaceScriptedCondition now also requires the owner folder's live chain
to match the generation (AuthorizeScriptedCondition gains ownerFolderChain),
so a moved equipment's alarm/children/commands follow its folder.
3. Identifiers containing NUL are denied before the decision cache, keeping
client ids disjoint from the NUL-prefixed resolved-scope keys (a client
could otherwise poison a real folder's cached verdict for its key shard).
4. An id no live node holds is Unknown: placed at [\0unknown] so only
Cluster / Namespace-uns grants decide it (a removed opaque id can no longer
split onto a same-named narrow grant; narrow grantees get no NodeDeleted for
removed UNS folders/vtags/conditions — documented); 4-segment
reference-shaped ids keep their split.
5. A wire test drives placements from DeploymentArtifact.ParseDataPlaneAuthority.
Baseline updates: #797 owner tests (unit + wire) and Sdk2RepeatedAlarmAuthority
now install folder scopes; Sdk2HistoryAuthority's UNS alias used the RAW path
as its UNS id (never minted in production) and now carries EqA/HistoryAlias.
Each new guard was falsified (reverting it fails its test).
This commit is contained in:
+18
-1
@@ -298,6 +298,23 @@ public sealed class ScriptedAlarmOwnerScopeWireTests
|
||||
new Dictionary<string, (string UnsLineId, string Name)> { [Pump] = (LineId, "Pump"), [Fan] = (LineId, "Fan") },
|
||||
[]);
|
||||
|
||||
/// <summary>
|
||||
/// The topology's UNS folder placements (Gitea #808), from the same resolver: a scripted condition is placed
|
||||
/// only while its owner's live folder ancestry matches the generation's.
|
||||
/// </summary>
|
||||
private static Dictionary<string, UnsFolderScope> FolderScopes()
|
||||
{
|
||||
var resolver = Resolver();
|
||||
NodeAclPath One(NodeAclScopeKind kind, string id) => resolver.ResolveOne(kind, id).ShouldHaveSingleItem();
|
||||
return new Dictionary<string, UnsFolderScope>
|
||||
{
|
||||
[AreaId] = new(One(NodeAclScopeKind.UnsArea, AreaId), [AreaId]),
|
||||
[LineId] = new(One(NodeAclScopeKind.UnsLine, LineId), [AreaId, LineId]),
|
||||
[Pump] = new(One(NodeAclScopeKind.Equipment, Pump), [AreaId, LineId, Pump]),
|
||||
[Fan] = new(One(NodeAclScopeKind.Equipment, Fan), [AreaId, LineId, Fan]),
|
||||
};
|
||||
}
|
||||
|
||||
private static Dictionary<string, NodeAclPath> OwnerPaths()
|
||||
{
|
||||
var resolver = Resolver();
|
||||
@@ -324,7 +341,7 @@ public sealed class ScriptedAlarmOwnerScopeWireTests
|
||||
private static void Reapply(
|
||||
DataPlaneAuthorityState state, string revision, IReadOnlyDictionary<string, string> deployedOwners,
|
||||
ApiKey[] keys, params NodeAcl[] grants) =>
|
||||
state.Apply(ServerEventRig.Cluster, revision, keys, grants, Resolver().Resolve(grants), OwnerPaths(), deployedOwners);
|
||||
state.Apply(ServerEventRig.Cluster, revision, keys, grants, Resolver().Resolve(grants), OwnerPaths(), deployedOwners, FolderScopes());
|
||||
|
||||
/// <summary>The generation's deployed scripted-alarm owners; <paramref name="alarmOwner"/> moves the Pump alarm.</summary>
|
||||
private static Dictionary<string, string> Deployed(string alarmOwner = Pump) =>
|
||||
|
||||
Reference in New Issue
Block a user