mirror of
https://github.com/LD-Reborn/Berufsschule_HAM.git
synced 2025-12-20 06:51:55 +00:00
Added settings model, added settings migration, added LDAP salted hashes
This commit is contained in:
@@ -11,6 +11,7 @@ public partial class LdapService : IDisposable
|
||||
{
|
||||
private readonly LdapConfig _opts;
|
||||
private readonly LdapConnection _conn;
|
||||
private AdminSettingsModel? adminSettingsModel;
|
||||
private ILogger _logger;
|
||||
|
||||
public LdapService(IOptions<LdapConfig> options, ILogger<LdapService> logger)
|
||||
@@ -63,10 +64,12 @@ public partial class LdapService : IDisposable
|
||||
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 SettingsBaseDn => string.IsNullOrEmpty(_opts.SettingsOu) ? _opts.BaseDn : $"{_opts.SettingsOu},{_opts.BaseDn}";
|
||||
public string[] UsersAttributes => ["cn", "sn", "title", "uid", "jpegPhoto", "userPassword", "description"];
|
||||
public string[] AssetsAttributes => ["CN", "description", "l", "owner", "serialNumber", "name"];
|
||||
public string[] LocationsAttributes => ["l", "description"];
|
||||
public string[] GroupsAttributes => ["cn", "gidNumber", "description"];
|
||||
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()};
|
||||
public async Task<IEnumerable<LocationModel>> ListLocationsAsync()
|
||||
{
|
||||
IEnumerable<Dictionary<string, string>> locations = await ListObjectBy(LocationsBaseDn, "", LocationsAttributes);
|
||||
@@ -115,6 +118,53 @@ public partial class LdapService : IDisposable
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<AdminSettingsModel> GetAdminSettingsModelAsync()
|
||||
{
|
||||
if (adminSettingsModel is not null) return adminSettingsModel;
|
||||
|
||||
Dictionary<string, string> objects;
|
||||
try
|
||||
{
|
||||
objects = (await ListObjectBy(_opts.BaseDn, _opts.SettingsOu, ["description"])).First();
|
||||
adminSettingsModel = JsonSerializer.Deserialize<AdminSettingsModel>(objects["description"]) ?? throw new Exception();
|
||||
adminSettingsModel.hashAlgorithm = initHashAlgorithm(adminSettingsModel);
|
||||
return adminSettingsModel;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.LogError("Unable to retrieve Admin settings");
|
||||
throw new Exception("Unable to retrieve Admin settings");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> SetAdminSettingsModelAsync(AdminSettingsModel adminSettingsModel)
|
||||
{
|
||||
adminSettingsModel.hashAlgorithm = initHashAlgorithm(adminSettingsModel);
|
||||
this.adminSettingsModel = adminSettingsModel;
|
||||
|
||||
await ConnectAndBind();
|
||||
try
|
||||
{
|
||||
string dn = SettingsBaseDn;
|
||||
string targetText = JsonSerializer.Serialize(adminSettingsModel);
|
||||
var modification = new LdapModification(
|
||||
LdapModification.Replace,
|
||||
new LdapAttribute("description", targetText)
|
||||
);
|
||||
await _conn.ModifyAsync(dn, modification);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private HashAlgorithm initHashAlgorithm(AdminSettingsModel adminSettingsModel)
|
||||
{
|
||||
return HashAlgorithms[adminSettingsModel.DefaultHashAlgorithm];
|
||||
}
|
||||
|
||||
public async Task<string> GetAssetCounterAndIncrementAsync()
|
||||
{
|
||||
AssetsMetadataModel assetModel = await GetAssetCounterAsync();
|
||||
@@ -292,7 +342,7 @@ public async Task CreateAsset(LdapAttributeSet attributeSet)
|
||||
{
|
||||
return new() { Success = false, AuthenticationState = UserNotAuthenticatedReason.InvalidCredentials };
|
||||
}
|
||||
if (CompareStringToSha256(password, user.UserPassword))
|
||||
if (CompareStringToHashed(password, user.UserPassword))
|
||||
{
|
||||
return new() { Success = true, UserModel = user };
|
||||
}
|
||||
@@ -309,22 +359,26 @@ public async Task CreateAsset(LdapAttributeSet attributeSet)
|
||||
}
|
||||
}
|
||||
|
||||
public bool CompareStringToSha256(string sourcePassword, string targetPasswordHashed)
|
||||
public bool CompareStringToHashed(string sourcePassword, string targetPasswordHashed)
|
||||
{
|
||||
byte[] sourcePasswordBytes = SHA256.HashData(Encoding.UTF8.GetBytes(sourcePassword));
|
||||
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, ""));
|
||||
if (sourcePasswordBytes.Length != targetPasswordHashedBytes.Length)
|
||||
int hashSize = hashAlgorithm.HashSize / 8;
|
||||
if (targetPasswordHashedBytes.Length > hashSize) // Has salt
|
||||
{
|
||||
return false;
|
||||
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;
|
||||
}
|
||||
for (int i = 0; i < sourcePasswordBytes.Length; i++)
|
||||
{
|
||||
if (sourcePasswordBytes[i] != targetPasswordHashedBytes[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
sourcePasswordBytes = hashAlgorithm.ComputeHash(sourcePasswordBytes);
|
||||
return CryptographicOperations.FixedTimeEquals(sourcePasswordBytes, targetPasswordHashedBytes);
|
||||
}
|
||||
|
||||
private string PrependRDN(string rdn, string dn)
|
||||
@@ -466,4 +520,6 @@ public async Task CreateAsset(LdapAttributeSet attributeSet)
|
||||
|
||||
[GeneratedRegex(@"\{.*?\}")]
|
||||
private static partial Regex CurlyBracesRemover();
|
||||
[GeneratedRegex(@"\{([^}]*)\}")]
|
||||
private static partial Regex CurlyBracesExtractor();
|
||||
}
|
||||
@@ -105,6 +105,30 @@ public class MigrationService : IHostedService
|
||||
return 2;
|
||||
}
|
||||
|
||||
public static async Task<int> UpdateFrom2Async(LdapService ldapService)
|
||||
{
|
||||
// Create the settings ou
|
||||
AdminSettingsModel adminSettings = new() {
|
||||
BarcodeText = "HAM",
|
||||
BarcodeType = "code128",
|
||||
MaxDownloadableUserImageSize = 256,
|
||||
DefaultHashAlgorithm = "SSHA512",
|
||||
Presets = []
|
||||
};
|
||||
string settingsString = JsonSerializer.Serialize(adminSettings);
|
||||
LdapAttributeSet settingsAttributes =
|
||||
[
|
||||
new("objectClass", "organizationalUnit"),
|
||||
new("objectClass", "top"),
|
||||
new("ou", "settings"),
|
||||
new("description", settingsString)
|
||||
];
|
||||
await TryCreateObjectIgnoreAlreadyExists(ldapService, ldapService.SettingsBaseDn, settingsAttributes);
|
||||
|
||||
return 3;
|
||||
}
|
||||
|
||||
|
||||
private static async Task TryCreateObjectIgnoreAlreadyExists(LdapService ldapService, string dn, LdapAttributeSet ldapAttributes)
|
||||
{
|
||||
try
|
||||
|
||||
Reference in New Issue
Block a user