140 lines
5.9 KiB
C#
140 lines
5.9 KiB
C#
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<bool> AuthenticateAsync(string username, string password);
|
|
Task<LdapUser> GetUserAsync(string username);
|
|
}
|
|
|
|
public partial class LdapService : ILdapService
|
|
{
|
|
public Dictionary<string, HashAlgorithm> 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<LdapService> _logger;
|
|
|
|
public LdapService(IConfiguration configuration, ILogger<LdapService> logger)
|
|
{
|
|
_configuration = configuration;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<bool> 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<LdapUser> 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; }
|
|
}
|