Improved LdapService thread safety, fixed user images sometimes not loading

This commit is contained in:
2025-11-24 11:02:58 +01:00
parent 22d72e124f
commit e9a55761c2
2 changed files with 70 additions and 25 deletions

View File

@@ -98,7 +98,6 @@ public class HomeController : Controller
[ResponseCache(Duration = 3600, Location = ResponseCacheLocation.Any, VaryByQueryKeys = new[] { "uid", "size" })] [ResponseCache(Duration = 3600, Location = ResponseCacheLocation.Any, VaryByQueryKeys = new[] { "uid", "size" })]
public async Task<IActionResult> UserPhotoAsync(string uid, int? size) public async Task<IActionResult> UserPhotoAsync(string uid, int? size)
{ {
Task<AdminSettingsModel> adminSettingsModelTask = _ldap.GetAdminSettingsModelAsync();
UserModel? user = await _ldap.GetUserByUidAsync(uid, _ldap.UsersAttributes); UserModel? user = await _ldap.GetUserByUidAsync(uid, _ldap.UsersAttributes);
if (user is null || user.JpegPhoto is null || user.JpegPhoto == "") if (user is null || user.JpegPhoto is null || user.JpegPhoto == "")
{ {
@@ -110,7 +109,7 @@ public class HomeController : Controller
} }
if (size is not null) if (size is not null)
{ {
AdminSettingsModel adminSettingsModel = await adminSettingsModelTask; AdminSettingsModel adminSettingsModel = await _ldap.GetAdminSettingsModelAsync();
size = Math.Min((int)size, adminSettingsModel.MaxDownloadableUserImageSize); size = Math.Min((int)size, adminSettingsModel.MaxDownloadableUserImageSize);
} }
byte[] encodedFile = ImageHelper.ResizeAndConvertToWebp(user.JpegPhoto, size ?? 32); byte[] encodedFile = ImageHelper.ResizeAndConvertToWebp(user.JpegPhoto, size ?? 32);

View File

@@ -11,6 +11,7 @@ public partial class LdapService : IDisposable
{ {
private readonly LdapConfig _opts; private readonly LdapConfig _opts;
private readonly LdapConnection _conn; private readonly LdapConnection _conn;
private readonly SemaphoreSlim _connLock = new(1, 1);
private AdminSettingsModel? adminSettingsModel; private AdminSettingsModel? adminSettingsModel;
private ILogger _logger; private ILogger _logger;
@@ -27,6 +28,9 @@ public partial class LdapService : IDisposable
int retries = 0; int retries = 0;
while (retries++ < _opts.ConnectionRetryCount) while (retries++ < _opts.ConnectionRetryCount)
{ {
try
{
await _connLock.WaitAsync();
try try
{ {
if (!_conn.Connected) if (!_conn.Connected)
@@ -42,6 +46,11 @@ public partial class LdapService : IDisposable
} }
} }
await _conn.BindAsync(_opts.BindDn, _opts.BindPassword); await _conn.BindAsync(_opts.BindDn, _opts.BindPassword);
}
finally
{
_connLock.Release();
}
return; return;
} }
catch (Exception ex) catch (Exception ex)
@@ -109,7 +118,7 @@ public partial class LdapService : IDisposable
LdapModification.Replace, LdapModification.Replace,
new LdapAttribute("description", targetText) new LdapAttribute("description", targetText)
); );
await _conn.ModifyAsync(dn, modification); await ModifyAsync(dn, modification);
} }
catch (Exception) catch (Exception)
{ {
@@ -151,7 +160,7 @@ public partial class LdapService : IDisposable
LdapModification.Replace, LdapModification.Replace,
new LdapAttribute("description", targetText) new LdapAttribute("description", targetText)
); );
await _conn.ModifyAsync(dn, modification); await ModifyAsync(dn, modification);
} }
catch (Exception) catch (Exception)
{ {
@@ -201,7 +210,7 @@ public partial class LdapService : IDisposable
LdapModification.Replace, LdapModification.Replace,
new LdapAttribute("description", targetText) new LdapAttribute("description", targetText)
); );
await _conn.ModifyAsync(dn, modification); await ModifyAsync(dn, modification);
} }
catch (Exception) catch (Exception)
{ {
@@ -387,16 +396,17 @@ public async Task CreateAsset(LdapAttributeSet attributeSet)
} }
public async Task<IEnumerable<Dictionary<string, string>>> ListObjectBy(string baseDn, string filter, string[] attributes) public async Task<IEnumerable<Dictionary<string, string>>> ListObjectBy(string baseDn, string filter, string[] attributes)
{
return await Task.Run(async () =>
{ {
await ConnectAndBind(); await ConnectAndBind();
await _connLock.WaitAsync();
try
{
var search = await _conn.SearchAsync( var search = await _conn.SearchAsync(
baseDn, baseDn,
LdapConnection.ScopeSub, LdapConnection.ScopeSub,
$"{filter}", $"{filter}",
attributes, attributes,
false); false).ConfigureAwait(false);
var list = new List<Dictionary<string, string>>(); var list = new List<Dictionary<string, string>>();
while (await search.HasMoreAsync()) while (await search.HasMoreAsync())
{ {
@@ -415,7 +425,11 @@ public async Task CreateAsset(LdapAttributeSet attributeSet)
catch (LdapException) { } catch (LdapException) { }
} }
return list; return list;
}); }
finally
{
_connLock.Release();
}
} }
public async Task DeleteUserAsync(string uid) public async Task DeleteUserAsync(string uid)
@@ -467,16 +481,24 @@ public async Task CreateAsset(LdapAttributeSet attributeSet)
await ConnectAndBind(); await ConnectAndBind();
string dn = PrependRDN($"{rdnKey}={rdnValue}", baseDn); string dn = PrependRDN($"{rdnKey}={rdnValue}", baseDn);
if (attributeName == rdnKey) if (attributeName == rdnKey)
{
await _connLock.WaitAsync();
try
{ {
await _conn.RenameAsync(dn, $"{rdnKey}={attributeValue}", true); await _conn.RenameAsync(dn, $"{rdnKey}={attributeValue}", true);
} }
finally
{
_connLock.Release();
}
}
else else
{ {
var modification = new LdapModification( var modification = new LdapModification(
LdapModification.Replace, LdapModification.Replace,
new LdapAttribute(attributeName, attributeValue) new LdapAttribute(attributeName, attributeValue)
); );
await _conn.ModifyAsync(dn, modification); await ModifyAsync(dn, modification);
} }
} }
@@ -490,24 +512,48 @@ public async Task CreateAsset(LdapAttributeSet attributeSet)
new LdapAttribute(attributeName) new LdapAttribute(attributeName)
); );
await _conn.ModifyAsync(dn, modification); await ModifyAsync(dn, modification);
} }
public async Task DeleteObjectByDnAsync(string dn) public async Task DeleteObjectByDnAsync(string dn)
{
await _connLock.WaitAsync();
try
{ {
await _conn.DeleteAsync(dn); await _conn.DeleteAsync(dn);
} }
finally
{
_connLock.Release();
}
}
public async Task CreateObject(string dn, LdapAttributeSet attributeSet) public async Task CreateObject(string dn, LdapAttributeSet attributeSet)
{ {
await ConnectAndBind(); await ConnectAndBind();
LdapEntry ldapEntry = new(dn, attributeSet); LdapEntry ldapEntry = new(dn, attributeSet);
await _connLock.WaitAsync();
try
{
await _conn.AddAsync(ldapEntry); await _conn.AddAsync(ldapEntry);
} }
finally
public async Task ModifyAsync(string dn, LdapModification ldapModification)
{ {
await _conn.ModifyAsync(dn, ldapModification); _connLock.Release();
}
}
public async Task ModifyAsync(string dn, LdapModification mod, CancellationToken ct = default)
{
await _connLock.WaitAsync(ct);
try
{
await _conn.ModifyAsync(dn, mod, ct);
}
finally
{
_connLock.Release();
}
} }
public void Dispose() public void Dispose()