Merge pull request #5408 from sbwalker/dev

improve user experience of permissions grid
This commit is contained in:
Shaun Walker
2025-07-22 16:12:48 -04:00
committed by GitHub
4 changed files with 263 additions and 255 deletions

View File

@ -9,62 +9,26 @@
@if (_permissions != null) @if (_permissions != null)
{ {
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<table class="table table-borderless"> <table class="table table-borderless">
<tbody> <tbody>
<tr>
<th scope="col">@Localizer["Role"]</th>
@foreach (var permissionname in _permissionnames)
{
<th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
}
</tr>
@foreach (Role role in _roles)
{
<tr> <tr>
<td>@role.Name</td> <th scope="col">@Localizer["Role"]</th>
@foreach (var permissionname in _permissionnames) @foreach (var permissionname in _permissionnames)
{ {
<td style="text-align: center;"> <th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
<TriStateCheckBox Value=@GetPermissionValue(permissionname, role.Name, -1) Disabled="@GetPermissionDisabled(permissionname, role.Name)" OnChange="@(e => PermissionChanged(e, permissionname, role.Name, -1))" />
</td>
} }
</tr> </tr>
} @foreach (Role role in _roles)
</tbody>
</table>
<br />
</div>
</div>
<div class="row">
<div class="col">
@if (_users.Count != 0)
{
<div class="row">
<div class="col">
</div>
</div>
<table class="table table-borderless">
<thead>
<tr>
<th scope="col">@Localizer["User"]</th>
@foreach (var permissionname in _permissionnames)
{
<th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
}
</tr>
</thead>
<tbody>
@foreach (User user in _users)
{ {
<tr> <tr>
<td>@user.DisplayName (@user.Username)</td> <td>@role.Name</td>
@foreach (var permissionname in _permissionnames) @foreach (var permissionname in _permissionnames)
{ {
<td style="text-align: center; width: 1px;"> <td style="text-align: center;">
<TriStateCheckBox Value=@GetPermissionValue(permissionname, "", user.UserId) Disabled="@GetPermissionDisabled(permissionname, "")" OnChange="@(e => PermissionChanged(e, permissionname, "", user.UserId))" /> <TriStateCheckBox Value="@GetPermissionValue(permissionname, role.Name, -1)" Disabled="@GetPermissionDisabled(permissionname, role.Name)" OnChange="@(e => PermissionChanged(e, permissionname, role.Name, -1))" />
</td> </td>
} }
</tr> </tr>
@ -72,200 +36,242 @@
</tbody> </tbody>
</table> </table>
<br /> <br />
} </div>
</div>
<div class="row">
<div class="col">
@if (_users.Count != 0)
{
<div class="row">
<div class="col">
</div>
</div>
<table class="table table-borderless">
<thead>
<tr>
<th scope="col">@Localizer["User"]</th>
@foreach (var permissionname in _permissionnames)
{
<th style="text-align: center; width: 1px;">@((MarkupString)DisplayPermissionName(permissionname).Replace(" ", "<br />"))</th>
}
</tr>
</thead>
<tbody>
@foreach (User user in _users)
{
<tr>
<td>@user.DisplayName (@user.Username)</td>
@foreach (var permissionname in _permissionnames)
{
<td style="text-align: center; width: 1px;">
<TriStateCheckBox Value="@GetPermissionValue(permissionname, "", user.UserId)" Disabled="@GetPermissionDisabled(permissionname, "")" OnChange="@(e => PermissionChanged(e, permissionname, "", user.UserId))" />
</td>
}
</tr>
}
</tbody>
</table>
<br />
}
</div>
</div>
<div class="row">
<div class="col-11">
<AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["Username.Enter"]" @ref="_user" />
</div>
<div class="col-1">
<button type="button" class="btn btn-primary" @onclick="AddUser">@SharedLocalizer["Add"]</button>
</div>
</div>
<div class="row">
<div class="col">
<ModuleMessage Type="MessageType.Warning" Message="@_message" />
</div>
</div> </div>
</div> </div>
<div class="row">
<div class="col-11">
<AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["Username.Enter"]" @ref="_user" />
</div>
<div class="col-1">
<button type="button" class="btn btn-primary" @onclick="AddUser">@SharedLocalizer["Add"]</button>
</div>
</div>
<div class="row">
<div class="col">
<ModuleMessage Type="MessageType.Warning" Message="@_message" />
</div>
</div>
</div>
} }
@code { @code {
private List<string> _permissionnames; private List<string> _permissionnames;
private List<Permission> _permissions; private List<Permission> _permissions;
private List<Role> _roles; private List<Role> _roles;
private List<User> _users = new List<User>(); private List<User> _users = new List<User>();
private AutoComplete _user; private AutoComplete _user;
private string _message = string.Empty; private string _message = string.Empty;
[Parameter] [Parameter]
public string EntityName { get; set; } public string EntityName { get; set; }
[Parameter] [Parameter]
public string PermissionNames { get; set; } public string PermissionNames { get; set; }
[Parameter] [Parameter]
public string Permissions { get; set; } // deprecated - use PermissionList instead public string Permissions { get; set; } // deprecated - use PermissionList instead
[Parameter] [Parameter]
public List<Permission> PermissionList { get; set; } public List<Permission> PermissionList { get; set; }
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
if (!string.IsNullOrEmpty(Permissions)) if (!string.IsNullOrEmpty(Permissions))
{ {
PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions); PermissionList = JsonSerializer.Deserialize<List<Permission>>(Permissions);
} }
_roles = await RoleService.GetRolesAsync(ModuleState.SiteId, true); _roles = await RoleService.GetRolesAsync(ModuleState.SiteId, true);
if (!UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) _roles.RemoveAll(item => item.Name == RoleNames.Host); // remove host role
{
_roles.RemoveAll(item => item.Name == RoleNames.Host);
}
// get permission names // get permission names
if (string.IsNullOrEmpty(PermissionNames)) if (string.IsNullOrEmpty(PermissionNames))
{ {
_permissionnames = new List<string>(); _permissionnames = new List<string>();
_permissionnames.Add(Shared.PermissionNames.View); _permissionnames.Add(Shared.PermissionNames.View);
_permissionnames.Add(Shared.PermissionNames.Edit); _permissionnames.Add(Shared.PermissionNames.Edit);
} }
else else
{ {
_permissionnames = PermissionNames.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList(); _permissionnames = PermissionNames.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList();
} }
// initialize permissions // initialize permissions
_permissions = new List<Permission>(); _permissions = new List<Permission>();
if (PermissionList != null && PermissionList.Any()) if (PermissionList != null && PermissionList.Any())
{ {
foreach (var permission in PermissionList) foreach (var permission in PermissionList)
{ {
_permissions.Add(permission); _permissions.Add(permission);
if (permission.UserId != null) if (permission.UserId != null)
{ {
if (!_users.Any(item => item.UserId == permission.UserId.Value)) if (!_users.Any(item => item.UserId == permission.UserId.Value))
{ {
_users.Add(await UserService.GetUserAsync(permission.UserId.Value, ModuleState.SiteId)); _users.Add(await UserService.GetUserAsync(permission.UserId.Value, ModuleState.SiteId));
} }
} }
} }
} }
else else
{ {
foreach (string permissionname in _permissionnames) foreach (string permissionname in _permissionnames)
{ {
// permission names can be in the form of "EntityName:PermissionName:Roles" // permission names can be in the form of "EntityName:PermissionName:Roles"
if (permissionname.Contains(":")) if (permissionname.Contains(":"))
{ {
var segments = permissionname.Split(':'); var segments = permissionname.Split(':');
if (segments.Length == 3) if (segments.Length == 3)
{ {
foreach (var role in segments[2].Split(';')) foreach (var role in segments[2].Split(';'))
{ {
_permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], role, null, true)); _permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], role, null, true));
} }
// ensure admin access // ensure admin access
if (!_permissions.Any(item => item.EntityName == segments[0] && item.PermissionName == segments[1] && item.RoleName == RoleNames.Admin)) if (!_permissions.Any(item => item.EntityName == segments[0] && item.PermissionName == segments[1] && item.RoleName == RoleNames.Admin))
{ {
_permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], RoleNames.Admin, null, true)); _permissions.Add(new Permission(ModuleState.SiteId, segments[0], segments[1], RoleNames.Admin, null, true));
} }
} }
} }
else else
{ {
_permissions.Add(new Permission(ModuleState.SiteId, EntityName, permissionname, RoleNames.Admin, null, true)); _permissions.Add(new Permission(ModuleState.SiteId, EntityName, permissionname, RoleNames.Admin, null, true));
} }
} }
} }
} }
private string GetPermissionName(string permissionName) private string GetPermissionName(string permissionName)
{ {
return (permissionName.Contains(":")) ? permissionName.Split(':')[1] : permissionName; return (permissionName.Contains(":")) ? permissionName.Split(':')[1] : permissionName;
} }
private string GetEntityName(string permissionName) private string GetEntityName(string permissionName)
{ {
return (permissionName.Contains(":")) ? permissionName.Split(':')[0] : EntityName; return (permissionName.Contains(":")) ? permissionName.Split(':')[0] : EntityName;
} }
private string DisplayPermissionName(string permissionName) private string DisplayPermissionName(string permissionName)
{ {
var name = Localizer[GetPermissionName(permissionName)].ToString(); var name = Localizer[GetPermissionName(permissionName)].ToString();
name += " " + Localizer[GetEntityName(permissionName)].ToString(); name += " " + Localizer[GetEntityName(permissionName)].ToString();
return name; return name;
} }
private bool? GetPermissionValue(string permissionName, string roleName, int userId) private bool? GetPermissionValue(string permissionName, string roleName, int userId)
{ {
bool? isauthorized = null; bool? isauthorized = null;
if (roleName != "") if (roleName != "")
{ {
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName); var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName);
if (permission != null) if (permission != null)
{ {
isauthorized = permission.IsAuthorized; isauthorized = permission.IsAuthorized;
} }
} }
else else
{ {
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId); var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId);
if (permission != null) if (permission != null)
{ {
isauthorized = permission.IsAuthorized; isauthorized = permission.IsAuthorized;
} }
} }
return isauthorized; return isauthorized;
} }
private bool GetPermissionDisabled(string permissionName, string roleName) private bool GetPermissionDisabled(string permissionName, string roleName)
{ {
if (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) var disabled = false;
{
return true;
}
else
{
if (GetEntityName(permissionName) != EntityName && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
return true;
}
else
{
return false;
}
}
}
private void PermissionChanged(bool? value, string permissionName, string roleName, int userId) // administrator role permissions can only be changed by a host
{ if (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
if (roleName != "") {
{ disabled = true;
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName); }
if (permission != null)
{ // API permissions can only be changed by an administrator
_permissions.Remove(permission); if (GetEntityName(permissionName) != EntityName && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
} {
if (value != null) disabled = true;
{ }
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), roleName, null, value.Value));
} return disabled;
} }
else
{ private bool? PermissionChanged(bool? value, string permissionName, string roleName, int userId)
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId); {
if (permission != null) if (roleName != "")
{ {
_permissions.Remove(permission); var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.RoleName == roleName);
} if (permission != null)
if (value != null) {
{ _permissions.Remove(permission);
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), null, userId, value.Value)); }
}
} // system roles cannot be denied - only custom roles can be denied
} var role = _roles.FirstOrDefault(item => item.Name == roleName);
if (value != null && !value.Value && role.IsSystem)
{
value = null;
}
if (value != null)
{
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), roleName, null, value.Value));
}
}
else
{
var permission = _permissions.FirstOrDefault(item => item.EntityName == GetEntityName(permissionName) && item.PermissionName == GetPermissionName(permissionName) && item.UserId == userId);
if (permission != null)
{
_permissions.Remove(permission);
}
if (value != null)
{
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionName), GetPermissionName(permissionName), null, userId, value.Value));
}
}
return value;
}
private async Task<Dictionary<string, string>> GetUsers(string filter) private async Task<Dictionary<string, string>> GetUsers(string filter)
{ {
@ -305,29 +311,20 @@
private void ValidatePermissions() private void ValidatePermissions()
{ {
// remove deny all users, unauthenticated, and registered users
var permissions = _permissions.Where(item => !item.IsAuthorized &&
(item.RoleName == RoleNames.Everyone || item.RoleName == RoleNames.Unauthenticated || item.RoleName == RoleNames.Registered)).ToList();
foreach (var permission in permissions)
{
_permissions.Remove(permission);
}
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
// remove deny administrators and host users // remove host role permissions
permissions = _permissions.Where(item => !item.IsAuthorized && var permissions = _permissions.Where(item => item.RoleName == RoleNames.Host).ToList();
(item.RoleName == RoleNames.Admin || item.RoleName == RoleNames.Host)).ToList(); foreach (var permission in permissions)
foreach (var permission in permissions) {
_permissions.Remove(permission);
}
// add host role permissions if administrator role is not assigned (to prevent lockout)
foreach (var permissionname in _permissionnames)
{ {
_permissions.Remove(permission); if (!_permissions.Any(item => item.EntityName == GetEntityName(permissionname) && item.PermissionName == GetPermissionName(permissionname) && item.RoleName == RoleNames.Admin))
}
foreach (var permissionname in _permissionnames)
{
// add administrators role if neither host or administrator is assigned
if (!_permissions.Any(item => item.EntityName == GetEntityName(permissionname) && item.PermissionName == GetPermissionName(permissionname) &&
(item.RoleName == RoleNames.Admin || item.RoleName == RoleNames.Host)))
{ {
_permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionname), GetPermissionName(permissionname), RoleNames.Admin, null, true)); _permissions.Add(new Permission(ModuleState.SiteId, GetEntityName(permissionname), GetPermissionName(permissionname), RoleNames.Host, null, true));
} }
} }
} }

View File

@ -16,7 +16,7 @@
public bool Disabled { get; set; } public bool Disabled { get; set; }
[Parameter] [Parameter]
public Action<bool?> OnChange { get; set; } public Func<bool?, bool?> OnChange { get; set; }
protected override void OnInitialized() protected override void OnInitialized()
{ {
@ -41,27 +41,35 @@
break; break;
} }
_value = OnChange(_value);
SetImage(); SetImage();
OnChange(_value);
} }
} }
private void SetImage() private void SetImage()
{ {
switch (_value) if (!Disabled)
{ {
case true: switch (_value)
_src = "images/checked.png"; {
_title = Localizer["PermissionGranted"]; case true:
break; _src = "images/checked.png";
case false: _title = Localizer["PermissionGranted"];
_src = "images/unchecked.png"; break;
_title = Localizer["PermissionDenied"]; case false:
break; _src = "images/unchecked.png";
case null: _title = Localizer["PermissionDenied"];
_src = "images/null.png"; break;
_title = string.Empty; case null:
break; _src = "images/null.png";
_title = string.Empty;
break;
}
}
else
{
_src = "images/disabled.png";
_title = Localizer["PermissionDisabled"];
} }
StateHasChanged(); StateHasChanged();

View File

@ -123,4 +123,7 @@
<data name="PermissionDenied" xml:space="preserve"> <data name="PermissionDenied" xml:space="preserve">
<value>Permission Denied</value> <value>Permission Denied</value>
</data> </data>
<data name="PermissionDisabled" xml:space="preserve">
<value>Permission Disabled</value>
</data>
</root> </root>

Binary file not shown.

After

Width:  |  Height:  |  Size: 875 B