using Novell.Directory.Ldap; using Microsoft.Extensions.Configuration; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; namespace OneForMe.Services; public interface ILdapService { Task AuthenticateAsync(string username, string password); Task GetUserAsync(string username); } public partial class LdapService : ILdapService { public Dictionary HashAlgorithms => new() { ["MD5"] = MD5.Create(), ["SHA1"] = SHA1.Create(), ["SHA256"] = SHA256.Create(), ["SHA384"] = SHA384.Create(), ["SHA512"] = SHA512.Create(), ["SSHA"] = SHA1.Create(), ["SSHA256"] = SHA256.Create(), ["SSHA384"] = SHA384.Create(), ["SSHA512"] = SHA512.Create()}; private readonly IConfiguration _configuration; private readonly ILogger _logger; public LdapService(IConfiguration configuration, ILogger logger) { _configuration = configuration; _logger = logger; } public async Task AuthenticateAsync(string username, string password) { try { LdapUser user = await GetUserAsync(username); bool hashMatches = CompareStringToHashed(password, user.Password); _logger.LogInformation("User {username} authenticated successfully via LDAP", [username]); return hashMatches; } catch (LdapException ex) { _logger.LogWarning("LDAP authentication failed for user {username}: {ex.Message}", [username, ex.Message]); return false; } catch (Exception ex) { _logger.LogError("LDAP authentication error for user {username}: {ex.Message}", [username, ex.Message]); return false; } } public async Task GetUserAsync(string username) { try { var connection = new LdapConnection(); var ldapSettings = _configuration.GetSection("LdapSettings"); var serverName = ldapSettings["ServerName"]; var port = int.Parse(ldapSettings["Port"] ?? "389"); var baseDn = ldapSettings["BaseDn"]; var bindDn = ldapSettings["BindDn"]; var bindPassword = ldapSettings["BindPassword"]; connection.Connect(serverName, port); connection.Bind(bindDn, bindPassword); var searchFilter = $"(uid={username})"; var results = connection.Search( baseDn, LdapConnection.ScopeSub, searchFilter, new[] { "uid", "mail", "cn", "sn", "userPassword" }, false ); LdapUser user = null; while (results.HasMore()) { var entry = results.Next(); LdapAttributeSet attributeSet = entry.GetAttributeSet(); attributeSet.TryGetValue("uid", out LdapAttribute? uid); attributeSet.TryGetValue("mail", out LdapAttribute? mail); attributeSet.TryGetValue("cn", out LdapAttribute? cn); attributeSet.TryGetValue("sn", out LdapAttribute? sn); attributeSet.TryGetValue("userPassword", out LdapAttribute? userPassword); user = new LdapUser { Username = uid?.StringValue ?? username, //entry.GetAttribute("uid")?.StringValue ?? username, Email = mail?.StringValue ?? "", //entry.GetAttribute("mail")?.StringValue ?? $"{username}@example.com", FullName = cn?.StringValue ?? "", //entry.GetAttribute("cn")?.StringValue ?? username, LastName = sn?.StringValue ?? "", //entry.GetAttribute("sn")?.StringValue ?? "", Password = userPassword?.StringValue ?? "" //entry.GetAttribute("userPassword")?.StringValue ?? "" }; } connection.Disconnect(); return user; } catch (Exception ex) { _logger.LogError($"Error retrieving LDAP user {username}: {ex.Message}"); return null; } } public bool CompareStringToHashed(string sourcePassword, string targetPasswordHashed) { string algorithmName = CurlyBracesExtractor().Match(targetPasswordHashed).Groups[1].Value; HashAlgorithm hashAlgorithm = HashAlgorithms[algorithmName.ToUpperInvariant()]; byte[] sourcePasswordBytes = Encoding.UTF8.GetBytes(sourcePassword); byte[] targetPasswordHashedBytes = Convert.FromBase64String(CurlyBracesRemover().Replace(targetPasswordHashed, "")); int hashSize = hashAlgorithm.HashSize / 8; if (targetPasswordHashedBytes.Length > hashSize) // Has salt { int saltLength = targetPasswordHashedBytes.Length - hashSize; byte[] salt = new byte[saltLength]; Array.Copy(targetPasswordHashedBytes, hashSize, salt, 0, saltLength); Array.Resize(ref targetPasswordHashedBytes, hashSize); byte[] newSourcePasswordBytes = new byte[sourcePasswordBytes.Length + salt.Length]; Array.Copy(sourcePasswordBytes, 0, newSourcePasswordBytes, 0, sourcePasswordBytes.Length); Array.Copy(salt, 0, newSourcePasswordBytes, sourcePasswordBytes.Length, salt.Length); sourcePasswordBytes = newSourcePasswordBytes; } sourcePasswordBytes = hashAlgorithm.ComputeHash(sourcePasswordBytes); return CryptographicOperations.FixedTimeEquals(sourcePasswordBytes, targetPasswordHashedBytes); } [GeneratedRegex(@"\{.*?\}")] private static partial Regex CurlyBracesRemover(); [GeneratedRegex(@"\{([^}]*)\}")] private static partial Regex CurlyBracesExtractor(); } public class LdapUser { public string Username { get; set; } public string Password { get; set; } public string Email { get; set; } public string FullName { get; set; } public string LastName { get; set; } }