fix(core): resolve Medium code-review finding (Core-003)
Add FolderSegment member to NodeAclScopeKind; update WalkSystemPlatform to report NodeAclScopeKind.FolderSegment (not Equipment) for each visited Galaxy folder level, so MatchedGrant.Scope in AuthorizationDecision.Provenance correctly distinguishes Galaxy folder grants from UNS Equipment grants in the audit trail and Admin UI diagnostics. Three regression tests added to PermissionTrieTests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,5 +8,11 @@ public enum NodeAclScopeKind
|
|||||||
UnsArea,
|
UnsArea,
|
||||||
UnsLine,
|
UnsLine,
|
||||||
Equipment,
|
Equipment,
|
||||||
|
/// <summary>
|
||||||
|
/// A Galaxy (SystemPlatform-kind) folder segment anchored below a namespace.
|
||||||
|
/// Distinguishes folder grants from UNS <see cref="Equipment"/> grants in the
|
||||||
|
/// <c>AuthorizationDecision.Provenance</c> audit trail and Admin UI diagnostics.
|
||||||
|
/// </summary>
|
||||||
|
FolderSegment,
|
||||||
Tag,
|
Tag,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,16 +79,15 @@ public sealed class PermissionTrie
|
|||||||
|
|
||||||
private static void WalkSystemPlatform(PermissionTrieNode ns, NodeScope scope, HashSet<string> groups, List<MatchedGrant> 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. Reuse the
|
// FolderSegments are nested under the namespace; each is its own trie level. Use the
|
||||||
// UnsArea scope kind for the flags — NodeAcl rows for Galaxy tags carry ScopeKind.Tag
|
// dedicated FolderSegment scope kind so Galaxy folder grants report their true scope in
|
||||||
// for leaf grants and ScopeKind.Namespace for folder-root grants; deeper folder grants
|
// AuthorizationDecision.Provenance — distinguishing them from UNS Equipment grants in
|
||||||
// are modeled as Equipment-level rows today since NodeAclScopeKind doesn't enumerate
|
// the audit trail and Admin UI "Probe this permission" diagnostic.
|
||||||
// a dedicated FolderSegment kind. Future-proof TODO tracked in Stream B follow-up.
|
|
||||||
var current = ns;
|
var current = ns;
|
||||||
foreach (var segment in scope.FolderSegments)
|
foreach (var segment in scope.FolderSegments)
|
||||||
{
|
{
|
||||||
if (!current.Children.TryGetValue(segment, out var child)) return;
|
if (!current.Children.TryGetValue(segment, out var child)) return;
|
||||||
CollectAtLevel(child, NodeAclScopeKind.Equipment, groups, matches);
|
CollectAtLevel(child, NodeAclScopeKind.FolderSegment, groups, matches);
|
||||||
current = child;
|
current = child;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -125,6 +125,55 @@ public sealed class PermissionTrieTests
|
|||||||
matchB.ShouldBeEmpty();
|
matchB.ShouldBeEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Core-003 regression: grants matched during the SystemPlatform (Galaxy) folder walk must
|
||||||
|
/// report <see cref="NodeAclScopeKind.FolderSegment"/> in <see cref="MatchedGrant.Scope"/>,
|
||||||
|
/// not <see cref="NodeAclScopeKind.Equipment"/>. This distinguishes Galaxy folder grants
|
||||||
|
/// from UNS Equipment grants in the audit trail and Admin UI "Probe this permission" panel.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void Galaxy_FolderSegment_Grant_Reports_FolderSegment_Scope_Not_Equipment()
|
||||||
|
{
|
||||||
|
var paths = new Dictionary<string, NodeAclPath>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["section-X"] = new(new[] { "ns-gal", "section-X" }),
|
||||||
|
};
|
||||||
|
var rows = new[] { Row("cn=ops", NodeAclScopeKind.Equipment, "section-X", NodePermissions.Read) };
|
||||||
|
var trie = PermissionTrieBuilder.Build("c1", 1, rows, paths);
|
||||||
|
|
||||||
|
var matches = trie.CollectMatches(GalaxyTag("c1", "ns-gal", ["section-X"], "tag1"), ["cn=ops"]);
|
||||||
|
|
||||||
|
matches.Count.ShouldBe(1);
|
||||||
|
matches[0].Scope.ShouldBe(NodeAclScopeKind.FolderSegment,
|
||||||
|
"the trie walk reports the structural level where the grant was found — FolderSegment for Galaxy, not Equipment");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Galaxy_DeepFolderPath_AllSegments_Report_FolderSegment_Scope()
|
||||||
|
{
|
||||||
|
// A three-level folder path — each visited level that carries a grant must report FolderSegment.
|
||||||
|
var paths = new Dictionary<string, NodeAclPath>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["area"] = new(new[] { "ns-gal", "area" }),
|
||||||
|
["area/line"] = new(new[] { "ns-gal", "area", "line" }),
|
||||||
|
["area/line/cell"] = new(new[] { "ns-gal", "area", "line", "cell" }),
|
||||||
|
};
|
||||||
|
var rows = new[]
|
||||||
|
{
|
||||||
|
Row("cn=ops", NodeAclScopeKind.Equipment, "area", NodePermissions.Read),
|
||||||
|
Row("cn=ops", NodeAclScopeKind.Equipment, "area/line", NodePermissions.Read),
|
||||||
|
Row("cn=ops", NodeAclScopeKind.Equipment, "area/line/cell", NodePermissions.Read),
|
||||||
|
};
|
||||||
|
var trie = PermissionTrieBuilder.Build("c1", 1, rows, paths);
|
||||||
|
|
||||||
|
var matches = trie.CollectMatches(
|
||||||
|
GalaxyTag("c1", "ns-gal", ["area", "line", "cell"], "tag1"), ["cn=ops"]);
|
||||||
|
|
||||||
|
matches.Count.ShouldBe(3, "one match per folder level visited");
|
||||||
|
matches.ShouldAllBe(m => m.Scope == NodeAclScopeKind.FolderSegment,
|
||||||
|
"every matched folder level must report FolderSegment, never Equipment");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CrossCluster_Grant_DoesNotLeak()
|
public void CrossCluster_Grant_DoesNotLeak()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user