Integracion LDAP v2

This commit is contained in:
Luis M 2024-03-13 15:22:06 -05:00
parent 6a8e003343
commit 724cd2f899
6 changed files with 495 additions and 148 deletions

View File

@ -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}");
}
}
}

200
LdapLoginLib/LDAP.cs Normal file
View File

@ -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<string>();
//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;
}
}
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
@ -7,8 +7,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="3.0.1" />
<PackageReference Include="System.DirectoryServices.Protocols" Version="6.0.1" />
<PackageReference Include="RawScape.Nini" Version="1.0.0" />
<PackageReference Include="System.DirectoryServices.Protocols" Version="6.0.2" />
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
</ItemGroup>
</Project>

View File

@ -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;
}
}
}
}

View File

@ -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
{
/// <summary>
/// The unique identifier for the user (mandatory).
/// Example: "jdoe"
/// </summary>
public string Uid { get; set; }
public string? Uid { get; set; }
/// <summary>
/// Doc. Identidad
/// The employee's unique identification number.
/// Example: "E12345"
/// </summary>
public string? EmployeeNumber { get; set; }
/// <summary>
/// The common name of the user.
@ -21,12 +28,14 @@ namespace LdapLoginLib
public string? Cn { get; set; }
/// <summary>
/// Nombres full
/// The user's given name.
/// Example: "John"
/// </summary>
public string? GivenName { get; set; }
/// <summary>
/// Apellidos
/// The user's surname.
/// Example: "Doe"
/// </summary>
@ -36,26 +45,38 @@ namespace LdapLoginLib
/// The user's email address.
/// Example: "jdoe@example.com"
/// </summary>
public string? Mail { get; set; }
public string? Mail { get; set; }
//TODO:
/// <summary>
/// The status of the user's internet account.
/// Example: "Active"
/// The user's ALTERNATIVE email address.
/// Example: "jdoe@example.com"
/// </summary>
public string? InetUserStatus { get; set; }
public string? MailAlternateAddress { get; set; }
/// <summary>
/// The organization the user belongs to.
/// Example: "Acme Inc.", currently "Sede"
/// </summary>
public string? O { get; set; }
/// <summary>
/// The status of the user's internet account.
/// Example: "Active"
/// </summary>
public string? InetUserStatus { get; set; }
/// <summary>
/// The status of the user's account as boolean.
/// Example: true or false
/// </summary>
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
/// </summary>
public string? EmployeeType { get; set; }
//public string? EmployeeType { get; set; }
/// <summary>
/// The business category of the user.
@ -83,11 +105,12 @@ namespace LdapLoginLib
/// </summary>
public string? BusinessCategory { get; set; }
// Definidos por LDAP
// No encontrado
/// <summary>
/// The employee's unique identification number.
/// Example: "E12345"
/// Numero unico identificacion personal
/// </summary>
public string? EmployeeNumber { get; set; }
//public string? NUIP { get; set; } = null;
/// <summary>
/// The license information for the user.

View File

@ -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
/// <summary>
/// {Uid} Uid - Nombre de usuario o Login
/// </summary>
/// <remarks>
/// xxx
/// </remarks>
public string? Usuario { get; set; }
/// <summary>
/// {EmployeeNumber} Documento de identidad.
/// </summary>
/// <remarks>
/// 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
/// </remarks>
public string? NumeroDocumento { get; set; }
/// <summary>
/// {Cn} Nombres y apellidos completos
/// </summary>
/// <remarks>
/// Cadena de caracteres, cada palabra con mayúscula inicial
/// </remarks>
public string? NombreCompleto { get; set; }
/// <summary>
/// {GivenName} Nombres completos
/// </summary>
/// <remarks>
/// Cadena de caracteres, cada palabra con mayúscula inicial
/// </remarks>
public string? Nombres { get; set; }
/// <summary>
/// {Sn} Apellidos completos
/// </summary>
/// <remarks>
/// Cadena de caracteres, cada palabra con mayúscula inicia
/// </remarks>
public string? Apellidos { get; set; }
/// <summary>
/// {Mail} Dirección de correo UNAL
/// </summary>
/// <remarks>
/// Cadena de caracteres en minúscula, uid+"@unal.edu.co"
/// </remarks>
public string? Correo { get; set; }
/// <summary>
/// {MailAlternateAddress} Correo alterno
/// </summary>
public string? CorreoAlt { get; set; }
/// <summary>
/// {O} Sede
/// </summary>
/// <remarks>
/// Cadena de caracteres, según tabla 11.1
/// </remarks>
public string? Sede { get; set; }
/// <summary>
/// {inetUserStatus} Estado identidad
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public string? Estado { get; set; }
/// <summary>
/// {IsActive}
/// Indica el estado, si esta activo o inactivo
/// </summary>
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 *
* *
********************************************
/// <summary>
/// {EmployeeType} Tipos de cuentas en LDAP
/// </summary>
/// <remarks>
/// Ver tabla 4.3, pagina 12 - Lineamientos Identidad
/// </remarks>
//public string? TipoUsuario { get; set; }
/// <summary>
/// {EmployeeNumber} xxxxxxxxx
/// </summary>
/// <remarks>
/// xxx
/// </remarks>
// Definidos por LDAP
// No encontrado
/// <summary>
/// Numero unico identificacion personal
/// </summary>
//public string? NUIP { get; set; } = null;
********************************************
* *
********************************************/
}