159 lines
5.6 KiB
C#
159 lines
5.6 KiB
C#
using Omika.Diagnostics.Security;
|
|
using Omika.Diagnostics.Tests.Fakes;
|
|
using Omika.Diagnostics.Users;
|
|
|
|
namespace Omika.Diagnostics.Tests;
|
|
|
|
/// <summary>
|
|
/// T23 — Authentification hybride : utilisateurs PostgreSQL en priorité, sinon opérateurs
|
|
/// bootstrap. Échec générique (jamais « inconnu » vs « désactivé »). DB autoritaire.
|
|
/// </summary>
|
|
public class DiagnosticUserAuthenticatorTests
|
|
{
|
|
private const string Password = "correct-password-123";
|
|
|
|
private static DiagnosticUserRecord DbUser(string username, string password, string role = DiagnosticRoles.Support, bool enabled = true) =>
|
|
new()
|
|
{
|
|
Id = 1,
|
|
Username = username,
|
|
PasswordHash = DiagnosticPasswordHasher.Hash(password),
|
|
Role = role,
|
|
Enabled = enabled
|
|
};
|
|
|
|
private static DiagnosticOperator Operator(string username, string password, params string[] roles) =>
|
|
new(username, DiagnosticPasswordHasher.Hash(password), roles);
|
|
|
|
// -------------------------------------------------- DB prioritaire ----
|
|
[Fact]
|
|
public async Task DbUser_CorrectPassword_Succeeds()
|
|
{
|
|
var repo = new FakeDiagnosticUserRepository();
|
|
repo.Seed(DbUser("alice", Password, DiagnosticRoles.Admin));
|
|
|
|
DiagnosticLoginResult result = await DiagnosticUserAuthenticator.AuthenticateAsync(
|
|
repo, Array.Empty<DiagnosticOperator>(), "alice", Password);
|
|
|
|
Assert.True(result.Success);
|
|
Assert.True(result.IsDatabaseUser);
|
|
Assert.Equal("alice", result.Username);
|
|
Assert.Contains(DiagnosticRoles.Admin, result.Roles);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DbUser_WrongPassword_Fails()
|
|
{
|
|
var repo = new FakeDiagnosticUserRepository();
|
|
repo.Seed(DbUser("alice", Password));
|
|
|
|
DiagnosticLoginResult result = await DiagnosticUserAuthenticator.AuthenticateAsync(
|
|
repo, Array.Empty<DiagnosticOperator>(), "alice", "wrong-password-123");
|
|
|
|
Assert.False(result.Success);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DbUser_Disabled_Fails_EvenWithCorrectPassword()
|
|
{
|
|
var repo = new FakeDiagnosticUserRepository();
|
|
repo.Seed(DbUser("alice", Password, enabled: false));
|
|
|
|
DiagnosticLoginResult result = await DiagnosticUserAuthenticator.AuthenticateAsync(
|
|
repo, Array.Empty<DiagnosticOperator>(), "alice", Password);
|
|
|
|
Assert.False(result.Success);
|
|
}
|
|
|
|
// -------------------------------------------------- fallback opérateurs ----
|
|
[Fact]
|
|
public async Task UnknownUser_OperatorFallback_Succeeds()
|
|
{
|
|
var repo = new FakeDiagnosticUserRepository();
|
|
|
|
DiagnosticLoginResult result = await DiagnosticUserAuthenticator.AuthenticateAsync(
|
|
repo,
|
|
new[] { Operator("bootstrap", Password, DiagnosticRoles.Admin) },
|
|
"bootstrap",
|
|
Password);
|
|
|
|
Assert.True(result.Success);
|
|
Assert.False(result.IsDatabaseUser);
|
|
Assert.Equal("bootstrap", result.Username);
|
|
Assert.Contains(DiagnosticRoles.Admin, result.Roles);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UnknownUser_NoOperators_Fails()
|
|
{
|
|
var repo = new FakeDiagnosticUserRepository();
|
|
|
|
DiagnosticLoginResult result = await DiagnosticUserAuthenticator.AuthenticateAsync(
|
|
repo, Array.Empty<DiagnosticOperator>(), "inconnu", Password);
|
|
|
|
Assert.False(result.Success);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Operator_WrongPassword_Fails()
|
|
{
|
|
var repo = new FakeDiagnosticUserRepository();
|
|
|
|
DiagnosticLoginResult result = await DiagnosticUserAuthenticator.AuthenticateAsync(
|
|
repo,
|
|
new[] { Operator("bootstrap", Password, DiagnosticRoles.Admin) },
|
|
"bootstrap",
|
|
"wrong-password-123");
|
|
|
|
Assert.False(result.Success);
|
|
}
|
|
|
|
// -------------------------------------------------- priorité DB (§36) ----
|
|
[Fact]
|
|
public async Task DbIsAuthoritative_DisabledDbUser_DoesNotFallbackToOperator()
|
|
{
|
|
var repo = new FakeDiagnosticUserRepository();
|
|
repo.Seed(DbUser("dup", Password, enabled: false));
|
|
|
|
// Même username existe aussi en configuration (bootstrap) avec un mot de passe valide :
|
|
// la DB doit rester autoritaire → échec, PAS de fallback.
|
|
DiagnosticLoginResult result = await DiagnosticUserAuthenticator.AuthenticateAsync(
|
|
repo,
|
|
new[] { Operator("dup", Password, DiagnosticRoles.Admin) },
|
|
"dup",
|
|
Password);
|
|
|
|
Assert.False(result.Success);
|
|
}
|
|
|
|
// -------------------------------------------------- cas limites ----
|
|
[Theory]
|
|
[InlineData("", "password-123")]
|
|
[InlineData(" ", "password-123")]
|
|
[InlineData("alice", "")]
|
|
[InlineData("alice", " ")]
|
|
public async Task BlankInput_Fails(string username, string password)
|
|
{
|
|
var repo = new FakeDiagnosticUserRepository();
|
|
repo.Seed(DbUser("alice", Password));
|
|
|
|
DiagnosticLoginResult result = await DiagnosticUserAuthenticator.AuthenticateAsync(
|
|
repo, Array.Empty<DiagnosticOperator>(), username, password);
|
|
|
|
Assert.False(result.Success);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DbLookup_CaseInsensitive()
|
|
{
|
|
var repo = new FakeDiagnosticUserRepository();
|
|
repo.Seed(DbUser("Alice.Dupont", Password, DiagnosticRoles.Support));
|
|
|
|
DiagnosticLoginResult result = await DiagnosticUserAuthenticator.AuthenticateAsync(
|
|
repo, Array.Empty<DiagnosticOperator>(), "alice.dupont", Password);
|
|
|
|
Assert.True(result.Success);
|
|
Assert.Equal("Alice.Dupont", result.Username);
|
|
}
|
|
}
|