Files
SGU-CredentialProvider/src/SGU.AuthBroker/Options/BrokerOptions.cs
T

174 lines
6.4 KiB
C#

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.ProfileTimeoutSeconds is < 2 or > 30 ||
Ntlm.MaxRedirects is < 0 or > 10)
{
throw new InvalidOperationException("NTLM timeout or redirect limits are outside the supported range.");
}
if (Ntlm.MaxProfileBytes is < 32 * 1024 or > 2 * 1024 * 1024)
{
throw new InvalidOperationException("The SGU profile response limit is outside the supported range.");
}
foreach (string profilePath in new[]
{
Ntlm.AuthenticationPath,
Ntlm.AdministrativeProfilePath,
Ntlm.StudentProfilePath,
Ntlm.MenuProfilePath
})
{
if (string.IsNullOrWhiteSpace(profilePath))
{
throw new InvalidOperationException("SGU profile paths are required.");
}
Uri profileUri = new(endpoint, profilePath);
if (profileUri.Scheme != Uri.UriSchemeHttps ||
!string.IsNullOrEmpty(profileUri.UserInfo) ||
!Ntlm.AllowedRedirectHosts.Contains(profileUri.IdnHost, StringComparer.OrdinalIgnoreCase))
{
throw new InvalidOperationException("SGU profile paths must resolve to an allowed HTTPS host.");
}
}
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.");
}
}
if (!string.IsNullOrWhiteSpace(Directory.RemoteDesktopGroupDn) &&
(!Directory.RemoteDesktopGroupDn.StartsWith("CN=", StringComparison.OrdinalIgnoreCase) ||
!Directory.RemoteDesktopGroupDn.EndsWith($",{Directory.BaseDn}", StringComparison.OrdinalIgnoreCase)))
{
throw new InvalidOperationException("RemoteDesktopGroupDn must identify a group beneath BaseDn.");
}
if (string.IsNullOrWhiteSpace(Directory.DefaultCompany) || Directory.DefaultCompany.Length > 64)
{
throw new InvalidOperationException("DefaultCompany is required and must not exceed 64 characters.");
}
}
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; } = 20;
public int ProfileTimeoutSeconds { get; init; } = 10;
public int MaxRedirects { get; init; } = 5;
public string AuthenticationPath { get; init; } = "/psulsa/";
public string AdministrativeProfilePath { get; init; } =
"/psulsa/gadmon/capitalhumano/controlincidencias/incidencias.aspx";
public string StudentProfilePath { get; init; } =
"/psulsa/alumnos/consultainformacionalumnos/consultainformacion.aspx";
public string MenuProfilePath { get; init; } = "/psulsa/menu.aspx";
public int MaxProfileBytes { get; init; } = 512 * 1024;
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 string RemoteDesktopGroupDn { get; init; } = string.Empty;
public string DefaultCompany { get; init; } = "Universidad La Salle";
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)
};
}