feat(authz): remove SystemPlatform scope + permission-trie walk (Galaxy resolves via Equipment)
This commit is contained in:
@@ -7,11 +7,9 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Authorization;
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Per decision #129 and the Phase 6.2 Stream B plan the hierarchy is
|
||||
/// <c>Cluster → Namespace → UnsArea → UnsLine → Equipment → Tag</c> for UNS
|
||||
/// (Equipment-kind) namespaces. Galaxy (SystemPlatform-kind) namespaces instead use
|
||||
/// <c>Cluster → Namespace → FolderSegment(s) → Tag</c>, and each folder segment takes
|
||||
/// one trie level — so a deeply-nested Galaxy folder implicitly reaches the same
|
||||
/// depth as a full UNS path.</para>
|
||||
/// <c>Cluster → Namespace → UnsArea → UnsLine → Equipment → Tag</c> for all
|
||||
/// (Equipment-kind) namespaces. Galaxy is a standard Equipment-kind driver, so Galaxy
|
||||
/// points are ordinary equipment tags that resolve through this same walk.</para>
|
||||
///
|
||||
/// <para>Unset mid-path levels (e.g. a Cluster-scoped request with no UnsArea) leave
|
||||
/// the corresponding id <c>null</c>. The evaluator walks as far as the scope goes +
|
||||
@@ -25,34 +23,25 @@ public sealed record NodeScope
|
||||
/// <summary>Namespace within the cluster. Null is not allowed for a request against a real node.</summary>
|
||||
public string? NamespaceId { get; init; }
|
||||
|
||||
/// <summary>For Equipment-kind namespaces: UNS area (e.g. "warsaw-west"). Null on Galaxy.</summary>
|
||||
/// <summary>UNS area (e.g. "warsaw-west") below the namespace.</summary>
|
||||
public string? UnsAreaId { get; init; }
|
||||
|
||||
/// <summary>For Equipment-kind namespaces: UNS line below the area. Null on Galaxy.</summary>
|
||||
/// <summary>UNS line below the area.</summary>
|
||||
public string? UnsLineId { get; init; }
|
||||
|
||||
/// <summary>For Equipment-kind namespaces: equipment row below the line. Null on Galaxy.</summary>
|
||||
/// <summary>Equipment row below the line.</summary>
|
||||
public string? EquipmentId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// For Galaxy (SystemPlatform-kind) namespaces only: the folder path segments from
|
||||
/// namespace root to the target tag, in order. Empty on Equipment namespaces.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> FolderSegments { get; init; } = [];
|
||||
|
||||
/// <summary>Target tag id when the scope addresses a specific tag; null for folder / equipment-level scopes.</summary>
|
||||
/// <summary>Target tag id when the scope addresses a specific tag; null for equipment-level scopes.</summary>
|
||||
public string? TagId { get; init; }
|
||||
|
||||
/// <summary>Which hierarchy applies — Equipment-kind (UNS) or SystemPlatform-kind (Galaxy).</summary>
|
||||
/// <summary>Which hierarchy applies. Currently only Equipment-kind (UNS) exists.</summary>
|
||||
public required NodeHierarchyKind Kind { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Selector between the two scope-hierarchy shapes.</summary>
|
||||
/// <summary>Selector for the scope-hierarchy shape.</summary>
|
||||
public enum NodeHierarchyKind
|
||||
{
|
||||
/// <summary><c>Cluster → Namespace → UnsArea → UnsLine → Equipment → Tag</c> — UNS / Equipment kind.</summary>
|
||||
Equipment,
|
||||
|
||||
/// <summary><c>Cluster → Namespace → FolderSegment(s) → Tag</c> — Galaxy / SystemPlatform kind.</summary>
|
||||
SystemPlatform,
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Authorization;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory permission trie for one <c>(ClusterId, GenerationId)</c>. Walk from the cluster
|
||||
/// root down through namespace → UNS levels (or folder segments) → tag, OR-ing the
|
||||
/// root down through namespace → UNS levels → tag, OR-ing the
|
||||
/// <see cref="TrieGrant.PermissionFlags"/> granted at each visited level for each of the session's
|
||||
/// LDAP groups. The accumulated bitmask is compared to the permission required by the
|
||||
/// requested <see cref="Abstractions.OpcUaOperation"/>.
|
||||
@@ -51,11 +51,10 @@ public sealed class PermissionTrie
|
||||
if (!Root.Children.TryGetValue(scope.NamespaceId, out var ns)) return matches;
|
||||
CollectAtLevel(ns, NodeAclScopeKind.Namespace, groups, matches);
|
||||
|
||||
// Two hierarchies diverge below the namespace.
|
||||
if (scope.Kind == NodeHierarchyKind.Equipment)
|
||||
WalkEquipment(ns, scope, groups, matches);
|
||||
else
|
||||
WalkSystemPlatform(ns, scope, groups, matches);
|
||||
// Below the namespace every kind walks the Equipment hierarchy — Galaxy is a standard
|
||||
// Equipment-kind driver, so Galaxy points are ordinary equipment tags keyed by
|
||||
// EquipmentId/path and resolve through this same walk.
|
||||
WalkEquipment(ns, scope, groups, matches);
|
||||
|
||||
return matches;
|
||||
}
|
||||
@@ -79,25 +78,6 @@ public sealed class PermissionTrie
|
||||
CollectAtLevel(tag, NodeAclScopeKind.Tag, groups, matches);
|
||||
}
|
||||
|
||||
private static void WalkSystemPlatform(PermissionTrieNode ns, NodeScope scope, HashSet<string> groups, List<MatchedGrant> matches)
|
||||
{
|
||||
// FolderSegments are nested under the namespace; each is its own trie level. Use the
|
||||
// dedicated FolderSegment scope kind so Galaxy folder grants report their true scope in
|
||||
// AuthorizationDecision.Provenance — distinguishing them from UNS Equipment grants in
|
||||
// the audit trail and Admin UI "Probe this permission" diagnostic.
|
||||
var current = ns;
|
||||
foreach (var segment in scope.FolderSegments)
|
||||
{
|
||||
if (!current.Children.TryGetValue(segment, out var child)) return;
|
||||
CollectAtLevel(child, NodeAclScopeKind.FolderSegment, groups, matches);
|
||||
current = child;
|
||||
}
|
||||
|
||||
if (scope.TagId is null) return;
|
||||
if (!current.Children.TryGetValue(scope.TagId, out var tag)) return;
|
||||
CollectAtLevel(tag, NodeAclScopeKind.Tag, groups, matches);
|
||||
}
|
||||
|
||||
private static void CollectAtLevel(PermissionTrieNode node, NodeAclScopeKind level, HashSet<string> groups, List<MatchedGrant> matches)
|
||||
{
|
||||
foreach (var grant in node.Grants)
|
||||
|
||||
@@ -121,8 +121,7 @@ public static class PermissionTrieBuilder
|
||||
/// <see cref="NodeAclScopeKind.UnsLine"/>-scoped row sits in the hierarchy.
|
||||
/// </summary>
|
||||
/// <param name="Segments">
|
||||
/// Namespace id, then (for Equipment kind) UnsAreaId / UnsLineId / EquipmentId / TagId as
|
||||
/// applicable; or (for SystemPlatform kind) NamespaceId / FolderSegment / .../TagId.
|
||||
/// Namespace id, then UnsAreaId / UnsLineId / EquipmentId / TagId as applicable.
|
||||
/// </param>
|
||||
public sealed record NodeAclPath(IReadOnlyList<string> Segments);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user