eabf270d71
Resolve all 622 issues flagged by the enhanced CommentChecker: add missing <returns> tags (incl. the standard phrasing on non-generic Task methods), add missing <summary> tags, and replace misused/redundant <inheritdoc/> on members that override or implement nothing with real documentation. Documentation-only — no behavior change; solution builds clean.
108 lines
4.4 KiB
C#
108 lines
4.4 KiB
C#
using System.Text.RegularExpressions;
|
|
using ZB.MOM.WW.Audit;
|
|
using ZB.MOM.WW.ScadaBridge.AuditLog.Payload;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
|
using static ZB.MOM.WW.ScadaBridge.AuditLog.Payload.AuditRedactionPrimitives;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Redaction;
|
|
|
|
/// <summary>
|
|
/// Minimal always-safe <see cref="IAuditRedactor"/> fallback for composition
|
|
/// roots that bypass the full <see cref="ScadaBridgeAuditRedactor"/>.
|
|
/// Performs line-oriented HTTP header
|
|
/// redaction for the always-sensitive defaults (Authorization, X-Api-Key,
|
|
/// Cookie, Set-Cookie) on the <c>RequestSummary</c> / <c>ResponseSummary</c>
|
|
/// fields carried inside <c>ZB.MOM.WW.Audit.AuditEvent.DetailsJson</c>. Does NOT
|
|
/// perform body-regex redaction, SQL-parameter redaction, or truncation — those
|
|
/// need <see cref="ScadaBridgeAuditRedactor"/> with live options. Contract:
|
|
/// over-redact safely, never throw, never miss a header on the default
|
|
/// sensitive list.
|
|
/// </summary>
|
|
public sealed class SafeDefaultAuditRedactor : IAuditRedactor
|
|
{
|
|
/// <summary>Singleton instance — the redactor is stateless and side-effect-free.</summary>
|
|
public static SafeDefaultAuditRedactor Instance { get; } = new SafeDefaultAuditRedactor();
|
|
|
|
private static readonly string[] DefaultHeaderRedactList =
|
|
{
|
|
"Authorization",
|
|
"X-Api-Key",
|
|
"Cookie",
|
|
"Set-Cookie",
|
|
};
|
|
|
|
private static readonly Regex HeaderRegex = new(
|
|
@"(?<name>[A-Za-z][A-Za-z0-9\-_]*)\s*:\s*(?<value>[^\r\n]*)",
|
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
|
|
private SafeDefaultAuditRedactor() { }
|
|
|
|
/// <summary>
|
|
/// Applies line-oriented header redaction to the default sensitive headers
|
|
/// (<c>Authorization</c>, <c>X-Api-Key</c>, <c>Cookie</c>, <c>Set-Cookie</c>)
|
|
/// found in <c>RequestSummary</c> and <c>ResponseSummary</c> inside
|
|
/// <paramref name="rawEvent"/>.<c>DetailsJson</c>. Never throws; over-redacts on
|
|
/// any internal failure.
|
|
/// </summary>
|
|
/// <param name="rawEvent">The audit event whose details JSON is to be redacted.</param>
|
|
/// <returns>A new <see cref="AuditEvent"/> with sensitive headers replaced by the redacted marker, or an over-redacted sentinel on failure.</returns>
|
|
public AuditEvent Apply(AuditEvent rawEvent)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(rawEvent);
|
|
|
|
// Fast path: no DetailsJson means no summaries to scrub.
|
|
if (string.IsNullOrEmpty(rawEvent.DetailsJson))
|
|
{
|
|
return rawEvent;
|
|
}
|
|
|
|
try
|
|
{
|
|
var d = AuditDetailsCodec.Deserialize(rawEvent.DetailsJson);
|
|
var scrubbed = d with
|
|
{
|
|
RequestSummary = RedactHeaders(d.RequestSummary),
|
|
ResponseSummary = RedactHeaders(d.ResponseSummary),
|
|
};
|
|
return rawEvent with { DetailsJson = AuditDetailsCodec.Serialize(scrubbed) };
|
|
}
|
|
catch
|
|
{
|
|
// Over-redact: suppress ALL sensitive free-text fields so a failure
|
|
// on any internal path never leaks the original. The contract is
|
|
// "never throw." Uses the shared OverRedactedEventMarker so all
|
|
// redactor safety-nets emit the same sentinel string.
|
|
var safe = new AuditDetails
|
|
{
|
|
RequestSummary = OverRedactedEventMarker,
|
|
ResponseSummary = OverRedactedEventMarker,
|
|
ErrorDetail = OverRedactedEventMarker,
|
|
ErrorMessage = OverRedactedEventMarker,
|
|
Extra = OverRedactedEventMarker,
|
|
PayloadTruncated = true,
|
|
};
|
|
return rawEvent with { DetailsJson = AuditDetailsCodec.Serialize(safe) };
|
|
}
|
|
}
|
|
|
|
private static string? RedactHeaders(string? summary)
|
|
{
|
|
if (string.IsNullOrEmpty(summary)) return summary;
|
|
|
|
return HeaderRegex.Replace(summary, m =>
|
|
{
|
|
var name = m.Groups["name"].Value;
|
|
foreach (var sensitive in DefaultHeaderRedactList)
|
|
{
|
|
if (string.Equals(name, sensitive, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// Use the shared RedactedMarker so line-format and JSON-format
|
|
// header redaction emit the same sentinel string.
|
|
return $"{name}: {RedactedMarker}";
|
|
}
|
|
}
|
|
return m.Value;
|
|
});
|
|
}
|
|
}
|