Added permissions to users controller and the client.

This commit is contained in:
Masu-Baumgartner
2024-10-07 16:37:18 +02:00
parent bb29177e41
commit 19afc5d055
11 changed files with 101 additions and 19 deletions

View File

@@ -3,6 +3,7 @@ using MoonCore.Exceptions;
using MoonCore.Extended.Abstractions;
using MoonCore.Extended.Helpers;
using MoonCore.Models;
using Moonlight.ApiServer.Attributes;
using Moonlight.ApiServer.Database.Entities;
using Moonlight.Shared.Http.Requests.Admin.Users;
using Moonlight.Shared.Http.Responses.Admin.Users;
@@ -23,14 +24,17 @@ public class UsersController : Controller
}
[HttpGet]
[RequirePermission("admin.users.read")]
public async Task<IPagedData<UserDetailResponse>> Get([FromQuery] int page, [FromQuery] int pageSize = 50)
=> await CrudHelper.Get(page, pageSize);
[HttpGet("{id}")]
[RequirePermission("admin.users.read")]
public async Task<UserDetailResponse> GetSingle(int id)
=> await CrudHelper.GetSingle(id);
[HttpPost]
[RequirePermission("admin.users.create")]
public async Task<UserDetailResponse> Create([FromBody] CreateUserRequest request)
{
// Reformat values
@@ -50,6 +54,7 @@ public class UsersController : Controller
}
[HttpPatch("{id}")]
[RequirePermission("admin.users.update")]
public async Task<UserDetailResponse> Update([FromRoute] int id, [FromBody] UpdateUserRequest request)
{
var user = await CrudHelper.GetSingleModel(id);
@@ -76,6 +81,7 @@ public class UsersController : Controller
}
[HttpDelete("{id}")]
[RequirePermission("admin.users.delete")]
public async Task Delete([FromRoute] int id)
=> await CrudHelper.Delete(id);
}

View File

@@ -86,6 +86,8 @@ public class AuthorizationMiddleware
detail: permission,
statusCode: 403
).ExecuteAsync(context);
return false;
}
}

View File

@@ -13,11 +13,12 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="MoonCore" Version="1.5.8" />
<PackageReference Include="MoonCore.Extended" Version="1.0.6" />
<PackageReference Include="MoonCore.Extended" Version="1.0.7" />
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
<PackageReference Include="MoonCore.Blazor.Tailwind" Version="1.0.5" />
</ItemGroup>
<ItemGroup>

View File

@@ -27,7 +27,8 @@ public class DefaultSidebarItemProvider : ISidebarItemProvider
Group = "Admin",
Path = "/admin",
Priority = 0,
RequiresExactMatch = true
RequiresExactMatch = true,
Permission = "admin.overview"
},
new SidebarItem()
{
@@ -36,7 +37,8 @@ public class DefaultSidebarItemProvider : ISidebarItemProvider
Group = "Admin",
Path = "/admin/users",
Priority = 1,
RequiresExactMatch = false
RequiresExactMatch = false,
Permission = "admin.users.read"
},
new SidebarItem()
{
@@ -45,7 +47,8 @@ public class DefaultSidebarItemProvider : ISidebarItemProvider
Group = "Admin",
Path = "/admin/api",
Priority = 2,
RequiresExactMatch = false
RequiresExactMatch = false,
Permission = "admin.api.read"
},
new SidebarItem()
{
@@ -54,7 +57,8 @@ public class DefaultSidebarItemProvider : ISidebarItemProvider
Group = "Admin",
Path = "/admin/system",
Priority = 3,
RequiresExactMatch = false
RequiresExactMatch = false,
Permission = "admin.system.info"
},
];
}

View File

@@ -8,4 +8,5 @@ public class SidebarItem
public string Path { get; set; }
public int Priority { get; set; }
public bool RequiresExactMatch { get; set; } = false;
public string? Permission { get; set; }
}

View File

@@ -13,7 +13,6 @@
<PackageReference Include="MoonCore" Version="1.5.8" />
<PackageReference Include="MoonCore.Blazor" Version="1.2.1" />
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.0" />
<PackageReference Include="MoonCore.Blazor.Tailwind" Version="1.0.2" />
</ItemGroup>
<ItemGroup>

View File

@@ -25,6 +25,8 @@ public class IdentityService
ApiClient = apiClient;
}
#region Login / Logout
public async Task Check()
{
try
@@ -56,4 +58,42 @@ public class IdentityService
await CookieService.SetValue("token", "", 30);
await Check();
}
#endregion
public bool HasPermission(string requiredPermission)
{
// Check for wildcard permission
if (Permissions.Contains("*"))
return true;
var requiredSegments = requiredPermission.Split('.');
// Check if the user has the exact permission or a wildcard match
foreach (var permission in Permissions)
{
var permissionSegments = permission.Split('.');
// Iterate over the segments of the required permission
for (var i = 0; i < requiredSegments.Length; i++)
{
// If the current segment matches or is a wildcard, continue to the next segment
if (i < permissionSegments.Length && requiredSegments[i] == permissionSegments[i] ||
permissionSegments[i] == "*")
{
// If we've reached the end of the permissionSegments array, it means we've found a match
if (i == permissionSegments.Length - 1)
return true; // Found an exact match or a wildcard match
}
else
{
// If we reach here, it means the segments don't match and we break out of the loop
break;
}
}
}
// No matching permission found
return false;
}
}

View File

@@ -2,7 +2,9 @@
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<CascadingValue Name="TargetPageType" Value="routeData.PageType">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
</CascadingValue>
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>

View File

@@ -1,11 +1,14 @@
@using MoonCore.Helpers
@using MoonCore.Exceptions
@using MoonCore.Helpers
@using MoonCore.PluginFramework.Services
@using Moonlight.Client.Interfaces
@using Moonlight.Client.Services
@using Moonlight.Client.UI.Partials
@inherits LayoutComponentBase
@inject ImplementationService ImplementationService
@inject IdentityService IdentityService
@inject IServiceProvider ServiceProvider
@inject ILogger<MainLayout> Logger
@@ -29,15 +32,21 @@ else
<div class="lg:pl-72">
<AppHeader Layout="this"/>
<ErrorHandler>
<main class="py-10">
<div class="px-4 sm:px-6 lg:px-8">
<ErrorHandler CustomHandler="HandleException">
<PermissionHandler CheckFunction="CheckPermission">
<CascadingValue Value="this" IsFixed="true">
@Body
</CascadingValue>
</PermissionHandler>
</ErrorHandler>
</div>
</main>
</ErrorHandler>
</div>
</div>
}
@@ -127,4 +136,17 @@ else
await InvokeAsync(StateHasChanged);
}
private bool CheckPermission(string permission) => IdentityService.HasPermission(permission);
private Task<bool> HandleException(Exception exception, ErrorHandler handler)
{
if (exception is HttpApiException httpApiException && httpApiException.Status == 401)
{
Task.Run(Load);
return Task.FromResult(true);
}
return Task.FromResult(false);
}
}

View File

@@ -1,9 +1,11 @@
@using MoonCore.PluginFramework.Services
@using Moonlight.Client.Interfaces
@using Moonlight.Client.Models
@using Moonlight.Client.Services
@using Moonlight.Client.UI.Layouts
@inject NavigationManager Navigation
@inject IdentityService IdentityService
@inject ImplementationService ImplementationService
@{
@@ -111,7 +113,7 @@
<li>
@if (!string.IsNullOrEmpty(group.Key))
{
<div class="text-xs font-semibold leading-6 text-gray-400">
<div class="text-xs font-semibold leading-6 text-gray-400 my-2">
@group.Key
</div>
}
@@ -168,7 +170,7 @@
{
Items = ImplementationService.Get<ISidebarItemProvider>()
.SelectMany(x => x.Get())
//.Where(x => x.Permission == null || (x.Permission != null && IdentityService.HasPermission(x.Permission)))
.Where(x => x.Permission == null || (x.Permission != null && IdentityService.HasPermission(x.Permission)))
.GroupBy(x => x.Group ?? "")
.OrderByDescending(x => string.IsNullOrEmpty(x.Key))
.ToDictionary(x => x.Key, x => x.OrderBy(y => y.Priority).ToArray());

View File

@@ -1,11 +1,14 @@
@page "/admin/users"
@using MoonCore.Blazor.Tailwind.Attributes
@using MoonCore.Blazor.Tailwind.Forms.Components
@using MoonCore.Helpers
@using MoonCore.Models
@using Moonlight.Shared.Http.Requests.Admin.Users
@using Moonlight.Shared.Http.Responses.Admin.Users
@attribute [RequirePermission("admin.users.read")]
@inject HttpApiClient HttpApiClient
<Crud TItem="UserDetailResponse"