Files
lmxopcua/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.MTConnect/MTConnectUriRedaction.cs
T
Joseph Doherty d67634cc4f fix(mtconnect): redact the Agent URI everywhere it is shown (R06:T-8)
The driver logged and surfaced the raw AgentUri (which may carry user:pass@) as
HostName (the /hosts key), in ~20 log lines and in exception messages that reach
DriverHealth.LastError, and the client's request-URI messages (timeouts, stream
end, not-a-stream, framing) embedded it too - while the Test-Connect probe
deliberately redacted it. New MTConnectUriRedaction (user-info, and for the
configured string its query, removed; a clean URI is unchanged so host keys do
not move); the driver computes its display form once per options document.

Refs #743

Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
2026-10-22 16:00:40 -04:00

61 lines
2.8 KiB
C#

namespace ZB.MOM.WW.OtOpcUa.Driver.MTConnect;
/// <summary>
/// Review 2026-10-21 R06:T-8 — the display form of an Agent URI for every log line, exception message,
/// <c>DriverHealth.LastError</c> and <c>HostName</c> this driver produces. An <c>AgentUri</c> may carry
/// <c>user:pass@</c>; the Test-Connect probe already redacted it (<c>MTConnectDriverProbe.RedactedDisplay</c>)
/// while the driver itself logged and surfaced the raw string — and every request URI built from it.
/// </summary>
/// <remarks>
/// A URI with no user-info (and, for the configured string, no query or fragment) is returned <b>unchanged</b>,
/// so the common case — and every host key derived from it — is byte-identical to what it was. One that does
/// carry user-info loses it (and, for the configured string, its query / fragment, which a reverse proxy may use
/// for a token). A configured string that is not an absolute URI but contains an <c>@</c> is replaced wholesale:
/// there is no reliable way to find the credential in it.
/// </remarks>
internal static class MTConnectUriRedaction
{
/// <summary>Placeholder for a configured value that cannot be parsed and might carry a credential.</summary>
internal const string Unparseable = "(agentUri withheld: not an absolute URI)";
/// <summary>The display form of a configured <c>AgentUri</c>.</summary>
/// <param name="agentUri">The configured value.</param>
/// <returns>The value with any user-info (and query / fragment) removed.</returns>
public static string Redact(string? agentUri)
{
if (string.IsNullOrWhiteSpace(agentUri))
{
return agentUri ?? string.Empty;
}
if (!Uri.TryCreate(agentUri.Trim(), UriKind.Absolute, out var uri))
{
return agentUri.Contains('@', StringComparison.Ordinal) ? Unparseable : agentUri;
}
if (uri.UserInfo.Length == 0 && uri.Query.Length == 0 && uri.Fragment.Length == 0)
{
return agentUri;
}
var port = uri.IsDefaultPort ? string.Empty : $":{uri.Port}";
var path = uri.AbsolutePath == "/" ? string.Empty : uri.AbsolutePath;
return $"{uri.Scheme}://{uri.Host}{port}{path}";
}
/// <summary>The display form of a request URI built from the <c>AgentUri</c> (its query is the request's own).</summary>
/// <param name="uri">The request URI.</param>
/// <returns>The URI as text, without user-info.</returns>
public static string Redact(Uri uri)
{
ArgumentNullException.ThrowIfNull(uri);
if (!uri.IsAbsoluteUri || uri.UserInfo.Length == 0)
{
return uri.ToString();
}
return new UriBuilder(uri) { UserName = string.Empty, Password = string.Empty }.Uri.ToString();
}
}