Files
lmxopcua/tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/AppSettingsSecretHygieneTests.cs
Joseph Doherty 3de688f8d6 fix(admin): resolve High code-review findings (Admin-003, Admin-004, Admin-005)
Admin-003 — SignalR hubs were anonymously reachable: an unauthenticated
client could open /hubs/fleet, /hubs/alerts and /hubs/script-log and
stream fleet state, alert detail text and server script-log contents.
Added [Authorize] to FleetStatusHub, AlertHub and ScriptLogHub, and
chained .RequireAuthorization() onto all three MapHub() calls as a
belt-and-braces backstop.

Admin-004 — appsettings.json committed live-looking secrets (the `sa`
ConfigDb password and the LDAP ServiceAccountPassword) in plaintext.
Replaced both with empty placeholders sourced from user-secrets (dev) or
the ConnectionStrings__ConfigDb / Authentication__Ldap__ServiceAccountPassword
environment variables (prod); added a UserSecretsId to the Admin csproj
and a fail-fast guard in Program.cs when ConfigDb is empty/missing.

Admin-005 — Login.razor performed SignInAsync from an interactive Blazor
circuit, where the original HTTP response has long completed so the auth
cookie was not emitted. Rewrote it as a static-rendered plain HTML form
(data-enhance="false") posting to a new AuthEndpoints.MapAuthEndpoints()
minimal-API handler (/auth/login, /auth/logout) that does the LDAP bind,
grant resolution, cookie SignInAsync and redirect while the endpoint
still owns the response. Includes an open-redirect guard on returnUrl.

Added xUnit + Shouldly regression tests: AuthEndpointsTests (login cookie
issuance, failed-bind redirect, open-redirect rejection, logout, anonymous
hub negotiate rejection) and AppSettingsSecretHygieneTests (no committed
secrets). All 26 auth-related tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 06:27:38 -04:00

80 lines
3.0 KiB
C#

using System.Text.Json;
using Shouldly;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.Admin.Tests;
/// <summary>
/// Regression coverage for Admin-004 — the committed <c>appsettings.json</c> must carry no
/// plaintext secrets. The <c>ConfigDb</c> connection string and the LDAP
/// <c>ServiceAccountPassword</c> are supplied at runtime via user-secrets (dev) or
/// environment variables (prod); the checked-in file holds only empty placeholders.
/// </summary>
[Trait("Category", "Unit")]
public sealed class AppSettingsSecretHygieneTests
{
private static JsonDocument LoadAdminAppSettings()
{
// Walk up from the test assembly to the repo root (the dir holding the .slnx) and
// read the SOURCE appsettings.json — not a bin/ copy — so the test asserts on what
// is actually committed.
var dir = AppContext.BaseDirectory;
while (dir is not null && !File.Exists(Path.Combine(dir, "ZB.MOM.WW.OtOpcUa.slnx")))
dir = Path.GetDirectoryName(dir);
dir.ShouldNotBeNull("could not locate the repo root (ZB.MOM.WW.OtOpcUa.slnx)");
var path = Path.Combine(dir, "src", "Server", "ZB.MOM.WW.OtOpcUa.Admin", "appsettings.json");
File.Exists(path).ShouldBeTrue($"Admin appsettings.json not found at {path}");
return JsonDocument.Parse(File.ReadAllText(path));
}
[Fact]
public void ConfigDb_connection_string_is_an_empty_placeholder()
{
using var doc = LoadAdminAppSettings();
var connectionString = doc.RootElement
.GetProperty("ConnectionStrings")
.GetProperty("ConfigDb")
.GetString();
connectionString.ShouldBeNullOrEmpty(
"the ConfigDb connection string must not be committed — supply it via user-secrets " +
"or the ConnectionStrings__ConfigDb environment variable (Admin-004)");
}
[Fact]
public void Ldap_service_account_password_is_an_empty_placeholder()
{
using var doc = LoadAdminAppSettings();
var password = doc.RootElement
.GetProperty("Authentication")
.GetProperty("Ldap")
.GetProperty("ServiceAccountPassword")
.GetString();
password.ShouldBeNullOrEmpty(
"the LDAP ServiceAccountPassword must not be committed (Admin-004)");
}
[Fact]
public void No_known_dev_secret_literals_appear_anywhere_in_appsettings()
{
var dir = AppContext.BaseDirectory;
while (dir is not null && !File.Exists(Path.Combine(dir, "ZB.MOM.WW.OtOpcUa.slnx")))
dir = Path.GetDirectoryName(dir);
dir.ShouldNotBeNull();
var raw = File.ReadAllText(Path.Combine(
dir, "src", "Server", "ZB.MOM.WW.OtOpcUa.Admin", "appsettings.json"));
// The exact secret literals the review (Admin-004) flagged must be gone entirely —
// not relocated to another key, not present as a comment.
raw.ShouldNotContain("OtOpcUaDev_2026!");
raw.ShouldNotContain("serviceaccount123");
raw.ShouldNotContain("User Id=sa");
}
}