using Novell.Directory.Ldap; using Microsoft.Extensions.Options; namespace Berufsschule_HAM.Services; public class LdapService : IDisposable { private readonly LdapOptions _opts; private readonly LdapConnection _conn; private ILogger _logger; public LdapService(IOptions options, ILogger 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>> ListLocationsAsync() { return await ListObjectBy(LocationsBaseDn, "(ou=locations)", ["l", "street", "description"]); } public async Task>> ListUsersAsync() { return await ListObjectBy(UsersBaseDn, "", ["cn", "sn", "title", "uid", "jpegPhoto", "userPassword", "description"]); } public async Task>> ListUsersAsync(string[] attributes) { return await ListObjectBy(UsersBaseDn, "", attributes); } public async Task> GetUserByUidAsync(string uid) { return (await ListObjectBy(UsersBaseDn, $"uid={uid}", ["cn", "sn", "title", "uid", "jpegPhoto", "userPassword", "description"])).First(); } public async Task> GetUserByUidAsync(string uid, string[] attributes) { return (await ListObjectBy(UsersBaseDn, $"uid={uid}", attributes)).First(); } public async Task>> 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); } private string PrependRDN(string rdn, string dn) { return rdn + "," + dn; } public async Task>> 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>(); while (await search.HasMoreAsync()) { try { var e = await search.NextAsync(); var attributeSet = e.GetAttributeSet(); if (attributeSet.Count == 0) { continue; } Dictionary 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(); } } }