Files
itworx-knowledge-hub-Public/tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Operations/LoginRedactionTests.cs
T

56 lines
2.4 KiB
C#

using System.Collections.Concurrent;
using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
namespace ItWorx.KnowledgeHub.IntegrationTests.Operations;
/// <summary>Proves docs/19-observability.md's "Never log ... passwords" rule with a
/// real request through the real logging pipeline, not by code review: every log line
/// emitted while handling a real login attempt (wrong password, so the flow visits the
/// most log-heavy failure path) is captured and asserted to never contain the raw
/// password value.</summary>
[Collection(PostgresTestGroup.Name)]
public sealed class LoginRedactionTests(PostgresContainerFixture postgres) : OwnerSetupTestBase(postgres)
{
[Fact]
public async Task No_log_line_emitted_during_a_login_attempt_contains_the_raw_password()
{
const string secretPassword = "u3q7VeryUniquePasswordNeverLogged9Zx";
var captured = new ConcurrentQueue<string>();
using var loggingFactory = Factory.WithWebHostBuilder(builder =>
builder.ConfigureLogging(logging => logging.AddProvider(new CapturingLoggerProvider(captured))));
using var client = loggingFactory.CreateClient();
// A wrong-password attempt exercises the most log-heavy path (Identity
// failure handling, audit event, rate limiter) without needing the real
// owner password.
await SetupHelper.LoginAsync(client, OwnerEmail, secretPassword);
Assert.DoesNotContain(captured, line => line.Contains(secretPassword, StringComparison.Ordinal));
}
private sealed class CapturingLoggerProvider(ConcurrentQueue<string> sink) : ILoggerProvider
{
public ILogger CreateLogger(string categoryName) => new CapturingLogger(categoryName, sink);
public void Dispose()
{
}
private sealed class CapturingLogger(string categoryName, ConcurrentQueue<string> sink) : ILogger
{
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
public bool IsEnabled(LogLevel logLevel) => true;
public void Log<TState>(
LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
sink.Enqueue($"[{categoryName}] {formatter(state, exception)} {exception}");
}
}
}
}