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
+114
View File
@@ -0,0 +1,114 @@
using SGU.AuthBroker.Core.Identity;
namespace SGU.AuthBroker.Options;
public sealed class BrokerOptions
{
public const string SectionName = "Broker";
public TlsOptions Tls { get; init; } = new();
public NtlmOptions Ntlm { get; init; } = new();
public ActiveDirectoryOptions Directory { get; init; } = new();
public void Validate()
{
if (Tls.AllowedClientThumbprints.Length == 0 ||
Tls.AllowedClientThumbprints.Any(value => !IsCertificateThumbprint(value)))
{
throw new InvalidOperationException("At least one client certificate thumbprint is required.");
}
if (!Uri.TryCreate(Ntlm.Endpoint, UriKind.Absolute, out Uri? endpoint) || endpoint.Scheme != Uri.UriSchemeHttps)
{
throw new InvalidOperationException("The institutional NTLM endpoint must be an absolute HTTPS URL.");
}
if (Ntlm.AllowedRedirectHosts.Length == 0 ||
!Ntlm.AllowedRedirectHosts.Contains(endpoint.IdnHost, StringComparer.OrdinalIgnoreCase))
{
throw new InvalidOperationException("The NTLM endpoint host must be present in AllowedRedirectHosts.");
}
if (Ntlm.TimeoutSeconds is < 2 or > 60 || Ntlm.MaxRedirects is < 0 or > 10)
{
throw new InvalidOperationException("NTLM timeout or redirect limits are outside the supported range.");
}
if (string.IsNullOrWhiteSpace(Directory.LdapHost) ||
string.IsNullOrWhiteSpace(Directory.BaseDn) ||
string.IsNullOrWhiteSpace(Directory.DomainNetbios) ||
string.IsNullOrWhiteSpace(Directory.UpnSuffix))
{
throw new InvalidOperationException("Active Directory connection and domain settings are required.");
}
foreach (InstitutionalRole role in Enum.GetValues<InstitutionalRole>())
{
string ouDn = Directory.GetOuDn(role);
if (string.IsNullOrWhiteSpace(ouDn))
{
throw new InvalidOperationException($"An OU mapping is required for {role}.");
}
if (!ouDn.EndsWith($",{Directory.BaseDn}", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException($"The OU mapping for {role} must be beneath BaseDn.");
}
}
}
private static bool IsCertificateThumbprint(string value)
{
string normalized = value.Replace(" ", string.Empty, StringComparison.Ordinal);
return normalized.Length == 40 && normalized.All(Uri.IsHexDigit);
}
}
public sealed class TlsOptions
{
public string[] AllowedClientThumbprints { get; init; } = [];
public bool CheckCertificateRevocation { get; init; } = true;
}
public sealed class NtlmOptions
{
public string Endpoint { get; init; } = "https://sgu.ulsa.edu.mx/";
public string Domain { get; init; } = string.Empty;
public int TimeoutSeconds { get; init; } = 15;
public int MaxRedirects { get; init; } = 5;
public string[] AllowedRedirectHosts { get; init; } = ["sgu.ulsa.edu.mx"];
}
public sealed class ActiveDirectoryOptions
{
public string LdapHost { get; init; } = "localhost";
public string BaseDn { get; init; } = "DC=lci,DC=lasalle,DC=mx";
public string DomainNetbios { get; init; } = "LCI";
public string UpnSuffix { get; init; } = "lci.lasalle.mx";
public string ProfessorOuDn { get; init; } = "OU=Docentes,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx";
public string StudentOuDn { get; init; } = "OU=Alumnos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx";
public string AdministrativeOuDn { get; init; } = "OU=Administrativos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx";
public bool CreateMissingOus { get; init; }
public string GetOuDn(InstitutionalRole role) => role switch
{
InstitutionalRole.Professor => ProfessorOuDn,
InstitutionalRole.Student => StudentOuDn,
InstitutionalRole.Administrative => AdministrativeOuDn,
_ => throw new ArgumentOutOfRangeException(nameof(role), role, null)
};
}