using SGU.AuthBroker.Core.Authentication; using SGU.AuthBroker.Core.Directory; using SGU.AuthBroker.Core.Identity; using SGU.AuthBroker.Core.Profiles; using Xunit; namespace SGU.AuthBroker.Core.Tests; public sealed class AuthenticationWorkflowTests { [Fact] public async Task PassesTheExactOriginalPasswordToNtlmAndActiveDirectory() { const string original = "Árbol-Exacto-🔐-NoDerivar-27!"; InstitutionalProfile profile = new( EmployeeNumber: "123456", DisplayName: "Persona de Prueba", Email: "persona@lasalle.mx", JobTitle: "DOCENTE", Department: "FACULTAD DE PRUEBA"); CapturingNtlmValidator ntlm = new(NtlmValidationResult.Valid(profile)); CapturingDirectorySynchronizer directory = new(); AuthenticationWorkflow workflow = new(ntlm, directory); AuthenticationFlowResult result = await workflow.AuthenticateAsync( "do123456", original, TestContext.Current.CancellationToken); Assert.Equal(AuthenticationFlowOutcome.Authorized, result.Outcome); Assert.Same(original, ntlm.Password); Assert.Same(original, directory.Password); Assert.Equal("DO123456", ntlm.Identity?.UserName); Assert.Equal(InstitutionalRole.Professor, directory.Identity?.Role); Assert.Same(profile, directory.Profile); } [Fact] public async Task InvalidNtlmCredentialsNeverReachActiveDirectory() { CapturingDirectorySynchronizer directory = new(); AuthenticationWorkflow workflow = new( new CapturingNtlmValidator(NtlmValidationResult.Invalid()), directory); AuthenticationFlowResult result = await workflow.AuthenticateAsync( "AL123456", "Wrong", TestContext.Current.CancellationToken); Assert.Equal(AuthenticationFlowOutcome.InvalidCredentials, result.Outcome); Assert.Null(directory.Password); } [Fact] public async Task NtlmOutageIsReportedAsUnavailableForProviderFallback() { CapturingDirectorySynchronizer directory = new(); AuthenticationWorkflow workflow = new( new CapturingNtlmValidator(NtlmValidationResult.Unavailable()), directory); AuthenticationFlowResult result = await workflow.AuthenticateAsync( "AD123456", "LastKnownPassword", TestContext.Current.CancellationToken); Assert.Equal(AuthenticationFlowOutcome.Unavailable, result.Outcome); Assert.Null(directory.Password); } private sealed class CapturingNtlmValidator(NtlmValidationResult result) : INtlmCredentialValidator { public UserIdentity? Identity { get; private set; } public string? Password { get; private set; } public Task ValidateAsync( UserIdentity identity, string password, CancellationToken cancellationToken) { Identity = identity; Password = password; return Task.FromResult(result); } } private sealed class CapturingDirectorySynchronizer : IActiveDirectorySynchronizer { public UserIdentity? Identity { get; private set; } public string? Password { get; private set; } public InstitutionalProfile? Profile { get; private set; } public Task SynchronizeAsync( UserIdentity identity, InstitutionalProfile? profile, string password, CancellationToken cancellationToken) { Identity = identity; Profile = profile; Password = password; return Task.FromResult(new DirectorySyncResult( "LCI", identity.UserName, $"{identity.UserName}@lci.lasalle.mx", true, false)); } } }