diff --git a/LdapLoginLib/Data/LdapConfig.cs b/LdapLoginLib/Data/LdapConfig.cs new file mode 100644 index 0000000..26f8942 --- /dev/null +++ b/LdapLoginLib/Data/LdapConfig.cs @@ -0,0 +1,102 @@ +using Nini.Config; +using System.Net; +using System.DirectoryServices.Protocols; + +namespace LdapLoginLib.Data +{ + internal class LdapConfig + { + // DN Config (route) + private static string? UserDn { get; set; } = null; + private static string? AdminDn { get; set; } = null; + + //Server Config + private static string? ServerIP { get; set; } = null; + private static int? ServerPort { get; set; } = null; + + // Admin Config + private static string? AdminUser { get; set; } = null; + private static string? AdminPassword { get; set; } = null; + + + static LdapConfig() + { + //string? mode = Environment.GetEnvironmentVariable("Mode"); + ReadCredentials(); + } + + private static void ReadCredentials() + { + try + { + var filePath = Path.Combine("C:\\Users\\Administrator", ".hims", "iop"); + + //Validacion manual antes de leer + if (File.Exists(filePath) == false) //Exist = false + { + //Devolver error + throw new Exception("Error: conexion con el servidor de identidad."); + } + + IConfigSource source = new Nini.Config.IniConfigSource(filePath); + var ldap_config = source.Configs["ldap"]; + + int _port; + if (int.TryParse(ldap_config.Get("port"), out _port)) + { + ServerPort = _port; + } + + ServerIP = ldap_config.Get("server"); + UserDn = ldap_config.Get("user_dn"); + AdminDn = ldap_config.Get("admin_dn"); + AdminUser = ldap_config.Get("admin_user"); + AdminPassword = ldap_config.Get("admin_pass"); + + if (String.IsNullOrEmpty(ServerIP) || + ServerPort == null || + String.IsNullOrEmpty(UserDn) || + String.IsNullOrEmpty(AdminDn) || + String.IsNullOrEmpty(AdminUser) || + String.IsNullOrEmpty(AdminPassword) + ) + { + throw new Exception("Error interno. Credenciales invalidas."); + } + + } + catch (IOException ioEx) + { + Console.WriteLine(ioEx.Message); + throw; + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + throw; + } + + } + + + + internal static NetworkCredential AdminCredential() + { + return new NetworkCredential($"uid={AdminUser},{AdminDn}", AdminPassword); + } + + internal static NetworkCredential UserCredentials(string username, string password) + { + return new NetworkCredential($"uid={username},{UserDn}", password); + } + + + internal static LdapConnection Connection() + { + return new LdapConnection($"{ServerIP}:{ServerPort}"); + } + + + } + +} diff --git a/LdapLoginLib/LDAP.cs b/LdapLoginLib/LDAP.cs new file mode 100644 index 0000000..8d36d7b --- /dev/null +++ b/LdapLoginLib/LDAP.cs @@ -0,0 +1,200 @@ +using LdapLoginLib.Data; +using LdapLoginLib.Models; +using System.DirectoryServices.Protocols; + +namespace LdapLoginLib +{ + public class LDAP + { + private readonly LdapConfig _ldap = new(); + + public bool Login(string password, string? document = null, string? username = null, string? email = null) + { + + using (LdapConnection ldapConnection = LdapConfig.Connection()) + { + try + { + UserInfo userInfo = GetUserInfo( + document: document, + username: username, + email: email); + + //Si el usuario está inactivo... + if (userInfo.Activo == false) return false; + + // Set LDAP connection options + ldapConnection.SessionOptions.SecureSocketLayer = false; + ldapConnection.AuthType = AuthType.Basic; + ldapConnection.Credential = LdapConfig.UserCredentials(userInfo.Usuario!, password); + + // Attempt to bind (authenticate) the user + ldapConnection.Bind(); + + return true; + + } + catch (LdapException ldapEx) + { + //Console.WriteLine($"Authentication failed: {ldapEx.Message}"); + throw new Exception(_getErrorMessage(ldapEx.ErrorCode, ldapEx.Message)); + } + catch + { + //Console.WriteLine($"An error occurred: {ex.Message}"); + //throw new Exception($"Ocurrió un error: {ex.Message}"); + throw; + } + + } + } + + public static UserInfo GetUserInfo(string? document = null, string? username = null, string? email = null) + { + if ( + string.IsNullOrEmpty(document?.Trim()) && + string.IsNullOrEmpty(username?.Trim()) && + string.IsNullOrEmpty(email?.Trim()) + ) + { + throw new Exception("Usuario, correo o documento son requeridos"); + } + + #region Query String Builder + var ldapFilterArgs = new List(); + + //uid=APPuser163,ou=services,o=unal.edu.co + if (string.IsNullOrEmpty(document?.Trim()) == false) + { + ldapFilterArgs.Add($"(employeeNumber={document.Trim()})"); + } + + if (string.IsNullOrEmpty(username?.Trim()) == false) + { + ldapFilterArgs.Add($"(uid={username.Trim()})"); + } + + if (string.IsNullOrEmpty(email?.Trim()) == false) + { + ldapFilterArgs.Add($"(mail={email.Trim()})"); + } + + //ldapFilter.Append("ou=People,o=unal.edu.co"); + + #endregion + + using (LdapConnection ldapConnection = LdapConfig.Connection()) + { + try + { + // Admin login + ldapConnection.SessionOptions.SecureSocketLayer = false; + ldapConnection.AuthType = AuthType.Basic; + ldapConnection.Credential = LdapConfig.AdminCredential(); + ldapConnection.Bind(); + + + //Query + string searchBase = $"o=unal.edu.co"; + string[] attributesToReturn = { + "uid", + "employeeNumber", + "cn", + "givenName", + "sn", + "mail", + "inetUserStatus", + "o", + "nuip" + }; + + string ldapFilter = $"(&{String.Join("", ldapFilterArgs.ToArray())})"; + + SearchRequest searchRequest = new( + searchBase, + ldapFilter, + SearchScope.Subtree, + attributesToReturn + ); + + searchRequest.SizeLimit = 1; // Buscar solo el primero + + SearchResponse searchResponse = (SearchResponse)ldapConnection.SendRequest(searchRequest); + + if (searchResponse != null && searchResponse.Entries.Count > 0) + { + SearchResultEntry entry = searchResponse.Entries[0]; + + LdapUserInfo ldapUserInfo = new() + { + Uid = entry.Attributes["uid"]?[0].ToString(), + EmployeeNumber = entry.Attributes["employeeNumber"]?[0].ToString(), + Cn = entry.Attributes["cn"]?[0].ToString(), + GivenName = entry.Attributes["givenName"]?[0].ToString(), + Sn = entry.Attributes["sn"]?[0].ToString(), + Mail = entry.Attributes["mail"]?[0].ToString(), + InetUserStatus = entry.Attributes["inetUserStatus"][0].ToString(), + O = entry.Attributes["o"]?[0].ToString(), + MailAlternateAddress = entry.Attributes["mailAlternateAddress"]?[0].ToString(), + //IsActive = entry.Attributes["uid"][0].ToString(), + //NUIP = entry.Attributes["NUIP"]?[0].ToString(), + }; + + ldapUserInfo.IsActive = _isActive(ldapUserInfo.InetUserStatus); + + return (UserInfo)ldapUserInfo; + + } + else + { + throw new Exception("Usuario no encontrado o no se recuperaron datos."); + } + + } + catch (LdapException ldapEx) + { + Console.WriteLine($"Authentication failed: {ldapEx.Message}"); + Console.WriteLine($"Authentication failed: {ldapEx.ErrorCode}"); + Console.WriteLine($"Authentication failed: {ldapEx.ServerErrorMessage}"); + throw new Exception(ldapEx.Message); + } + catch // (Exception ex) + { + //Console.WriteLine($"An error occurred: {ex.Message}"); + //throw new Exception($"Ocurrió un error: {ex.Message}"); + throw; + } + } + + } + + + private static bool _isActive(string? inetUserStatus) + { + if (String.IsNullOrEmpty(inetUserStatus?.Trim()) == false) + { + return inetUserStatus.ToLower().Trim() == "active" ? true : false; + } + return false; + } + + private static string _getErrorMessage(int errorCode, string errorMessage) + { + // Map LDAP error codes to error messages + + switch (errorCode) + { + case 49: + return "Error de credenciales: nombre de usuario o contraseña incorrectos"; + case 52: + return "Error de autenticación: cuenta está deshabilitada"; + case 81: + return "Error de servidor: no disponible"; + default: + return errorMessage; + } + } + + + } +} diff --git a/LdapLoginLib/LdapLoginLib.csproj b/LdapLoginLib/LdapLoginLib.csproj index 4c2dabd..46b6dcd 100644 --- a/LdapLoginLib/LdapLoginLib.csproj +++ b/LdapLoginLib/LdapLoginLib.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -7,8 +7,9 @@ - - + + + diff --git a/LdapLoginLib/LoginLib.cs b/LdapLoginLib/LoginLib.cs deleted file mode 100644 index 6f9035f..0000000 --- a/LdapLoginLib/LoginLib.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System.DirectoryServices.Protocols; - -namespace LdapLoginLib -{ - public class LoginLib - { - private const string _ldapServer = "10.31.3.13"; - private const int _ldapPort = 389; - - private const string _ldapDn = "ou=People,o=unal.edu.co"; //uid=pdocente, - //string ldapPassword = "TJBjzn64"; - - - - public static bool Login(string uid, string password, string ldapDn = _ldapDn) - { - - using (LdapConnection ldapConnection = new($"{_ldapServer}:{_ldapPort}")) - { - try - { - string ldapUserDn = $"uid={uid},{_ldapDn}"; - - // Set LDAP connection options - ldapConnection.SessionOptions.SecureSocketLayer = false; - ldapConnection.AuthType = AuthType.Basic; - ldapConnection.Credential = new System.Net.NetworkCredential(ldapUserDn, password); - - // Attempt to bind (authenticate) the user - ldapConnection.Bind(); - - return _userIsActive(ldapConnection, ldapUserDn); - } - catch (LdapException ldapEx) - { - //Console.WriteLine($"Authentication failed: {ldapEx.Message}"); - throw new Exception(_getErrorMessage(ldapEx.ErrorCode, ldapEx.Message)); - } - catch (Exception ex) - { - //Console.WriteLine($"An error occurred: {ex.Message}"); - throw new Exception($"Ocurrió un error: {ex.Message}"); - } - - } - } - - private static bool _userIsActive(LdapConnection ldapConnection, string ldapUserDn) - { - //ldapUserDn = $"uid=acbuitragoc,{_ldapDn}"; - SearchRequest searchRequest = new( - ldapUserDn, - "(objectClass=*)", - SearchScope.Base, - "InetUserStatus" - ); - - SearchResponse searchResponse = (SearchResponse)ldapConnection.SendRequest(searchRequest); - - if (searchResponse.Entries.Count > 0) - { - SearchResultEntry entry = searchResponse.Entries[0]; - - string? inetUserStatus = entry.Attributes["inetUserStatus"][0].ToString(); - - if (inetUserStatus != null) - { - return inetUserStatus.ToLower().Trim() == "active" ? true : false; - } - throw new Exception(); - } - else - { - throw new Exception($"Usuario o atributo no encontrado."); - } - } - - - private static LdapUser _getUserData(LdapConnection ldapConnection, string ldapUserDn, string[] attributesToReturn) - { - - return new LdapUser(); - - - //SearchRequest searchRequest = new( - // searchBase, - // ldapFilter, - // SearchScope.Subtree, - // attributesToReturn - //); - - //SearchResponse searchResponse = (SearchResponse)ldapConnection.SendRequest(searchRequest); - - - //if (searchResponse != null && searchResponse.Entries.Count > 0) - //{ - // SearchResultEntry entry = searchResponse.Entries[0]; - - // // Access and process user attributes here - // foreach (DirectoryAttribute attribute in entry.Attributes.Values) - // { - // string attributeName = attribute.Name; - // string[] attributeValues = (string[])attribute.GetValues(typeof(string)); - - // // Process or display attribute values as needed - // Console.WriteLine($"{attributeName}: {string.Join(", ", attributeValues)}"); - // } - //} - //else - //{ - // throw new Exception($"Usuario o atributos no encontrados."); - //} - } - - - private static string _getErrorMessage(int errorCode, string errorMessage) - { - // Map LDAP error codes to error messages - - switch (errorCode) - { - case 49: - return "Error de credenciales: nombre de usuario o contraseña incorrectos"; - case 52: - return "Error de autenticación: cuenta está deshabilitada"; - case 81: - return "Error de servidor: no disponible"; - default: - return errorMessage; - } - } - } -} \ No newline at end of file diff --git a/LdapLoginLib/LdapUser.cs b/LdapLoginLib/Models/LdapUserInfo.cs similarity index 78% rename from LdapLoginLib/LdapUser.cs rename to LdapLoginLib/Models/LdapUserInfo.cs index c5a4fde..a92f5c5 100644 --- a/LdapLoginLib/LdapUser.cs +++ b/LdapLoginLib/Models/LdapUserInfo.cs @@ -4,15 +4,22 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace LdapLoginLib +namespace LdapLoginLib.Models { - public class LdapUser + public class LdapUserInfo { /// /// The unique identifier for the user (mandatory). /// Example: "jdoe" /// - public string Uid { get; set; } + public string? Uid { get; set; } + + /// + /// Doc. Identidad + /// The employee's unique identification number. + /// Example: "E12345" + /// + public string? EmployeeNumber { get; set; } /// /// The common name of the user. @@ -21,12 +28,14 @@ namespace LdapLoginLib public string? Cn { get; set; } /// + /// Nombres full /// The user's given name. /// Example: "John" /// public string? GivenName { get; set; } /// + /// Apellidos /// The user's surname. /// Example: "Doe" /// @@ -36,26 +45,38 @@ namespace LdapLoginLib /// The user's email address. /// Example: "jdoe@example.com" /// - public string? Mail { get; set; } + public string? Mail { get; set; } + + //TODO: /// - /// The status of the user's internet account. - /// Example: "Active" + /// The user's ALTERNATIVE email address. + /// Example: "jdoe@example.com" /// - public string? InetUserStatus { get; set; } - + public string? MailAlternateAddress { get; set; } + + /// /// The organization the user belongs to. /// Example: "Acme Inc.", currently "Sede" /// public string? O { get; set; } + /// + /// The status of the user's internet account. + /// Example: "Active" + /// + public string? InetUserStatus { get; set; } + /// /// The status of the user's account as boolean. /// Example: true or false /// public bool? IsActive { get; set; } = null; + + + } @@ -75,7 +96,8 @@ namespace LdapLoginLib /// The type of employee (e.g., full-time, part-time). /// Example: "Full-Time", currently numbers /// - public string? EmployeeType { get; set; } + //public string? EmployeeType { get; set; } + /// /// The business category of the user. @@ -83,11 +105,12 @@ namespace LdapLoginLib /// public string? BusinessCategory { get; set; } + // Definidos por LDAP + // No encontrado /// - /// The employee's unique identification number. - /// Example: "E12345" + /// Numero unico identificacion personal /// - public string? EmployeeNumber { get; set; } + //public string? NUIP { get; set; } = null; /// /// The license information for the user. diff --git a/LdapLoginLib/Models/UserInfo.cs b/LdapLoginLib/Models/UserInfo.cs new file mode 100644 index 0000000..5c04b12 --- /dev/null +++ b/LdapLoginLib/Models/UserInfo.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LdapLoginLib.Models +{ + public class UserInfo + { + #region Propiedades + + /// + /// {Uid} Uid - Nombre de usuario o Login + /// + /// + /// xxx + /// + public string? Usuario { get; set; } + + /// + /// {EmployeeNumber} Documento de identidad. + /// + /// + /// Cadena de caracteres, sólo números y letras en + /// maýuscula, sin espacios, sin ceros al inicio.Con + /// la consolidación de identidades, este atributo + /// queda ÚNICO en el directorio a nivel nacional. + /// Puede contener un guion y un dígito + /// consecutivo que asociado en las cuentas hijas + /// de un usuario. La cuenta principal no tendrá este sufijo + /// + public string? NumeroDocumento { get; set; } + + /// + /// {Cn} Nombres y apellidos completos + /// + /// + /// Cadena de caracteres, cada palabra con mayúscula inicial + /// + public string? NombreCompleto { get; set; } + + /// + /// {GivenName} Nombres completos + /// + /// + /// Cadena de caracteres, cada palabra con mayúscula inicial + /// + public string? Nombres { get; set; } + + /// + /// {Sn} Apellidos completos + /// + /// + /// Cadena de caracteres, cada palabra con mayúscula inicia + /// + public string? Apellidos { get; set; } + + /// + /// {Mail} Dirección de correo UNAL + /// + /// + /// Cadena de caracteres en minúscula, uid+"@unal.edu.co" + /// + public string? Correo { get; set; } + + /// + /// {MailAlternateAddress} Correo alterno + /// + public string? CorreoAlt { get; set; } + + /// + /// {O} Sede + /// + /// + /// Cadena de caracteres, según tabla 11.1 + /// + public string? Sede { get; set; } + + /// + /// {inetUserStatus} Estado identidad + /// + /// + /// Active / Inactive, uso propio del directorio. + /// A diferencia del campo nsAccountLock este + /// campo es informativo.Anteriormente usado + /// en la suite de servicio de correo Sun JES. + /// + public string? Estado { get; set; } + + /// + /// {IsActive} + /// Indica el estado, si esta activo o inactivo + /// + public bool Activo { get; set; } + + #endregion + + public static explicit operator UserInfo(LdapUserInfo userInfo) + { + //if (userInfo == null) throw new Exception(); + + return new UserInfo + { + Usuario = userInfo.Uid, + NumeroDocumento = userInfo.EmployeeNumber, + NombreCompleto = userInfo.Cn, + Nombres = userInfo.GivenName, + Apellidos = userInfo.Sn, + Correo = userInfo.Mail, + CorreoAlt = userInfo.MailAlternateAddress, + Sede = userInfo.O, + Estado = userInfo.InetUserStatus, + Activo = userInfo.IsActive ?? false + }; + } + + + } + + /******************************************** + * * + * Discared / not in used * + * * + ******************************************** + + /// + /// {EmployeeType} Tipos de cuentas en LDAP + /// + /// + /// Ver tabla 4.3, pagina 12 - Lineamientos Identidad + /// + //public string? TipoUsuario { get; set; } + + /// + /// {EmployeeNumber} xxxxxxxxx + /// + /// + /// xxx + /// + + + // Definidos por LDAP + // No encontrado + /// + /// Numero unico identificacion personal + /// + //public string? NUIP { get; set; } = null; + + ******************************************** + * * + ********************************************/ + +}