Files
Berufsschule_HAM/src/Services/LdapService.cs

227 lines
7.5 KiB
C#

using Novell.Directory.Ldap;
using Microsoft.Extensions.Options;
using Berufsschule_HAM.Models;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
namespace Berufsschule_HAM.Services;
public partial class LdapService : IDisposable
{
private readonly LdapOptions _opts;
private readonly LdapConnection _conn;
private ILogger _logger;
public LdapService(IOptions<LdapOptions> options, ILogger<LdapService> logger)
{
_opts = options.Value;
_conn = new LdapConnection { SecureSocketLayer = _opts.UseSsl };
_logger = logger;
ConnectAndBind().Wait();
}
private async Task ConnectAndBind()
{
if (!_conn.Connected)
{
Console.WriteLine(_opts.Host);
Console.WriteLine(_opts.Port);
try
{
await _conn.ConnectAsync(_opts.Host, _opts.Port);
}
catch (SystemException ex)
{
_logger.LogCritical("Unable to connect to LDAP: {ex.Message}\n{ex.StackTrace}", [ex.Message, ex.StackTrace]);
throw;
}
}
await _conn.BindAsync(_opts.BindDn, _opts.BindPassword);
}
private string AssetsBaseDn => string.IsNullOrEmpty(_opts.AssetsOu) ? _opts.BaseDn : $"{_opts.AssetsOu},{_opts.BaseDn}";
private string LocationsBaseDn => string.IsNullOrEmpty(_opts.LocationsOu) ? _opts.BaseDn : $"{_opts.LocationsOu},{_opts.BaseDn}";
private string UsersBaseDn => string.IsNullOrEmpty(_opts.UsersOu) ? _opts.BaseDn : $"{_opts.UsersOu},{_opts.BaseDn}";
public async Task<IEnumerable<Dictionary<string, string>>> ListLocationsAsync()
{
return await ListObjectBy(LocationsBaseDn, "(ou=locations)", ["l", "street", "description"]);
}
public async Task<IEnumerable<Dictionary<string, string>>> ListUsersAsync()
{
return await ListObjectBy(UsersBaseDn, "", ["cn", "sn", "title", "uid", "jpegPhoto", "userPassword", "description"]);
}
public async Task<IEnumerable<UserModel>> ListUsersAsync(string[] attributes)
{
List<UserModel> returnValue = [];
(await ListObjectBy(UsersBaseDn, "", attributes))
.ToList()
.ForEach(x =>
returnValue.Add(
new UserModel(x)
{
Uid = x["uid"]
}
)
);
return returnValue;
}
public async Task<UserModel> GetUserByUidAsync(string uid)
{
return new UserModel((await ListObjectBy(UsersBaseDn, $"uid={uid}", ["cn", "sn", "title", "uid", "jpegPhoto", "userPassword", "description"])).First()) {Uid = uid};
}
public async Task<UserModel> GetUserByUidAsync(string uid, string[] attributes)
{
return new UserModel((await ListObjectBy(UsersBaseDn, $"uid={uid}", attributes)).First()) {Uid = uid};
}
public async Task<IEnumerable<Dictionary<string, string>>> ListDeviceAsync()
{
return await ListObjectBy(AssetsBaseDn, "(objectClass=device)", ["CN", "description", "l", "owner", "serialNumber"]);
}
public void CreateUser(string uid, LdapAttributeSet attributeSet)
{
string dn = PrependRDN($"uid={uid}", UsersBaseDn);
CreateObject(dn, attributeSet);
}
public void CreateAsset(LdapAttributeSet attributeSet)
{
CreateObject(AssetsBaseDn, attributeSet);
}
public void CreateLocation(LdapAttributeSet attributeSet)
{
CreateObject(LocationsBaseDn, attributeSet);
}
public async Task<UserAuthenticationResult> AuthenticateUser(string username, string password)
{
await ConnectAndBind();
try
{
UserModel user = await GetUserByUidAsync(username);
if (user.UserPassword is null)
{
return new() { Success = false, AuthenticationState = UserNotAuthenticatedReason.InvalidCredentials };
}
if (CompareStringToSha256(password, user.UserPassword))
{
return new() { Success = true};
}
return new() { Success = false, AuthenticationState = UserNotAuthenticatedReason.InvalidCredentials };
}
catch (LdapException)
{
return new() { Success = false, AuthenticationState = UserNotAuthenticatedReason.InvalidCredentials };
}
}
public bool CompareStringToSha256(string sourcePassword, string targetPasswordHashed)
{
byte[] sourcePasswordBytes = SHA256.HashData(Encoding.UTF8.GetBytes(sourcePassword));
byte[] targetPasswordHashedBytes = Convert.FromBase64String(CurlyBracesRemover().Replace(targetPasswordHashed, ""));
if (sourcePasswordBytes.Length != targetPasswordHashedBytes.Length)
{
return false;
}
for (int i = 0; i < sourcePasswordBytes.Length; i++)
{
if (sourcePasswordBytes[i] != targetPasswordHashedBytes[i])
{
return false;
}
}
return true;
}
private string PrependRDN(string rdn, string dn)
{
return rdn + "," + dn;
}
public async Task<IEnumerable<Dictionary<string, string>>> ListObjectBy(string baseDn, string filter, string[] attributes)
{
return await Task.Run(async () =>
{
await ConnectAndBind();
var search = await _conn.SearchAsync(
baseDn,
LdapConnection.ScopeSub,
$"{filter}",
attributes,
false);
var list = new List<Dictionary<string, string>>();
while (await search.HasMoreAsync())
{
try
{
var e = await search.NextAsync();
var attributeSet = e.GetAttributeSet();
if (attributeSet.Count == 0) { continue; }
Dictionary<string, string> attributeMap = [];
foreach (LdapAttribute attribute in attributeSet)
{
attributeMap[attribute.Name] = attribute.StringValue;
}
list.Add(attributeMap);
}
catch (LdapException) { }
}
return list;
});
}
public void DeleteUser(string uid)
{
string dn = PrependRDN($"uid={uid}", UsersBaseDn);
DeleteObjectByDn(dn);
}
public async Task UpdateUser(string uid, string attributeName, string attributeValue)
{
await ConnectAndBind();
string dn = PrependRDN($"uid={uid}", UsersBaseDn);
if (attributeName == "uid")
{
await _conn.RenameAsync(dn, $"uid={attributeValue}", true);
}
else
{
var modification = new LdapModification(
LdapModification.Replace,
new LdapAttribute(attributeName, attributeValue)
);
await _conn.ModifyAsync(dn, modification);
}
}
public async void DeleteObjectByDn(string dn)
{
await _conn.DeleteAsync(dn);
}
public async void CreateObject(string dn, LdapAttributeSet attributeSet)
{
await ConnectAndBind();
LdapEntry ldapEntry = new(dn, attributeSet);
await _conn.AddAsync(ldapEntry);
}
public void Dispose()
{
if (_conn != null && _conn.Connected)
{
_conn.Disconnect();
}
}
[GeneratedRegex(@"\{.*?\}")]
private static partial Regex CurlyBracesRemover();
}