Skip to content

Commit 4e7bd84

Browse files
committed
feat: Implement user permissions retrieval and normalization in UserManagement
1 parent 3bbc0dc commit 4e7bd84

4 files changed

Lines changed: 106 additions & 10 deletions

File tree

ShopInventory.Web/Components/Pages/UserManagement.razor

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3091,7 +3091,7 @@
30913091

30923092
private bool showPermissionsModal = false;
30933093
private UserDisplayModel? selectedUserForPermissions;
3094-
private HashSet<string> selectedPermissions = new();
3094+
private HashSet<string> selectedPermissions = new(StringComparer.OrdinalIgnoreCase);
30953095
private bool useRoleDefaults = false;
30963096

30973097
// Security stats
@@ -3546,12 +3546,22 @@ new();
35463546
}
35473547
}
35483548

3549-
private void ShowPermissionsModal(UserDisplayModel user)
3549+
private async Task ShowPermissionsModal(UserDisplayModel user)
35503550
{
3551-
selectedUserForPermissions = user;
3552-
selectedPermissions = new HashSet<string>(user.Permissions ?? new List<string>());
3553-
useRoleDefaults = selectedPermissions.Count == 0;
3554-
showPermissionsModal = true;
3551+
CloseActionMenu();
3552+
3553+
try
3554+
{
3555+
var permissions = await UserService.GetUserPermissionsAsync(user.Id);
3556+
selectedUserForPermissions = user;
3557+
selectedPermissions = new HashSet<string>(permissions.EffectivePermissions, StringComparer.OrdinalIgnoreCase);
3558+
useRoleDefaults = permissions.UsesRoleDefaults;
3559+
showPermissionsModal = true;
3560+
}
3561+
catch (Exception ex)
3562+
{
3563+
Snackbar.Add(ex.Message, Severity.Error);
3564+
}
35553565
}
35563566

35573567
private void ClosePermissionsModal()
@@ -3602,7 +3612,9 @@ new();
36023612
{
36033613
var userId = selectedUserForPermissions.Id;
36043614
var username = selectedUserForPermissions.Username;
3605-
var permissions = useRoleDefaults ? new List<string>() : selectedPermissions.ToList();
3615+
var permissions = useRoleDefaults
3616+
? new List<string>()
3617+
: selectedPermissions.OrderBy(permission => permission, StringComparer.OrdinalIgnoreCase).ToList();
36063618
await UserService.UpdateUserPermissionsAsync(userId, permissions);
36073619
Snackbar.Add("Permissions updated successfully", Severity.Success);
36083620
ClosePermissionsModal();

ShopInventory.Web/Services/UserManagementService.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public interface IUserManagementService
2020
{
2121
Task<UserListResponse> GetUsersAsync(int page = 1, int pageSize = 20, string? search = null, string? role = null, string? status = null);
2222
Task<UserModel?> GetUserAsync(Guid id);
23+
Task<(List<string> DirectPermissions, List<string> EffectivePermissions, bool UsesRoleDefaults)> GetUserPermissionsAsync(Guid id);
2324
Task CreateUserAsync(string username, string email, string password, string role);
2425
Task CreateUserAsync(UserFormModel model);
2526
Task CreateMerchandiserAccountAsync(CreateMerchandiserAccountFormModel model, CancellationToken cancellationToken = default);
@@ -100,6 +101,25 @@ public async Task<UserListResponse> GetUsersAsync(int page = 1, int pageSize = 2
100101
}
101102
}
102103

104+
public async Task<(List<string> DirectPermissions, List<string> EffectivePermissions, bool UsesRoleDefaults)> GetUserPermissionsAsync(Guid id)
105+
{
106+
var client = await CreateAuthenticatedClientAsync();
107+
var response = await client.GetAsync($"api/usermanagement/{id}/permissions");
108+
109+
if (!response.IsSuccessStatusCode)
110+
{
111+
await ThrowApiExceptionAsync(response, "Failed to load user permissions.");
112+
}
113+
114+
using var document = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
115+
var root = document.RootElement;
116+
117+
return (
118+
ReadStringListProperty(root, "permissions"),
119+
ReadStringListProperty(root, "effectivePermissions"),
120+
ReadBooleanProperty(root, "usesRoleDefaults"));
121+
}
122+
103123
public async Task CreateUserAsync(string username, string email, string password, string role)
104124
{
105125
var client = await CreateAuthenticatedClientAsync();
@@ -471,6 +491,48 @@ private static async Task ThrowApiExceptionAsync(HttpResponseMessage response, s
471491
response.StatusCode);
472492
}
473493

494+
private static List<string> ReadStringListProperty(JsonElement root, string propertyName)
495+
{
496+
if (!TryGetPropertyIgnoreCase(root, propertyName, out var propertyElement) || propertyElement.ValueKind != JsonValueKind.Array)
497+
{
498+
return new List<string>();
499+
}
500+
501+
return propertyElement.EnumerateArray()
502+
.Where(item => item.ValueKind == JsonValueKind.String)
503+
.Select(item => item.GetString())
504+
.Where(value => !string.IsNullOrWhiteSpace(value))
505+
.Select(value => value!.Trim())
506+
.Distinct(StringComparer.OrdinalIgnoreCase)
507+
.OrderBy(value => value, StringComparer.OrdinalIgnoreCase)
508+
.ToList();
509+
}
510+
511+
private static bool ReadBooleanProperty(JsonElement root, string propertyName)
512+
{
513+
return TryGetPropertyIgnoreCase(root, propertyName, out var propertyElement)
514+
&& (propertyElement.ValueKind == JsonValueKind.True || propertyElement.ValueKind == JsonValueKind.False)
515+
&& propertyElement.GetBoolean();
516+
}
517+
518+
private static bool TryGetPropertyIgnoreCase(JsonElement root, string propertyName, out JsonElement propertyElement)
519+
{
520+
if (root.ValueKind == JsonValueKind.Object)
521+
{
522+
foreach (var property in root.EnumerateObject())
523+
{
524+
if (string.Equals(property.Name, propertyName, StringComparison.OrdinalIgnoreCase))
525+
{
526+
propertyElement = property.Value;
527+
return true;
528+
}
529+
}
530+
}
531+
532+
propertyElement = default;
533+
return false;
534+
}
535+
474536
private static string ExtractApiErrorMessage(
475537
string errorBody,
476538
HttpStatusCode? statusCode = null,

ShopInventory/DTOs/SecurityDto.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,7 @@ public class UserPermissionsResponse
426426
public string Role { get; set; } = string.Empty;
427427
public List<string> Permissions { get; set; } = new();
428428
public List<string> EffectivePermissions { get; set; } = new();
429+
public bool UsesRoleDefaults { get; set; }
429430
}
430431

431432
/// <summary>

ShopInventory/Services/UserManagementService.cs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -281,15 +281,19 @@ public async Task<ServiceResult> DeleteUserAsync(Guid userId)
281281
}
282282
}
283283

284-
var effectivePermissions = await GetEffectivePermissionsAsync(userId);
284+
var normalizedDirectPermissions = NormalizePermissions(directPermissions);
285+
var effectivePermissions = NormalizePermissions(await GetEffectivePermissionsAsync(userId));
286+
var roleDefaultPermissions = Permission.GetDefaultPermissionsForRole(user.Role);
285287

286288
return new UserPermissionsResponse
287289
{
288290
UserId = user.Id,
289291
Username = user.Username,
290292
Role = user.Role,
291-
Permissions = directPermissions,
292-
EffectivePermissions = effectivePermissions
293+
Permissions = normalizedDirectPermissions,
294+
EffectivePermissions = effectivePermissions,
295+
UsesRoleDefaults = normalizedDirectPermissions.Count == 0 ||
296+
HasSamePermissions(normalizedDirectPermissions, roleDefaultPermissions)
293297
};
294298
}
295299

@@ -478,6 +482,23 @@ private void InvalidateEffectivePermissionsCache(Guid userId)
478482
_memoryCache.Remove(GetEffectivePermissionsCacheKey(userId));
479483
}
480484

485+
private static bool HasSamePermissions(IEnumerable<string> permissions, IEnumerable<string> otherPermissions)
486+
{
487+
return NormalizePermissions(permissions)
488+
.SequenceEqual(NormalizePermissions(otherPermissions), StringComparer.OrdinalIgnoreCase);
489+
}
490+
491+
private static List<string> NormalizePermissions(IEnumerable<string>? permissions)
492+
{
493+
return permissions?
494+
.Where(permission => !string.IsNullOrWhiteSpace(permission))
495+
.Select(permission => permission.Trim())
496+
.Distinct(StringComparer.OrdinalIgnoreCase)
497+
.OrderBy(permission => permission, StringComparer.OrdinalIgnoreCase)
498+
.ToList()
499+
?? new List<string>();
500+
}
501+
481502
private static UserDetailDto MapToUserDetailDto(User user)
482503
{
483504
var permissions = new List<string>();

0 commit comments

Comments
 (0)