using Shouldly; using Xunit; namespace ZB.MOM.WW.OtOpcUa.Host.Tests.Configuration; /// /// Security register item 3 (DI role-scoping pin). IMeshPskProvider and /// IMeshCommandDialer are central's per-cluster key lookup + outbound command dialer — /// a driver-only node holds exactly its own MeshTransport:GrpcPsk and never dials /// another node, so these two singletons must be registered ONLY inside one of Program.cs's /// if (hasAdmin) blocks. Nothing in the language enforces that placement; this is a /// source scan so a refactor that hoists either registration out of its block fails loudly /// here instead of silently widening the mesh trust boundary to driver-only nodes. /// /// /// Program.cs gates several unrelated concerns behind repeated if (hasAdmin) checks /// (auth/AdminUI mounting, health probes, gRPC endpoint mapping, ...), so this pin cannot /// assume there is only one such block in the file — it must find the SPECIFIC block that /// contains each registration and prove that containment is unambiguous. /// [Trait("Category", "Unit")] public sealed class MeshRegistrationRoleScopingTests { private const string PskRegistration = "AddSingleton"; private const string DialerRegistration = "AddSingleton"; private const string HasAdminMarker = "if (hasAdmin)"; private static string ReadProgramSource() { var dir = new DirectoryInfo(AppContext.BaseDirectory); while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "ZB.MOM.WW.OtOpcUa.slnx"))) dir = dir.Parent; dir.ShouldNotBeNull(); return File.ReadAllText(Path.Combine( dir!.FullName, "src", "Server", "ZB.MOM.WW.OtOpcUa.Host", "Program.cs")); } private static int CountOccurrences(string haystack, string needle) { var count = 0; var index = 0; while ((index = haystack.IndexOf(needle, index, StringComparison.Ordinal)) >= 0) { count++; index += needle.Length; } return count; } /// /// Brace-matches every if (hasAdmin) block in the file (not a regex over the block /// body — the body is free-form C# and a regex would be fragile to reformatting) and /// returns each block's (open-brace, close-brace) character range. /// private static List<(int Start, int End)> FindHasAdminBlockRanges(string source) { var ranges = new List<(int Start, int End)>(); var searchIndex = 0; while (true) { var markerIndex = source.IndexOf(HasAdminMarker, searchIndex, StringComparison.Ordinal); if (markerIndex < 0) break; var openBraceIndex = source.IndexOf('{', markerIndex + HasAdminMarker.Length); openBraceIndex.ShouldBeGreaterThan(-1, $"'{HasAdminMarker}' at offset {markerIndex} is not followed by an opening brace."); var depth = 0; var closeBraceIndex = -1; for (var i = openBraceIndex; i < source.Length; i++) { if (source[i] == '{') depth++; else if (source[i] == '}') { depth--; if (depth == 0) { closeBraceIndex = i; break; } } } closeBraceIndex.ShouldBeGreaterThan(-1, $"brace-matching for the '{HasAdminMarker}' block starting at offset {markerIndex} never closed."); ranges.Add((openBraceIndex, closeBraceIndex)); searchIndex = closeBraceIndex + 1; } return ranges; } /// /// Locates the single if (hasAdmin) block that contains . /// Fails loudly — rather than silently picking the first match — if the registration is /// missing entirely, sits outside every admin-role block, or (should a future edit make two /// blocks both contain matching text) the containing block is ambiguous. Any of those is a /// reason to stop and re-examine the pin, not to guess. /// private static string ExtractContainingHasAdminBlock(string source, string registration, string whyItMatters) { var ranges = FindHasAdminBlockRanges(source); ranges.ShouldNotBeEmpty( $"Program.cs no longer contains any '{HasAdminMarker}' block — the admin-role gate this " + $"pin relies on is gone. {whyItMatters}"); var registrationIndex = source.IndexOf(registration, StringComparison.Ordinal); registrationIndex.ShouldBeGreaterThan(-1, $"'{registration}' was not found anywhere in Program.cs. {whyItMatters}"); var containingBlocks = ranges .Where(r => registrationIndex >= r.Start && registrationIndex <= r.End) .ToList(); containingBlocks.ShouldNotBeEmpty( $"'{registration}' is registered OUTSIDE every '{HasAdminMarker}' block. {whyItMatters}"); containingBlocks.Count.ShouldBe(1, $"'{registration}' falls inside {containingBlocks.Count} '{HasAdminMarker}' blocks, so " + "which one actually gates it is ambiguous — extend this pin to disambiguate rather than " + $"trusting the first match. {whyItMatters}"); var (start, end) = containingBlocks[0]; return source.Substring(start, end - start + 1); } [Fact] public void MeshPskProvider_IsRegisteredExactlyOnce_InsideAnUnambiguousHasAdminBlock() { const string why = "A driver-only node must not carry central's per-cluster key lookup, which it can never " + "legitimately use."; var source = ReadProgramSource(); // Global uniqueness first: if this text appeared twice, block-containment below could pass // even though a second, unguarded copy exists elsewhere in the file. CountOccurrences(source, PskRegistration).ShouldBe(1, $"IMeshPskProvider must be registered exactly once in Program.cs. {why}"); var block = ExtractContainingHasAdminBlock(source, PskRegistration, why); block.ShouldContain(PskRegistration, Case.Sensitive, $"IMeshPskProvider must be registered inside 'if (hasAdmin)'. {why}"); } [Fact] public void MeshGrpcCommandDialer_IsRegisteredExactlyOnce_InsideAnUnambiguousHasAdminBlock() { const string why = "A driver-only node never dials another node and must not carry central's outbound mesh " + "command dialer."; var source = ReadProgramSource(); CountOccurrences(source, DialerRegistration).ShouldBe(1, $"IMeshCommandDialer must be registered exactly once in Program.cs. {why}"); var block = ExtractContainingHasAdminBlock(source, DialerRegistration, why); block.ShouldContain(DialerRegistration, Case.Sensitive, $"IMeshCommandDialer must be registered inside 'if (hasAdmin)'. {why}"); } }