From dcb23b76ec13e86e42a50c238309cd88cdeda174 Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Sat, 25 Oct 2025 22:14:11 +0200 Subject: [PATCH 1/6] Fixed something in HomeController, I don't remember what --- src/Controllers/HomeController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controllers/HomeController.cs b/src/Controllers/HomeController.cs index dc276f7..9d5555a 100644 --- a/src/Controllers/HomeController.cs +++ b/src/Controllers/HomeController.cs @@ -89,7 +89,7 @@ public class HomeController : Controller Surname = user.Sn ?? "", Title = user.Title ?? "", Uid = user.Uid, - Workplace = user.Description?.Workplace ?? "" + Description = user.Description ?? new() {Address = new(), BirthDate = "", Workplace = "", Groups = []} }); } return View(new UsersIndexViewModel() { UserTableViewModels = UserTableViewModels }); From 310e05545f18301a72f750be98cd7dd5fc8db5a0 Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Sat, 25 Oct 2025 22:15:08 +0200 Subject: [PATCH 2/6] Implemented users update button in frontend --- src/Controllers/UsersController.cs | 96 ++++---- src/Helpers/UsersHelper.cs | 44 ++++ src/Models/UserModels.cs | 12 +- src/Models/UsersRequestModels.cs | 14 +- src/Models/UsersResponseModels.cs | 13 ++ src/Views/Home/Users.cshtml | 355 ++++++++++++++++++++++++++--- 6 files changed, 458 insertions(+), 76 deletions(-) create mode 100644 src/Helpers/UsersHelper.cs create mode 100644 src/Models/UsersResponseModels.cs diff --git a/src/Controllers/UsersController.cs b/src/Controllers/UsersController.cs index 96cb3b4..ac43bd4 100644 --- a/src/Controllers/UsersController.cs +++ b/src/Controllers/UsersController.cs @@ -1,4 +1,3 @@ -// Controllers/UsersController.cs using Berufsschule_HAM.Services; using Microsoft.AspNetCore.Mvc; using Novell.Directory.Ldap; @@ -8,6 +7,7 @@ using System.Security.Cryptography; using System.Text; using Microsoft.AspNetCore.Authorization; using System.Text.Json; +using System.Buffers.Text; [Authorize(Roles = "CanManageUsers")] [Route("[controller]")] @@ -66,7 +66,7 @@ public class UsersController : Controller try { jpegPhoto ??= Convert.ToBase64String(System.IO.File.ReadAllBytes("wwwroot/user_default.jpeg")); // TODO: cleanup - make this a config setting - uid ??= sn.ToLower() + cn.ToLower(); + uid ??= UsersHelper.CreateUsername(cn, sn); title ??= ""; description ??= "{}"; if (!userPassword.StartsWith('{')) @@ -98,53 +98,67 @@ public class UsersController : Controller } [HttpPost("Update")] - public async Task Update([FromBody] UsersModifyRequestModel requestModel) + public async Task Update([FromBody] UsersModifyRequestModel requestModel) { if (requestModel is null) { _logger.LogError("Unable to update a user because the UsersModifyRequestModel is null"); - return false; + return new() { Success = false }; } - string uid = requestModel.uid; - UserModel? user = null; - if (requestModel.Cn is not null) + try { - await _ldap.UpdateUser(uid, "cn", requestModel.Cn); - user ??= await _ldap.GetUserByUidAsync(uid); - string newUid = user.Sn?.ToLower() + requestModel.Cn.ToLower(); - await _ldap.UpdateUser(uid, "uid", newUid); - uid = newUid; - } - if (requestModel.Sn is not null) + string uid = requestModel.Uid; + UserModel? user = null; + if (requestModel.NewUid is not null && requestModel.NewUid.Length > 0) + { + await _ldap.UpdateUser(uid, "uid", requestModel.NewUid); + uid = requestModel.NewUid; + } + if (requestModel.Title is not null) + { + await _ldap.UpdateUser(uid, "title", requestModel.Title); + } + if (requestModel.Description is not null) + { + await _ldap.UpdateUser(uid, "description", JsonSerializer.Serialize(requestModel.Description)); + } + if (requestModel.JpegPhoto is not null && requestModel.JpegPhoto.Length > 0) + { + await _ldap.UpdateUser(uid, "jpegPhoto", requestModel.JpegPhoto); + } + if (requestModel.UserPassword is not null && requestModel.UserPassword.Length > 0) + { + await _ldap.UpdateUser(uid, "userPassword", "{SHA256}" + Convert.ToBase64String(SHA256.HashData(Encoding.UTF8.GetBytes(requestModel.UserPassword)))); + } + + string newUid = uid; + if (requestModel.Cn is not null) + { + await _ldap.UpdateUser(uid, "cn", requestModel.Cn); + user ??= await _ldap.GetUserByUidAsync(uid); + newUid = UsersHelper.CreateUsername(requestModel.Cn, user.Sn ?? ""); + + } + if (requestModel.Sn is not null) + { + await _ldap.UpdateUser(uid, "sn", requestModel.Sn); + user ??= await _ldap.GetUserByUidAsync(uid); + newUid = UsersHelper.CreateUsername(user.Cn ?? "", requestModel.Sn); + } + if (newUid.Length == 0) + { + throw new Exception("Username cannot be empty"); + } + if (newUid != uid) + { + await _ldap.UpdateUser(uid, "uid", newUid); + uid = newUid; + } + return new() { Success = true, NewUid = uid }; + } catch (Exception ex) { - await _ldap.UpdateUser(uid, "sn", requestModel.Sn); - user ??= await _ldap.GetUserByUidAsync(uid); - string newUid = requestModel.Sn.ToLower() + user.Cn?.ToLower(); - await _ldap.UpdateUser(uid, "uid", newUid); - uid = newUid; + return new() { Success = false, Exception = ex.Message }; } - if (requestModel.NewUid is not null) - { - await _ldap.UpdateUser(uid, "uid", requestModel.NewUid); - uid = requestModel.NewUid; - } - if (requestModel.Title is not null) - { - await _ldap.UpdateUser(uid, "title", requestModel.Title); - } - if (requestModel.Description is not null) - { - await _ldap.UpdateUser(uid, "description", requestModel.Description); - } - if (requestModel.JpegPhoto is not null) - { - await _ldap.UpdateUser(uid, "jpegPhoto", requestModel.JpegPhoto); - } - if (requestModel.UserPassword is not null) - { - await _ldap.UpdateUser(uid, "userPassword", requestModel.UserPassword); - } - return true; } [HttpPost("AddGroup")] diff --git a/src/Helpers/UsersHelper.cs b/src/Helpers/UsersHelper.cs new file mode 100644 index 0000000..7f16c4e --- /dev/null +++ b/src/Helpers/UsersHelper.cs @@ -0,0 +1,44 @@ +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; +namespace Berufsschule_HAM.Helpers; + +public static partial class UsersHelper +{ + public static string CreateUsername(string givenName, string surname) + { + if (string.IsNullOrWhiteSpace(givenName) || string.IsNullOrWhiteSpace(surname)) + throw new ArgumentException("Given name and surname must not be empty."); + + string combined = (surname + givenName).ToLowerInvariant(); + + // Normalize to decompose accents (e.g., é -> e + ́) + string normalized = combined.Normalize(NormalizationForm.FormD); + + // Remove diacritics + var sb = new StringBuilder(); + foreach (var c in normalized) + { + var category = CharUnicodeInfo.GetUnicodeCategory(c); + if (category != UnicodeCategory.NonSpacingMark) + sb.Append(c); + } + + string cleaned = sb.ToString(); + + // Replace German ligatures etc. + cleaned = cleaned + .Replace("ß", "ss") + .Replace("æ", "ae") + .Replace("œ", "oe") + .Replace("ø", "o"); + + // Remove everything not a-z + cleaned = AtoZ().Replace(cleaned, ""); + + return cleaned; + } + + [GeneratedRegex("[^a-z]")] + private static partial Regex AtoZ(); +} \ No newline at end of file diff --git a/src/Models/UserModels.cs b/src/Models/UserModels.cs index 7fe6616..52cd777 100644 --- a/src/Models/UserModels.cs +++ b/src/Models/UserModels.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using System.Text.Json.Serialization; namespace Berufsschule_HAM.Models; @@ -26,17 +27,24 @@ public class UserModel public class UserDescription { + [JsonPropertyName("BirthDate")] public required string BirthDate { get; set; } + [JsonPropertyName("Address")] public required UserAddress Address { get; set; } + [JsonPropertyName("Workplace")] public required string Workplace { get; set; } + [JsonPropertyName("Groups")] public List? Groups { get; set; } } public class UserAddress { + [JsonPropertyName("City")] public string? City { get; set; } + [JsonPropertyName("Street")] public string? Street { get; set; } - public string? StreetNr { get; set; } + [JsonPropertyName("StreetNr")] + public int? StreetNr { get; set; } } public class UserAuthenticationResult @@ -61,5 +69,5 @@ public class UserTableViewModel public required string Title { get; set; } public required string Name { get; set; } public required string Surname { get; set; } - public required string Workplace { get; set; } + public required UserDescription Description { get; set; } } \ No newline at end of file diff --git a/src/Models/UsersRequestModels.cs b/src/Models/UsersRequestModels.cs index 0abfe5a..9b98e63 100644 --- a/src/Models/UsersRequestModels.cs +++ b/src/Models/UsersRequestModels.cs @@ -1,3 +1,5 @@ +using System.Text.Json.Serialization; + namespace Berufsschule_HAM.Models; public class UsersIndexRequestModel @@ -13,13 +15,21 @@ public class UsersIndexRequestModel public class UsersModifyRequestModel { - public required string uid { get; set; } + [JsonPropertyName("Uid")] + public required string Uid { get; set; } + [JsonPropertyName("NewUid")] public string? NewUid { get; set; } = null; + [JsonPropertyName("Cn")] public string? Cn { get; set; } = null; + [JsonPropertyName("Sn")] public string? Sn { get; set; } = null; + [JsonPropertyName("Title")] public string? Title { get; set; } = null; - public string? Description { get; set; } = null; + [JsonPropertyName("Description")] + public UserDescription? Description { get; set; } = null; + [JsonPropertyName("JpegPhoto")] public string? JpegPhoto { get; set; } = null; + [JsonPropertyName("UserPassword")] public string? UserPassword { get; set; } = null; } diff --git a/src/Models/UsersResponseModels.cs b/src/Models/UsersResponseModels.cs new file mode 100644 index 0000000..0df701d --- /dev/null +++ b/src/Models/UsersResponseModels.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace Berufsschule_HAM.Models; + +public class UsersUpdateRequestModel +{ + [JsonPropertyName("Success")] + public required bool Success { get; set; } + [JsonPropertyName("Exception")] + public string? Exception { get; set; } + [JsonPropertyName("NewUid")] + public string? NewUid { get; set; } +} \ No newline at end of file diff --git a/src/Views/Home/Users.cshtml b/src/Views/Home/Users.cshtml index c526b36..84e5392 100644 --- a/src/Views/Home/Users.cshtml +++ b/src/Views/Home/Users.cshtml @@ -1,6 +1,8 @@ @using Microsoft.AspNetCore.Mvc.Localization @using Berufsschule_HAM.Models @using System.Buffers.Text +@using System.Text.Json; +@using System.Text.Json.Serialization; @model UsersIndexViewModel @inject IViewLocalizer T @{ @@ -42,15 +44,29 @@ @userTableViewModel.Title @userTableViewModel.Name @userTableViewModel.Surname - @userTableViewModel.Workplace + @userTableViewModel.Description.Workplace
- +
@@ -84,6 +100,34 @@ + + + + + + + From 85cb68a6c292ec53f16e6ec63658529cdaf6496c Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Sat, 25 Oct 2025 22:44:36 +0200 Subject: [PATCH 3/6] Implemented user creation in frontend --- src/Controllers/UsersController.cs | 24 ++-- src/Models/UsersRequestModels.cs | 8 +- src/Models/UsersResponseModels.cs | 13 +- src/Views/Home/Users.cshtml | 189 ++++++++++++++++++++++++++++- 4 files changed, 220 insertions(+), 14 deletions(-) diff --git a/src/Controllers/UsersController.cs b/src/Controllers/UsersController.cs index ac43bd4..630c41b 100644 --- a/src/Controllers/UsersController.cs +++ b/src/Controllers/UsersController.cs @@ -60,15 +60,19 @@ public class UsersController : Controller } } - [HttpGet("Create")] - public async Task Create(string cn, string sn, string? title, string? uid, string userPassword, string? description, string jpegPhoto) + [HttpPost("Create")] + public async Task Create([FromBody] UsersCreateRequestModel requestModel) { try { + string? jpegPhoto = requestModel.JpegPhoto; + string? title = requestModel.Title; + string userPassword = requestModel.UserPassword ?? ""; + UserDescription? description = requestModel.Description; jpegPhoto ??= Convert.ToBase64String(System.IO.File.ReadAllBytes("wwwroot/user_default.jpeg")); // TODO: cleanup - make this a config setting - uid ??= UsersHelper.CreateUsername(cn, sn); + string uid = UsersHelper.CreateUsername(requestModel.Cn ?? "", requestModel.Sn ?? ""); title ??= ""; - description ??= "{}"; + description ??= new() {Address = new(), BirthDate = "", Workplace = "", Groups = []}; if (!userPassword.StartsWith('{')) { byte[] passwordBytes = Encoding.UTF8.GetBytes(userPassword); @@ -79,26 +83,26 @@ public class UsersController : Controller LdapAttributeSet attributeSet = [ new LdapAttribute("objectClass", "inetOrgPerson"), - new LdapAttribute("cn", cn), - new LdapAttribute("sn", sn), + new LdapAttribute("cn", requestModel.Cn), + new LdapAttribute("sn", requestModel.Sn), new LdapAttribute("title", title), new LdapAttribute("uid", uid), new LdapAttribute("jpegPhoto", jpegPhoto), - new LdapAttribute("description", description), + new LdapAttribute("description", JsonSerializer.Serialize(description)), new LdapAttribute("userPassword", userPassword), ]; await _ldap.CreateUser(uid, attributeSet); - return true; + return new(){Success = true, Uid = uid}; } catch (Exception ex) { _logger.LogError("Unable to create user: {ex.Message} - {ex.StackTrace}", [ex.Message, ex.StackTrace]); - return false; + return new() {Success = false, Exception = ex.Message}; } } [HttpPost("Update")] - public async Task Update([FromBody] UsersModifyRequestModel requestModel) + public async Task Update([FromBody] UsersModifyRequestModel requestModel) { if (requestModel is null) { diff --git a/src/Models/UsersRequestModels.cs b/src/Models/UsersRequestModels.cs index 9b98e63..14361d7 100644 --- a/src/Models/UsersRequestModels.cs +++ b/src/Models/UsersRequestModels.cs @@ -13,12 +13,16 @@ public class UsersIndexRequestModel public bool UserPassword { get; set; } = true; } -public class UsersModifyRequestModel +public class UsersModifyRequestModel : UsersCreateRequestModel { [JsonPropertyName("Uid")] - public required string Uid { get; set; } + public required string Uid { get; set; } [JsonPropertyName("NewUid")] public string? NewUid { get; set; } = null; +} + +public class UsersCreateRequestModel +{ [JsonPropertyName("Cn")] public string? Cn { get; set; } = null; [JsonPropertyName("Sn")] diff --git a/src/Models/UsersResponseModels.cs b/src/Models/UsersResponseModels.cs index 0df701d..7e2a532 100644 --- a/src/Models/UsersResponseModels.cs +++ b/src/Models/UsersResponseModels.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; namespace Berufsschule_HAM.Models; -public class UsersUpdateRequestModel +public class UsersUpdateResponseModel { [JsonPropertyName("Success")] public required bool Success { get; set; } @@ -10,4 +10,15 @@ public class UsersUpdateRequestModel public string? Exception { get; set; } [JsonPropertyName("NewUid")] public string? NewUid { get; set; } +} + + +public class UsersCreateResponseModel +{ + [JsonPropertyName("Success")] + public required bool Success { get; set; } + [JsonPropertyName("Exception")] + public string? Exception { get; set; } + [JsonPropertyName("Uid")] + public string? Uid { get; set; } } \ No newline at end of file diff --git a/src/Views/Home/Users.cshtml b/src/Views/Home/Users.cshtml index 84e5392..c6aead0 100644 --- a/src/Views/Home/Users.cshtml +++ b/src/Views/Home/Users.cshtml @@ -15,7 +15,9 @@
- +
@@ -461,3 +463,188 @@ }); + + + + + + \ No newline at end of file From 4b39d721f36da5463a082f96738a3c259d330ed6 Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Sat, 25 Oct 2025 23:24:20 +0200 Subject: [PATCH 4/6] Implemented user view modal in frontend --- src/Views/Home/Users.cshtml | 139 +++++++++++++++++++++++++++++++++++- 1 file changed, 137 insertions(+), 2 deletions(-) diff --git a/src/Views/Home/Users.cshtml b/src/Views/Home/Users.cshtml index c6aead0..7a70841 100644 --- a/src/Views/Home/Users.cshtml +++ b/src/Views/Home/Users.cshtml @@ -38,7 +38,7 @@ @{ foreach (UserTableViewModel userTableViewModel in Model.UserTableViewModels) { - + Photo @@ -647,4 +647,139 @@ } } }); - \ No newline at end of file + + + + + + + + + + \ No newline at end of file From 614abfbe903225ddda9ff7a1b9bcc6a90a9b8731 Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Sat, 25 Oct 2025 23:25:06 +0200 Subject: [PATCH 5/6] Removed stray logging --- src/Views/Home/Users.cshtml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Views/Home/Users.cshtml b/src/Views/Home/Users.cshtml index 7a70841..68a553d 100644 --- a/src/Views/Home/Users.cshtml +++ b/src/Views/Home/Users.cshtml @@ -742,7 +742,6 @@ groups: JSON.parse(updateBtn.dataset.userGroups || '[]'), }; - console.log(data); // Fill modal fields document.getElementById('detailUid').value = data.uid || ''; From a0a499902606b5575277593d06684f93d418e27b Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Sun, 26 Oct 2025 00:08:28 +0200 Subject: [PATCH 6/6] Added user view localization, fixed user delete not working --- src/Resources/Views.Home.Users.de.resx | 108 +++++++++++++++++++++ src/Resources/Views.Home.Users.en.resx | 19 ++++ src/Views/Home/Users.cshtml | 127 +++++++++++++------------ 3 files changed, 191 insertions(+), 63 deletions(-) create mode 100644 src/Resources/Views.Home.Users.en.resx diff --git a/src/Resources/Views.Home.Users.de.resx b/src/Resources/Views.Home.Users.de.resx index d47f14b..a779788 100644 --- a/src/Resources/Views.Home.Users.de.resx +++ b/src/Resources/Views.Home.Users.de.resx @@ -19,4 +19,112 @@ Benutzer + + Benutzer erfolgreich gelöscht + + + Unbekannter Fehler + + + Server konnte nicht erreicht werden + + + Benutzer wurde erfolgreich angepasst + + + Anpassung ist fehlgeschlagen + + + Fehler beim Anpassen des Benutzers + + + Benutzer wurde erfolgreich erstellt + + + Erstellung des Benutzers ist fehlgeschlagen + + + Fehler beim Erstellen des Benutzers + + + Benutzername + + + Anrede + + + Name + + + Nachname + + + Arbeitsplatz + + + Aktion + + + Anpassen + + + Löschen + + + Sind Sie sich sicher, dass Sie den Benutzer {0} löschen möchten? + + + Ja, löschen + + + Abbrechen + + + Benutzer anpassen + + + Persönliche Informationen + + + Geburtsdatum + + + Stadt + + + Straße + + + Hausnummer + + + Arbeitsplatz und Benutzerkonto + + + Gruppen + + + Neues Passwort + + + Passwort + + + Foto + + + Änderungen anwenden + + + Anlegen + + + Benutzerdaten + + + Adresse + + + Löschen bestätigen + diff --git a/src/Resources/Views.Home.Users.en.resx b/src/Resources/Views.Home.Users.en.resx new file mode 100644 index 0000000..0a1a49b --- /dev/null +++ b/src/Resources/Views.Home.Users.en.resx @@ -0,0 +1,19 @@ + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, ... + + + System.Resources.ResXResourceWriter, System.Windows.Forms, ... + + + + Are you sure you want to delete user {0}? + + diff --git a/src/Views/Home/Users.cshtml b/src/Views/Home/Users.cshtml index 68a553d..62ead14 100644 --- a/src/Views/Home/Users.cshtml +++ b/src/Views/Home/Users.cshtml @@ -1,3 +1,4 @@ +@using Microsoft.AspNetCore.Html @using Microsoft.AspNetCore.Mvc.Localization @using Berufsschule_HAM.Models @using System.Buffers.Text @@ -26,12 +27,12 @@ - User ID - title - Name - Surname - Workplace - Action + @T["Username"] + @T["Title"] + @T["Name"] + @T["Surname"] + @T["Workplace"] + @T["Action"] @@ -62,13 +63,13 @@ data-user-groups="@JsonSerializer.Serialize(userTableViewModel.Description.Groups)" data-bs-toggle="modal" data-bs-target="#updateModal"> - Update + @T["Update"] @@ -85,16 +86,18 @@