Add SGU credential provider and authentication broker

This commit is contained in:
2026-08-31 17:48:18 -06:00
parent 5e216f42a4
commit 1f43f200b4
50 changed files with 3226 additions and 236 deletions
@@ -0,0 +1,87 @@
using System.Net;
using System.Text;
using System.Text.Json;
using Xunit;
namespace SGU.CredentialProvider.Tests;
public sealed class BrokerClientTests
{
[Fact]
public async Task SendsTheOriginalPasswordWithoutDerivation()
{
const string original = "Exacta-Árbol-🔐-27!";
CapturingHandler handler = new(HttpStatusCode.OK, """
{"domain":"LCI","username":"DO123456","upn":"DO123456@lci.lasalle.mx","created":true,"moved":false}
""");
using BrokerClient client = new(CreateSettings(), handler);
BrokerDecision decision = await client.AuthenticateAsync(
"DO123456",
original,
TestContext.Current.CancellationToken);
Assert.Equal(BrokerDecisionKind.Authorized, decision.Kind);
using JsonDocument requestJson = JsonDocument.Parse(handler.RequestBody);
Assert.Equal(original, requestJson.RootElement.GetProperty("password").GetString());
Assert.DoesNotContain("derived", handler.RequestBody, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task ExplicitUnauthorizedResponseStopsTheLogin()
{
using BrokerClient client = new(
CreateSettings(),
new CapturingHandler(HttpStatusCode.Unauthorized, "{\"code\":\"INVALID_INSTITUTIONAL_CREDENTIALS\"}"));
BrokerDecision decision = await client.AuthenticateAsync(
"AL123456",
"Wrong",
TestContext.Current.CancellationToken);
Assert.Equal(BrokerDecisionKind.InvalidCredentials, decision.Kind);
}
[Fact]
public async Task BrokerOutageRequestsWindowsCachedCredentialFallback()
{
using BrokerClient client = new(
CreateSettings(),
new CapturingHandler(HttpStatusCode.ServiceUnavailable, "{\"code\":\"NTLM_UPSTREAM_ERROR\"}"));
BrokerDecision decision = await client.AuthenticateAsync(
"AD123456",
"LastKnown",
TestContext.Current.CancellationToken);
Assert.Equal(BrokerDecisionKind.Unavailable, decision.Kind);
}
private static ProviderSettings CreateSettings() => new()
{
BrokerEndpoint = new Uri("https://broker.example.test/v1/authenticate"),
DomainNetbios = "LCI",
TimeoutSeconds = 5,
ClientCertificateThumbprint = new string('A', 40),
ServerCertificateThumbprint = new string('B', 40)
};
private sealed class CapturingHandler(HttpStatusCode statusCode, string responseBody) : HttpMessageHandler
{
public string RequestBody { get; private set; } = string.Empty;
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
RequestBody = request.Content is null
? string.Empty
: await request.Content.ReadAsStringAsync(cancellationToken);
return new HttpResponseMessage(statusCode)
{
Content = new StringContent(responseBody, Encoding.UTF8, "application/json")
};
}
}
}
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0-windows</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\SGU.CredentialProvider\SGU.CredentialProvider.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
<PackageReference Include="xunit.v3" Version="4.0.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="4.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>