Added documentation and improved permission validation

This commit is contained in:
2024-08-18 12:01:17 +02:00
parent 893431536d
commit 4aaf126c9d
16 changed files with 202 additions and 35 deletions

View File

@@ -5,7 +5,7 @@ using HopFrame.Web;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<DatabaseContext>(); builder.Services.AddDbContext<DatabaseContext>();
builder.Services.AddHopFrameServices<DatabaseContext>(); builder.Services.AddHopFrame<DatabaseContext>();
// Add services to the container. // Add services to the container.
builder.Services.AddRazorComponents() builder.Services.AddRazorComponents()

View File

@@ -1,2 +1,30 @@
# HopFrame API module # HopFrame API module
This module contains some useful endpoints for user login / register management. This module contains some useful endpoints for user login / register management.
## Ho to use the Web API version
1. Add the HopFrame.Api library to your project:
```
dotnet add package HopFrame.Api
```
2. Create a DbContext that inherits the ``HopDbContext`` and add a data source
```csharp
public class DatabaseContext : HopDbContextBase {
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlite("...");
}
}
```
3. Add the DbContext and HopFrame to your services
```csharp
builder.Services.AddDbContext<DatabaseContext>();
builder.Services.AddHopFrame<DatabaseContext>();
```

View File

@@ -0,0 +1,15 @@
namespace HopFrame.Security;
public static class AdminPermissions {
public const string IsAdmin = "hopframe.admin";
public const string ViewUsers = "hopframe.admin.users.view";
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";
}

View File

@@ -6,12 +6,16 @@ public static class PermissionValidator {
var permLow = permission.ToLower(); var permLow = permission.ToLower();
var permsLow = permissions.Select(perm => perm.ToLower()).ToArray(); var permsLow = permissions.Select(perm => perm.ToLower()).ToArray();
if (permsLow.Any(perm => perm == permLow || perm == "*")) return true; if (permsLow.Any(perm =>
perm == permLow ||
(perm.Length > permLow.Length && perm.StartsWith(permLow) && perm.ToCharArray()[permLow.Length] == '.') ||
perm == "*"))
return true;
foreach (var perm in permsLow) { foreach (var perm in permsLow) {
if (!perm.EndsWith(".*")) continue; if (!perm.EndsWith(".*")) continue;
var permissionGroup = perm.Replace(".*", ""); var permissionGroup = perm.Substring(0, perm.Length - 1);
if (permLow.StartsWith(permissionGroup)) return true; if (permLow.StartsWith(permissionGroup)) return true;
} }

View File

@@ -2,6 +2,13 @@ using HopFrame.Database.Models;
namespace HopFrame.Security.Services; namespace HopFrame.Security.Services;
/// <summary>
/// permission system:<br/>
/// - "*" -> all rights<br/>
/// - "group.[name]" -> group member<br/>
/// - "[namespace].[name]" -> single permission<br/>
/// - "[namespace].*" -> all permissions in the namespace
/// </summary>
public interface IPermissionService { public interface IPermissionService {
Task<bool> HasPermission(string permission, Guid user); Task<bool> HasPermission(string permission, Guid user);

View File

@@ -1,15 +1,16 @@
namespace HopFrame.Web; namespace HopFrame.Web;
[Obsolete("Use HopFrame.Security.AdminPermissions instead")]
public static class AdminPermissions { public static class AdminPermissions {
public const string IsAdmin = "hopframe.admin"; public const string IsAdmin = Security.AdminPermissions.IsAdmin;
public const string ViewUsers = "hopframe.admin.users.view"; public const string ViewUsers = Security.AdminPermissions.ViewUsers;
public const string EditUser = "hopframe.admin.users.edit"; public const string EditUser = Security.AdminPermissions.EditUser;
public const string DeleteUser = "hopframe.admin.users.delete"; public const string DeleteUser = Security.AdminPermissions.DeleteUser;
public const string AddUser = "hopframe.admin.users.add"; public const string AddUser = Security.AdminPermissions.AddUser;
public const string ViewGroups = "hopframe.admin.groups.view"; public const string ViewGroups = Security.AdminPermissions.ViewGroups;
public const string EditGroup = "hopframe.admin.groups.edit"; public const string EditGroup = Security.AdminPermissions.EditGroup;
public const string DeleteGroup = "hopframe.admin.groups.delete"; public const string DeleteGroup = Security.AdminPermissions.DeleteGroup;
public const string AddGroup = "hopframe.admin.groups.add"; public const string AddGroup = Security.AdminPermissions.AddGroup;
} }

View File

@@ -167,7 +167,7 @@
} }
if (_isEdit) { if (_isEdit) {
if (!(await Permissions.HasPermission(AdminPermissions.EditGroup, Context.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.EditGroup, Context.User.Id))) {
await NoEditPermissions(); await NoEditPermissions();
return; return;
} }
@@ -202,7 +202,7 @@
} }
if (_isEdit) { if (_isEdit) {
if (!(await Permissions.HasPermission(AdminPermissions.EditGroup, Context.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.EditGroup, Context.User.Id))) {
await NoEditPermissions(); await NoEditPermissions();
return; return;
} }
@@ -219,7 +219,7 @@
private async Task AddGroup() { private async Task AddGroup() {
if (_isEdit) { if (_isEdit) {
if (!(await Permissions.HasPermission(AdminPermissions.EditGroup, Context.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.EditGroup, Context.User.Id))) {
await NoEditPermissions(); await NoEditPermissions();
return; return;
} }
@@ -239,7 +239,7 @@
return; return;
} }
if (!(await Permissions.HasPermission(AdminPermissions.AddGroup, Context.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.AddGroup, Context.User.Id))) {
await NoAddPermissions(); await NoAddPermissions();
return; return;
} }

View File

@@ -69,7 +69,7 @@
} }
private async Task AddUser() { private async Task AddUser() {
if (!(await Permissions.HasPermission(AdminPermissions.AddUser, Auth.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.AddUser, Auth.User.Id))) {
await NoAddPermissions(); await NoAddPermissions();
return; return;
} }

View File

@@ -118,7 +118,7 @@
private string _permissionToAdd; private string _permissionToAdd;
public async Task ShowAsync(User user) { public async Task ShowAsync(User user) {
if (!(await Permissions.HasPermission(AdminPermissions.EditUser, Auth.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
await NoEditPermissions(); await NoEditPermissions();
return; return;
} }
@@ -130,7 +130,7 @@
} }
private async Task AddGroup() { private async Task AddGroup() {
if (!(await Permissions.HasPermission(AdminPermissions.EditUser, Auth.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
await NoEditPermissions(); await NoEditPermissions();
return; return;
} }
@@ -158,7 +158,7 @@
} }
private async Task RemoveGroup(PermissionGroup group) { private async Task RemoveGroup(PermissionGroup group) {
if (!(await Permissions.HasPermission(AdminPermissions.EditUser, Auth.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
await NoEditPermissions(); await NoEditPermissions();
return; return;
} }
@@ -186,7 +186,7 @@
} }
private async Task AddPermission() { private async Task AddPermission() {
if (!(await Permissions.HasPermission(AdminPermissions.EditUser, Auth.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
await NoEditPermissions(); await NoEditPermissions();
return; return;
} }
@@ -213,7 +213,7 @@
} }
private async Task RemovePermission(Permission perm) { private async Task RemovePermission(Permission perm) {
if (!(await Permissions.HasPermission(AdminPermissions.EditUser, Auth.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
await NoEditPermissions(); await NoEditPermissions();
return; return;
} }
@@ -241,7 +241,7 @@
} }
private async void EditUser() { private async void EditUser() {
if (!(await Permissions.HasPermission(AdminPermissions.EditUser, Auth.User.Id))) { if (!(await Permissions.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id))) {
await NoEditPermissions(); await NoEditPermissions();
return; return;
} }

View File

@@ -16,7 +16,7 @@
@using HopFrame.Web.Pages.Administration.Layout @using HopFrame.Web.Pages.Administration.Layout
<PageTitle>Groups</PageTitle> <PageTitle>Groups</PageTitle>
<AuthorizedView Permission="@AdminPermissions.ViewGroups" RedirectIfUnauthorized="administration/login?redirect=/administration/groups"/> <AuthorizedView Permission="@Security.AdminPermissions.ViewGroups" RedirectIfUnauthorized="administration/login?redirect=/administration/groups"/>
<GroupAddModal ReloadPage="Reload" @ref="_groupAddModal"/> <GroupAddModal ReloadPage="Reload" @ref="_groupAddModal"/>
@@ -32,7 +32,7 @@
<input class="form-control me-2 input-dark" type="search" placeholder="Search" aria-label="Search" @bind="_searchText"> <input class="form-control me-2 input-dark" type="search" placeholder="Search" aria-label="Search" @bind="_searchText">
<BSButton Color="BSColor.Success" IsOutlined="true" type="submit">Search</BSButton> <BSButton Color="BSColor.Success" IsOutlined="true" type="submit">Search</BSButton>
</form> </form>
<AuthorizedView Permission="@AdminPermissions.AddGroup"> <AuthorizedView Permission="@Security.AdminPermissions.AddGroup">
<BSButton IsSubmit="false" Color="BSColor.Success" Target="add-user" OnClick="() => _groupAddModal.ShowAsync()">Add Group</BSButton> <BSButton IsSubmit="false" Color="BSColor.Success" Target="add-user" OnClick="() => _groupAddModal.ShowAsync()">Add Group</BSButton>
</AuthorizedView> </AuthorizedView>
</div> </div>
@@ -112,8 +112,8 @@
protected override async Task OnInitializedAsync() { protected override async Task OnInitializedAsync() {
_groups = await Permissions.GetPermissionGroups(); _groups = await Permissions.GetPermissionGroups();
_hasEditPrivileges = await Permissions.HasPermission(AdminPermissions.EditGroup, Auth.User.Id); _hasEditPrivileges = await Permissions.HasPermission(Security.AdminPermissions.EditGroup, Auth.User.Id);
_hasDeletePrivileges = await Permissions.HasPermission(AdminPermissions.DeleteGroup, Auth.User.Id); _hasDeletePrivileges = await Permissions.HasPermission(Security.AdminPermissions.DeleteGroup, Auth.User.Id);
} }
private async Task Reload() { private async Task Reload() {

View File

@@ -2,7 +2,7 @@
@using BlazorStrap.V5 @using BlazorStrap.V5
@inherits LayoutComponentBase @inherits LayoutComponentBase
<AuthorizedView Permission="@AdminPermissions.IsAdmin" RedirectIfUnauthorized="administration/login" /> <AuthorizedView Permission="@Security.AdminPermissions.IsAdmin" RedirectIfUnauthorized="administration/login" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">

View File

@@ -53,13 +53,13 @@
Name = "Users", Name = "Users",
Url = "administration/users", Url = "administration/users",
Description = "On this page you can manage all user accounts.", Description = "On this page you can manage all user accounts.",
Permission = AdminPermissions.ViewUsers Permission = Security.AdminPermissions.ViewUsers
}, },
new () { new () {
Name = "Groups", Name = "Groups",
Url = "administration/groups", Url = "administration/groups",
Description = "On this page you can view, create, edit and delete permission groups.", Description = "On this page you can view, create, edit and delete permission groups.",
Permission = AdminPermissions.ViewGroups Permission = Security.AdminPermissions.ViewGroups
} }
}; };

View File

@@ -16,7 +16,7 @@
@using HopFrame.Web.Components.Administration @using HopFrame.Web.Components.Administration
<PageTitle>Users</PageTitle> <PageTitle>Users</PageTitle>
<AuthorizedView Permission="@AdminPermissions.ViewUsers" RedirectIfUnauthorized="administration/login?redirect=/administration/users"/> <AuthorizedView Permission="@Security.AdminPermissions.ViewUsers" RedirectIfUnauthorized="administration/login?redirect=/administration/users"/>
<UserAddModal @ref="_userAddModal" ReloadPage="Reload"/> <UserAddModal @ref="_userAddModal" ReloadPage="Reload"/>
<UserEditModal @ref="_userEditModal" ReloadPage="Reload"/> <UserEditModal @ref="_userEditModal" ReloadPage="Reload"/>
@@ -33,7 +33,7 @@
<input class="form-control me-2 input-dark" type="search" placeholder="Search" aria-label="Search" @bind="_searchText"> <input class="form-control me-2 input-dark" type="search" placeholder="Search" aria-label="Search" @bind="_searchText">
<BSButton Color="BSColor.Success" IsOutlined="true" type="submit">Search</BSButton> <BSButton Color="BSColor.Success" IsOutlined="true" type="submit">Search</BSButton>
</form> </form>
<AuthorizedView Permission="@AdminPermissions.AddUser"> <AuthorizedView Permission="@Security.AdminPermissions.AddUser">
<BSButton IsSubmit="false" Color="BSColor.Success" Target="add-user" OnClick="() => _userAddModal.ShowAsync()">Add User</BSButton> <BSButton IsSubmit="false" Color="BSColor.Success" Target="add-user" OnClick="() => _userAddModal.ShowAsync()">Add User</BSButton>
</AuthorizedView> </AuthorizedView>
</div> </div>
@@ -123,8 +123,8 @@
_userGroups.Add(user.Id, groups.LastOrDefault()); _userGroups.Add(user.Id, groups.LastOrDefault());
} }
_hasEditPrivileges = await PermissionsService.HasPermission(AdminPermissions.EditUser, Auth.User.Id); _hasEditPrivileges = await PermissionsService.HasPermission(Security.AdminPermissions.EditUser, Auth.User.Id);
_hasDeletePrivileges = await PermissionsService.HasPermission(AdminPermissions.DeleteUser, Auth.User.Id); _hasDeletePrivileges = await PermissionsService.HasPermission(Security.AdminPermissions.DeleteUser, Auth.User.Id);
} }
private async Task Reload() { private async Task Reload() {

View File

@@ -1,2 +1,43 @@
# HopFrame Web module # HopFrame Web module
This module contains useful helpers for Blazor Apps and an Admin Dashboard. This module contains useful helpers for Blazor Apps and an Admin Dashboard.
## How to use the Blazor API
1. Add the HopFrame.Web library to your project
```
dotnet add package HopFrame.Web
```
2. Create a DbContext that inherits the ``HopDbContext`` and add a data source
```csharp
public class DatabaseContext : HopDbContextBase {
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlite("...");
}
}
```
3. Add the DbContext and HopFrame to your services
```csharp
builder.Services.AddDbContext<DatabaseContext>();
builder.Services.AddHopFrame<DatabaseContext>();
```
4. Add the authentication middleware to your app
```csharp
app.UseMiddleware<AuthMiddleware>();
```
5. Add the HopFrame pages to your Razor components
```csharp
app.MapRazorComponents<App>()
.AddHopFrameAdminPages()
.AddInteractiveServerRenderMode();
```

View File

@@ -10,7 +10,7 @@ using Microsoft.Extensions.DependencyInjection;
namespace HopFrame.Web; namespace HopFrame.Web;
public static class ServiceCollectionExtensions { public static class ServiceCollectionExtensions {
public static IServiceCollection AddHopFrameServices<TDbContext>(this IServiceCollection services) where TDbContext : HopDbContextBase { public static IServiceCollection AddHopFrame<TDbContext>(this IServiceCollection services) where TDbContext : HopDbContextBase {
services.AddHttpClient(); services.AddHttpClient();
services.AddScoped<IAuthService, AuthService<TDbContext>>(); services.AddScoped<IAuthService, AuthService<TDbContext>>();
services.AddTransient<AuthMiddleware>(); services.AddTransient<AuthMiddleware>();

View File

@@ -6,3 +6,74 @@ A simple backend management api for ASP.NET Core Web APIs
- [x] User authentication - [x] User authentication
- [x] Permission management - [x] Permission management
- [x] Frontend dashboards - [x] Frontend dashboards
# Usage
There are two different versions of HopFrame, either the Web API version or the full Blazor web version.
## Ho to use the Web API version
1. Add the HopFrame.Api library to your project:
```
dotnet add package HopFrame.Api
```
2. Create a DbContext that inherits the ``HopDbContext`` and add a data source
```csharp
public class DatabaseContext : HopDbContextBase {
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlite("...");
}
}
```
3. Add the DbContext and HopFrame to your services
```csharp
builder.Services.AddDbContext<DatabaseContext>();
builder.Services.AddHopFrame<DatabaseContext>();
```
## How to use the Blazor API
1. Add the HopFrame.Web library to your project
```
dotnet add package HopFrame.Web
```
2. Create a DbContext that inherits the ``HopDbContext`` and add a data source
```csharp
public class DatabaseContext : HopDbContextBase {
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlite("...");
}
}
```
3. Add the DbContext and HopFrame to your services
```csharp
builder.Services.AddDbContext<DatabaseContext>();
builder.Services.AddHopFrame<DatabaseContext>();
```
4. Add the authentication middleware to your app
```csharp
app.UseMiddleware<AuthMiddleware>();
```
5. Add the HopFrame pages to your Razor components
```csharp
app.MapRazorComponents<App>()
.AddHopFrameAdminPages()
.AddInteractiveServerRenderMode();
```