mirror of
https://github.com/LD-Reborn/Berufsschule_HAM.git
synced 2025-12-20 06:51:55 +00:00
314 lines
11 KiB
C#
314 lines
11 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;
|
|
using System.Text.Json;
|
|
|
|
namespace Berufsschule_HAM.Services;
|
|
public partial class LdapService : IDisposable
|
|
{
|
|
private readonly LdapConfig _opts;
|
|
private readonly LdapConnection _conn;
|
|
private ILogger _logger;
|
|
|
|
public LdapService(IOptions<LdapConfig> 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);
|
|
}
|
|
|
|
public string AssetsBaseDn => string.IsNullOrEmpty(_opts.AssetsOu) ? _opts.BaseDn : $"{_opts.AssetsOu},{_opts.BaseDn}";
|
|
public string LocationsBaseDn => string.IsNullOrEmpty(_opts.LocationsOu) ? _opts.BaseDn : $"{_opts.LocationsOu},{_opts.BaseDn}";
|
|
public string UsersBaseDn => string.IsNullOrEmpty(_opts.UsersOu) ? _opts.BaseDn : $"{_opts.UsersOu},{_opts.BaseDn}";
|
|
public string GroupsBaseDn => string.IsNullOrEmpty(_opts.GroupsOu) ? _opts.BaseDn : $"{_opts.GroupsOu},{_opts.BaseDn}";
|
|
public string MigrationsBaseDn => string.IsNullOrEmpty(_opts.MigrationsOu) ? _opts.BaseDn : $"{_opts.MigrationsOu},{_opts.BaseDn}";
|
|
public string[] GroupsAttributes => ["cn", "gidNumber", "description"];
|
|
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<MigrationModel> GetMigrationVersionAsync()
|
|
{
|
|
Dictionary<string, string> objects;
|
|
try
|
|
{
|
|
objects = (await ListObjectBy(_opts.BaseDn, _opts.MigrationsOu, ["description"])).First();
|
|
}
|
|
catch (Exception)
|
|
{
|
|
objects = [];
|
|
}
|
|
return new MigrationModel(objects);
|
|
}
|
|
|
|
public async Task<bool> UpdateMigrationVersionAsync(MigrationModel migrationModel)
|
|
{
|
|
await ConnectAndBind();
|
|
try
|
|
{
|
|
string dn = MigrationsBaseDn; //PrependRDN($"", MigrationsBaseDn);
|
|
string targetText = JsonSerializer.Serialize(migrationModel);
|
|
_logger.LogInformation("Setting the LDAP migration description to {targetText} for {dn}", [targetText, dn]);
|
|
var modification = new LdapModification(
|
|
LdapModification.Replace,
|
|
new LdapAttribute("description", targetText)
|
|
);
|
|
await _conn.ModifyAsync(dn, modification);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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<IEnumerable<GroupModel>> ListGroupsAsync(string[] attributes)
|
|
{
|
|
List<GroupModel> returnValue = [];
|
|
(await ListObjectBy(GroupsBaseDn, "", attributes))
|
|
.ToList()
|
|
.ForEach(x =>
|
|
returnValue.Add(
|
|
new GroupModel(x)
|
|
{
|
|
Cn = x["cn"]
|
|
}
|
|
)
|
|
);
|
|
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<GroupModel> GetGroupByCnAsync(string cn, string[] attributes)
|
|
{
|
|
return new GroupModel((await ListObjectBy(GroupsBaseDn, $"cn={cn}", attributes)).First()) { Cn = cn };
|
|
}
|
|
|
|
|
|
public async Task<IEnumerable<Dictionary<string, string>>> ListDeviceAsync()
|
|
{
|
|
return await ListObjectBy(AssetsBaseDn, "(objectClass=device)", ["CN", "description", "l", "owner", "serialNumber"]);
|
|
}
|
|
|
|
public async Task CreateUser(string uid, LdapAttributeSet attributeSet)
|
|
{
|
|
string dn = PrependRDN($"uid={uid}", UsersBaseDn);
|
|
await CreateObject(dn, attributeSet);
|
|
}
|
|
|
|
public async Task CreateGroup(string cn, LdapAttributeSet attributeSet)
|
|
{
|
|
string dn = PrependRDN($"cn={cn}", GroupsBaseDn);
|
|
await CreateObject(dn, attributeSet);
|
|
}
|
|
|
|
public async Task CreateAsset(LdapAttributeSet attributeSet)
|
|
{
|
|
await CreateObject(AssetsBaseDn, attributeSet);
|
|
}
|
|
|
|
public async Task CreateLocation(LdapAttributeSet attributeSet)
|
|
{
|
|
await CreateObject(LocationsBaseDn, attributeSet);
|
|
}
|
|
|
|
public async Task<UserAuthenticationResult> AuthenticateUser(string username, string password)
|
|
{
|
|
await ConnectAndBind();
|
|
try
|
|
{
|
|
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 (InvalidOperationException)
|
|
{
|
|
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 void DeleteGroup(string cn)
|
|
{
|
|
string dn = PrependRDN($"cn={cn}", GroupsBaseDn);
|
|
DeleteObjectByDn(dn);
|
|
}
|
|
|
|
public async Task UpdateUser(string uid, string attributeName, string attributeValue)
|
|
{
|
|
await UpdateObject(UsersBaseDn, "uid", uid, attributeName, attributeValue);
|
|
}
|
|
|
|
public async Task UpdateGroup(string cn, string attributeName, string attributeValue)
|
|
{
|
|
await UpdateObject(GroupsBaseDn, "cn", cn, attributeName, attributeValue);
|
|
}
|
|
|
|
public async Task UpdateObject(string baseDn, string rdnKey, string rdnValue, string attributeName, string attributeValue)
|
|
{
|
|
await ConnectAndBind();
|
|
string dn = PrependRDN($"{rdnKey}={rdnValue}", baseDn);
|
|
if (attributeName == rdnKey)
|
|
{
|
|
await _conn.RenameAsync(dn, $"{rdnKey}={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 Task 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();
|
|
} |