174 lines
6.4 KiB
C#
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)
|
|
};
|
|
}
|