From 4ec531f026993a26cdb8ef39d9fc67c2510e8a62 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sat, 3 Aug 2024 13:59:48 +0200 Subject: [PATCH] Added group administration --- .idea/.idea.HopFrame/.idea/dataSources.xml | 2 +- HopFrame.Database/Models/PermissionGroup.cs | 2 +- .../Services/IPermissionService.cs | 4 +- .../Implementation/PermissionService.cs | 39 ++- HopFrame.Web/AdminPermissions.cs | 10 +- HopFrame.Web/Model/PermissionGroupAdd.cs | 7 + .../Components/GroupAddModal.razor | 252 ++++++++++++++++++ .../Components/UserEditModal.razor | 12 +- .../Pages/Administration/GroupsPage.razor | 133 +++++++++ .../Pages/Administration/GroupsPage.razor.css | 26 ++ .../Administration/Layout/AdminMenu.razor | 8 +- .../Pages/Administration/UsersPage.razor | 4 +- 12 files changed, 483 insertions(+), 16 deletions(-) create mode 100644 HopFrame.Web/Model/PermissionGroupAdd.cs create mode 100644 HopFrame.Web/Pages/Administration/Components/GroupAddModal.razor create mode 100644 HopFrame.Web/Pages/Administration/GroupsPage.razor create mode 100644 HopFrame.Web/Pages/Administration/GroupsPage.razor.css diff --git a/.idea/.idea.HopFrame/.idea/dataSources.xml b/.idea/.idea.HopFrame/.idea/dataSources.xml index 81347d0..56f170e 100644 --- a/.idea/.idea.HopFrame/.idea/dataSources.xml +++ b/.idea/.idea.HopFrame/.idea/dataSources.xml @@ -5,7 +5,7 @@ sqlite.xerial true org.sqlite.JDBC - jdbc:sqlite:$PROJECT_DIR$/DatabaseTest/bin/Debug/net8.0/test.db + jdbc:sqlite:$PROJECT_DIR$/RestApiTest/bin/Debug/net8.0/test.db diff --git a/HopFrame.Database/Models/PermissionGroup.cs b/HopFrame.Database/Models/PermissionGroup.cs index 6d151b1..3472e39 100644 --- a/HopFrame.Database/Models/PermissionGroup.cs +++ b/HopFrame.Database/Models/PermissionGroup.cs @@ -1,6 +1,6 @@ namespace HopFrame.Database.Models; -public sealed class PermissionGroup : IPermissionOwner { +public class PermissionGroup : IPermissionOwner { public string Name { get; init; } public bool IsDefaultGroup { get; set; } public string Description { get; set; } diff --git a/HopFrame.Security/Services/IPermissionService.cs b/HopFrame.Security/Services/IPermissionService.cs index 3f051b3..2071ce9 100644 --- a/HopFrame.Security/Services/IPermissionService.cs +++ b/HopFrame.Security/Services/IPermissionService.cs @@ -10,11 +10,13 @@ public interface IPermissionService { Task GetPermissionGroup(string name); + Task EditPermissionGroup(PermissionGroup group); + Task> GetUserPermissionGroups(User user); Task RemoveGroupFromUser(User user, PermissionGroup group); - Task CreatePermissionGroup(string name, bool isDefault = false, string description = null); + Task CreatePermissionGroup(string name, bool isDefault = false, string description = null); Task DeletePermissionGroup(PermissionGroup group); diff --git a/HopFrame.Security/Services/Implementation/PermissionService.cs b/HopFrame.Security/Services/Implementation/PermissionService.cs index bda9d8e..ac0e156 100644 --- a/HopFrame.Security/Services/Implementation/PermissionService.cs +++ b/HopFrame.Security/Services/Implementation/PermissionService.cs @@ -53,6 +53,19 @@ internal sealed class PermissionService(TDbContext context, ITokenCo .SingleOrDefaultAsync(); } + public async Task EditPermissionGroup(PermissionGroup group) { + var orig = await context.Groups.SingleOrDefaultAsync(g => g.Name == group.Name); + + if (orig is null) return; + + var entity = context.Groups.Update(orig); + + entity.Entity.Default = group.IsDefaultGroup; + entity.Entity.Description = group.Description; + + await context.SaveChangesAsync(); + } + public async Task> GetUserPermissionGroups(User user) { var groups = await context.Groups.ToListAsync(); var perms = await GetFullPermissions(user.Id.ToString()); @@ -74,7 +87,7 @@ internal sealed class PermissionService(TDbContext context, ITokenCo await context.SaveChangesAsync(); } - public async Task CreatePermissionGroup(string name, bool isDefault = false, string description = null) { + public async Task CreatePermissionGroup(string name, bool isDefault = false, string description = null) { var group = new GroupEntry { Name = name, Description = description, @@ -83,12 +96,36 @@ internal sealed class PermissionService(TDbContext context, ITokenCo }; await context.Groups.AddAsync(group); + + if (isDefault) { + var users = await context.Users.ToListAsync(); + + foreach (var user in users) { + await context.Permissions.AddAsync(new PermissionEntry { + GrantedAt = DateTime.Now, + PermissionText = group.Name, + UserId = user.Id + }); + } + } + await context.SaveChangesAsync(); + + return group.ToPermissionGroup(context); } public async Task DeletePermissionGroup(PermissionGroup group) { var entry = await context.Groups.SingleOrDefaultAsync(entry => entry.Name == group.Name); context.Groups.Remove(entry); + + var permissions = await context.Permissions + .Where(perm => perm.UserId == group.Name || perm.PermissionText == group.Name) + .ToListAsync(); + + if (permissions.Count > 0) { + context.Permissions.RemoveRange(permissions); + } + await context.SaveChangesAsync(); } diff --git a/HopFrame.Web/AdminPermissions.cs b/HopFrame.Web/AdminPermissions.cs index 36ac057..499cdb0 100644 --- a/HopFrame.Web/AdminPermissions.cs +++ b/HopFrame.Web/AdminPermissions.cs @@ -2,8 +2,14 @@ namespace HopFrame.Web; public static class AdminPermissions { public const string IsAdmin = "hopframe.admin"; + public const string ViewUsers = "hopframe.admin.users.view"; - public const string EditUsers = "hopframe.admin.users.edit"; - public const string DeleteUsers = "hopframe.admin.users.delete"; + public const string EditUser = "hopframe.admin.users.edit"; + public const string DeleteUser = "hopframe.admin.users.delete"; public const string AddUser = "hopframe.admin.users.add"; + + public const string ViewGroups = "hopframe.admin.groups.view"; + public const string EditGroup = "hopframe.admin.groups.edit"; + public const string DeleteGroup = "hopframe.admin.groups.delete"; + public const string AddGroup = "hopframe.admin.groups.add"; } \ No newline at end of file diff --git a/HopFrame.Web/Model/PermissionGroupAdd.cs b/HopFrame.Web/Model/PermissionGroupAdd.cs new file mode 100644 index 0000000..a94643e --- /dev/null +++ b/HopFrame.Web/Model/PermissionGroupAdd.cs @@ -0,0 +1,7 @@ +using HopFrame.Database.Models; + +namespace HopFrame.Web.Model; + +public class PermissionGroupAdd : PermissionGroup { + public string GroupName { get; set; } +} \ No newline at end of file diff --git a/HopFrame.Web/Pages/Administration/Components/GroupAddModal.razor b/HopFrame.Web/Pages/Administration/Components/GroupAddModal.razor new file mode 100644 index 0000000..7955ce1 --- /dev/null +++ b/HopFrame.Web/Pages/Administration/Components/GroupAddModal.razor @@ -0,0 +1,252 @@ +@rendermode InteractiveServer + +@using BlazorStrap +@using BlazorStrap.Shared.Components.Modal +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using BlazorStrap.V5 +@using CurrieTechnologies.Razor.SweetAlert2 +@using HopFrame.Database.Models +@using HopFrame.Security.Services +@using HopFrame.Web.Model + + + + @if (_isEdit) { + Edit group + } + else { + Add group + } + +
+ Name + @if (!_isEdit) { + + group. + + + } + else { + + } +
+ + @if (_isEdit) { +
+ Created at + +
+ } + +
+ Description + +
+ +
+ + Default group + +
+ +
+ Inherits from + + + + @foreach (var group in _group.Permissions.Where(g => g.PermissionName.StartsWith("group."))) { + + + + + + @group.PermissionName.Replace("group.", "") + + } + + + +
+ + + + @foreach (var group in _allGroups) { + @if (_group.Permissions.All(g => g.PermissionName != group.Name) && group.Name != _group.Name) { + + } + } + + Add +
+
+
+
+ +
+ Permissions + + + + @foreach (var perm in _group.Permissions.Where(perm => !perm.PermissionName.StartsWith("group."))) { + + + + + + @perm.PermissionName + + } + + + +
+ + Add +
+
+
+
+
+ + Cancel + Save + +
+
+ +@inject IPermissionService Permissions +@inject SweetAlertService Alerts + +@code { + [Parameter] public Func ReloadPage { get; set; } + + private PermissionGroupAdd _group; + + private BSModalBase _modal; + private string _permissionToAdd; + private string _groupToAdd; + + private IList _allGroups; + + private bool _isEdit; + + public async Task ShowAsync(PermissionGroup group = null) { + _allGroups = await Permissions.GetPermissionGroups(); + + if (group is not null) { + _group = new PermissionGroupAdd { + CreatedAt = group.CreatedAt, + Description = group.Description, + Name = group.Name, + IsDefaultGroup = group.IsDefaultGroup, + Permissions = group.Permissions + }; + _isEdit = true; + } + else { + _group = new PermissionGroupAdd { + Permissions = new List(), + IsDefaultGroup = false + }; + _isEdit = false; + } + + await _modal.ShowAsync(); + } + + private async Task AddPermission() { + if (string.IsNullOrWhiteSpace(_permissionToAdd)) { + await Alerts.FireAsync(new SweetAlertOptions { + Title = "Enter a permission name!", + Icon = SweetAlertIcon.Error, + ShowConfirmButton = true + }); + return; + } + + if (_isEdit) { + await Permissions.AddPermission(_group, _permissionToAdd); + } + + _group.Permissions.Add(new Permission { + PermissionName = _permissionToAdd + }); + + _permissionToAdd = null; + } + + private async Task RemovePermission(Permission permission) { + if (_isEdit) { + var perm = await Permissions.GetPermission(permission.PermissionName, _group); + await Permissions.RemovePermission(perm); + } + + _group.Permissions.Remove(permission); + } + + private async Task AddInheritanceGroup() { + if (string.IsNullOrWhiteSpace(_groupToAdd)) { + await Alerts.FireAsync(new SweetAlertOptions { + Title = "Select a group!", + Icon = SweetAlertIcon.Error, + ShowConfirmButton = true + }); + return; + } + + if (_isEdit) { + await Permissions.AddPermission(_group, _groupToAdd); + } + + _group.Permissions.Add(new Permission { + PermissionName = _groupToAdd + }); + + _groupToAdd = null; + } + + private async Task AddGroup() { + if (_isEdit) { + await Permissions.EditPermissionGroup(_group); + + if (ReloadPage is not null) + await ReloadPage.Invoke(); + + await Alerts.FireAsync(new SweetAlertOptions { + Title = "Group edited!", + Icon = SweetAlertIcon.Success, + Timer = 1500, + ShowConfirmButton = false + }); + + return; + } + + if (_allGroups.Any(group => group.Name == _group.Name)) { + await Alerts.FireAsync(new SweetAlertOptions { + Title = "Something went wrong!", + Text = "This group already exists!", + Icon = SweetAlertIcon.Error, + ShowConfirmButton = false, + Timer = 1500 + }); + return; + } + + var dbGroup = await Permissions.CreatePermissionGroup("group." + _group.GroupName, _group.IsDefaultGroup, _group.Description); + + foreach (var permission in _group.Permissions) { + await Permissions.AddPermission(dbGroup, permission.PermissionName); + } + + if (ReloadPage is not null) + await ReloadPage.Invoke(); + + await Alerts.FireAsync(new SweetAlertOptions { + Title = "Group added!", + Icon = SweetAlertIcon.Success, + Timer = 1500, + ShowConfirmButton = false + }); + } +} \ No newline at end of file diff --git a/HopFrame.Web/Pages/Administration/Components/UserEditModal.razor b/HopFrame.Web/Pages/Administration/Components/UserEditModal.razor index 330750b..a5495c7 100644 --- a/HopFrame.Web/Pages/Administration/Components/UserEditModal.razor +++ b/HopFrame.Web/Pages/Administration/Components/UserEditModal.razor @@ -119,7 +119,7 @@ private string _permissionToAdd; public async Task ShowAsync(User user) { - if (!(await Permissions.HasPermission(AdminPermissions.EditUsers, Auth.User.Id))) { + if (!(await Permissions.HasPermission(AdminPermissions.EditUser, Auth.User.Id))) { await NoEditPermissions(); return; } @@ -131,7 +131,7 @@ } private async Task AddGroup() { - if (!(await Permissions.HasPermission(AdminPermissions.EditUsers, Auth.User.Id))) { + if (!(await Permissions.HasPermission(AdminPermissions.EditUser, Auth.User.Id))) { await NoEditPermissions(); return; } @@ -159,7 +159,7 @@ } private async Task RemoveGroup(PermissionGroup group) { - if (!(await Permissions.HasPermission(AdminPermissions.EditUsers, Auth.User.Id))) { + if (!(await Permissions.HasPermission(AdminPermissions.EditUser, Auth.User.Id))) { await NoEditPermissions(); return; } @@ -187,7 +187,7 @@ } private async Task AddPermission() { - if (!(await Permissions.HasPermission(AdminPermissions.EditUsers, Auth.User.Id))) { + if (!(await Permissions.HasPermission(AdminPermissions.EditUser, Auth.User.Id))) { await NoEditPermissions(); return; } @@ -214,7 +214,7 @@ } private async Task RemovePermission(Permission perm) { - if (!(await Permissions.HasPermission(AdminPermissions.EditUsers, Auth.User.Id))) { + if (!(await Permissions.HasPermission(AdminPermissions.EditUser, Auth.User.Id))) { await NoEditPermissions(); return; } @@ -242,7 +242,7 @@ } private async void EditUser() { - if (!(await Permissions.HasPermission(AdminPermissions.EditUsers, Auth.User.Id))) { + if (!(await Permissions.HasPermission(AdminPermissions.EditUser, Auth.User.Id))) { await NoEditPermissions(); return; } diff --git a/HopFrame.Web/Pages/Administration/GroupsPage.razor b/HopFrame.Web/Pages/Administration/GroupsPage.razor new file mode 100644 index 0000000..73d2a89 --- /dev/null +++ b/HopFrame.Web/Pages/Administration/GroupsPage.razor @@ -0,0 +1,133 @@ +@page "/administration/groups" +@rendermode InteractiveServer +@layout AdminLayout + +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using BlazorStrap +@using Microsoft.AspNetCore.Components.Web +@using HopFrame.Web.Components +@using HopFrame.Web.Pages.Administration.Components +@using BlazorStrap.V5 +@using CurrieTechnologies.Razor.SweetAlert2 +@using HopFrame.Database.Models +@using HopFrame.Security.Claims +@using HopFrame.Security.Services +@using HopFrame.Web.Pages.Administration.Layout + +Groups + + + + +
+

+ Groups administration + + + +

+ + + Add Group +
+ + + + + Name + Description + Default + Created + + @if (_hasEditPrivileges || _hasDeletePrivileges) { + Actions + } + + + + + @foreach (var group in _groups) { + + @group.Name.Replace("group.", "") + @group.Description + + @if (group.IsDefaultGroup) { + Yes + } + else { + No + } + + @group.CreatedAt + + @if (_hasEditPrivileges || _hasDeletePrivileges) { + + + @if (_hasEditPrivileges) { + Edit + } + + @if (_hasDeletePrivileges) { + Delete + } + + + } + + } + + + +@inject IPermissionService Permissions +@inject ITokenContext Auth +@inject SweetAlertService Alerts + +@code { + private IList _groups = new List(); + + private bool _hasEditPrivileges = false; + private bool _hasDeletePrivileges = false; + + private GroupAddModal _groupAddModal; + + protected override async Task OnInitializedAsync() { + _groups = await Permissions.GetPermissionGroups(); + + _hasEditPrivileges = await Permissions.HasPermission(AdminPermissions.EditGroup, Auth.User.Id); + _hasDeletePrivileges = await Permissions.HasPermission(AdminPermissions.DeleteGroup, Auth.User.Id); + } + + private async Task Reload() { + _groups = new List(); + + _groups = await Permissions.GetPermissionGroups(); + + StateHasChanged(); + } + + private async Task Delete(PermissionGroup group) { + var result = await Alerts.FireAsync(new SweetAlertOptions { + Title = "Are you sure?", + Text = "You won't be able to revert this!", + Icon = SweetAlertIcon.Warning, + ConfirmButtonText = "Yes", + ShowCancelButton = true, + ShowConfirmButton = true + }); + + if (result.IsConfirmed) { + await Permissions.DeletePermissionGroup(group); + await Reload(); + + await Alerts.FireAsync(new SweetAlertOptions { + Title = "Deleted!", + Icon = SweetAlertIcon.Success, + Timer = 1500, + ShowConfirmButton = false + }); + } + } +} \ No newline at end of file diff --git a/HopFrame.Web/Pages/Administration/GroupsPage.razor.css b/HopFrame.Web/Pages/Administration/GroupsPage.razor.css new file mode 100644 index 0000000..445d132 --- /dev/null +++ b/HopFrame.Web/Pages/Administration/GroupsPage.razor.css @@ -0,0 +1,26 @@ +.title { + display: flex; + flex-direction: row; + gap: 10px; + margin-bottom: 10px; +} + +#search { + margin-left: auto; +} + +th, h3 { + user-select: none; +} + +h3 { + color: white; +} + +.reload, .sorter { + cursor: pointer; +} + +.bold { + font-weight: bold; +} diff --git a/HopFrame.Web/Pages/Administration/Layout/AdminMenu.razor b/HopFrame.Web/Pages/Administration/Layout/AdminMenu.razor index dd4fbdc..4ec85c8 100644 --- a/HopFrame.Web/Pages/Administration/Layout/AdminMenu.razor +++ b/HopFrame.Web/Pages/Administration/Layout/AdminMenu.razor @@ -21,7 +21,7 @@ @foreach (var nav in Subpages) { - @nav.Value + @nav.Value } @@ -52,7 +52,11 @@ return Navigator.Uri.Contains(element); } - private async void Logout() { + private void Navigate(string url) { + Navigator.NavigateTo(url, true); + } + + private void Logout() { Navigator.NavigateTo("login?redirect=/administration", true); } } diff --git a/HopFrame.Web/Pages/Administration/UsersPage.razor b/HopFrame.Web/Pages/Administration/UsersPage.razor index 2794387..271c36a 100644 --- a/HopFrame.Web/Pages/Administration/UsersPage.razor +++ b/HopFrame.Web/Pages/Administration/UsersPage.razor @@ -121,8 +121,8 @@ _userGroups.Add(user.Id, groups.FirstOrDefault()); } - _hasEditPrivileges = await PermissionsService.HasPermission(AdminPermissions.EditUsers, Auth.User.Id); - _hasDeletePrivileges = await PermissionsService.HasPermission(AdminPermissions.DeleteUsers, Auth.User.Id); + _hasEditPrivileges = await PermissionsService.HasPermission(AdminPermissions.EditUser, Auth.User.Id); + _hasDeletePrivileges = await PermissionsService.HasPermission(AdminPermissions.DeleteUser, Auth.User.Id); } private async Task Reload() {