added permission configuration

This commit is contained in:
2024-12-21 14:59:04 +01:00
parent 51c15eff4c
commit 92afc85dba
13 changed files with 138 additions and 36 deletions

80
docs/permissions.md Normal file
View File

@@ -0,0 +1,80 @@
# HopFrame Permissions
Permissions in the HopFrame are simple and effective to use.
As discussed in the [repositories](./repositories.md) documentation, you can manage user / group permissions
via the `IPermissionRepository` service.
## How do permissions work in the HopFrame
Permissions are defined using the . (dot) syntax. This enables you to nest permissions in namespaces.
You can also give a user or a group the permission to every permission in a namespace by using the * (star) syntax.
| Permission | Example | Description |
|----------------------|-------------------------------|-------------------------------------------------------|
| `*` | `*` | all permissions |
| `[namespace].[name]` | `hopframe.admin.users.create` | single permission |
| `[namespace].*` | `hopframe.admin.*` | all permissions in that namespace (works recursively) |
### Reserved namespaces
| Namespace | Example | Description |
|-----------|---------------|------------------------------------------|
| `group` | `group.admin` | The user needs to be in a specific group |
### Permission Groups
You can manage them through the `IGroupRepository` as described in the [repositories](./repositories.md) documentation.
You add permissions just like you would to a user with the `IPermissionRepository`.
You can assign a user to a group by assigning the group permission to the user:
```csharp
permissionRepository.AddPermission(user, "group.admin");
```
## Predefined Permissions
| Permission | Description |
|--------------------------------|-------------------------------|
| `hopframe.admin` | Access to the admin dashboard |
| `hopframe.admin.users.read` | View all users |
| `hopframe.admin.users.update` | Edit a user |
| `hopframe.admin.users.delete` | Delete a user |
| `hopframe.admin.users.create` | Add a group |
| `hopframe.admin.groups.read` | View all groups |
| `hopframe.admin.groups.update` | Edit a group |
| `hopframe.admin.groups.delete` | Delete a group |
| `hopframe.admin.groups.create` | Add a group |
### Configuring HopFrame permissions
You can also configure the predefined permissions using the `appsettings.json` or environment variables
by configuring your configuration to load these.
>**Hint**: Configuring your application to use environment variables works by simply adding
> `builder.Configuration.AddEnvironmentVariables();` to your startup configuration before you add the
> custom configurations / HopFrame services.
You can specify `Dashboard` for the dashboard permission and for `Users` and `Groups` you can specify
`Create`, `Read`, `Update` and `Delete` permissions.
#### Configuration example
```json
"HopFrame": {
"Permissions": {
"Dashboard": "myapp.dashboard.view",
"Users": {
"Read": "myapp.read.users"
},
"Groups": {
"Create": "myapp.create.groups",
"Update": "myapp.update.groups"
}
}
}
```
#### Environment variables example
```dotenv
HOPFRAME__PERMISSIONS__DASHBOARD="myapp.dashboard.view"
HOPFRAME__PERMISSIONS__USERS__READ="myapp.read.users"
HOPFRAME__PERMISSIONS__GROUPS__CREATE="myapp.create.groups"
HOPFRAME__PERMISSIONS__GROUPS__UPDATE="myapp.update.groups"
```

View File

@@ -8,6 +8,7 @@ The HopFrame comes in two variations, you can eiter only use the backend with so
- [Repositories](./repositories.md) - [Repositories](./repositories.md)
- [Base Models](./models.md) - [Base Models](./models.md)
- [Authentication](./authentication.md) - [Authentication](./authentication.md)
- [Permissions](./permissions.md)
## HopFrame Web API ## HopFrame Web API

View File

@@ -1,15 +0,0 @@
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

@@ -1,3 +1,4 @@
using HopFrame.Security.Authorization;
using HopFrame.Security.Claims; using HopFrame.Security.Claims;
using HopFrame.Security.Options; using HopFrame.Security.Options;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
@@ -20,6 +21,7 @@ public static class HopFrameAuthenticationExtensions {
service.AddScoped<ITokenContext, TokenContextImplementor>(); service.AddScoped<ITokenContext, TokenContextImplementor>();
service.AddOptionsFromConfiguration<HopFrameAuthenticationOptions>(configuration); service.AddOptionsFromConfiguration<HopFrameAuthenticationOptions>(configuration);
service.AddOptionsFromConfiguration<AdminPermissionOptions>(configuration);
service.AddAuthentication(HopFrameAuthentication.SchemeName).AddScheme<AuthenticationSchemeOptions, HopFrameAuthentication>(HopFrameAuthentication.SchemeName, _ => {}); service.AddAuthentication(HopFrameAuthentication.SchemeName).AddScheme<AuthenticationSchemeOptions, HopFrameAuthentication>(HopFrameAuthentication.SchemeName, _ => {});
service.AddAuthorization(); service.AddAuthorization();

View File

@@ -0,0 +1,30 @@
using HopFrame.Security.Options;
namespace HopFrame.Security.Authorization;
public class AdminPermissionOptions : OptionsFromConfiguration {
public override string Position { get; } = "HopFrame:Permissions";
public string Dashboard { get; set; } = "hopframe.admin";
public CrudPermission Users { get; set; } = new() {
Read = "hopframe.admin.users.read",
Update = "hopframe.admin.users.update",
Delete = "hopframe.admin.users.delete",
Create = "hopframe.admin.users.create"
};
public CrudPermission Groups { get; set; } = new() {
Read = "hopframe.admin.groups.read",
Update = "hopframe.admin.groups.update",
Delete = "hopframe.admin.groups.delete",
Create = "hopframe.admin.groups.create"
};
public class CrudPermission {
public string Create { get; set; }
public string Read { get; set; }
public string Update { get; set; }
public string Delete { get; set; }
}
}

View File

@@ -8,6 +8,6 @@ public sealed class AdminPermissionsAttribute(string view = null, string create
Create = create, Create = create,
Update = update, Update = update,
Delete = delete, Delete = delete,
View = view Read = view
}; };
} }

View File

@@ -24,7 +24,7 @@ public interface IAdminPageGenerator<TModel> {
/// </summary> /// </summary>
/// <param name="permission">the specified permission</param> /// <param name="permission">the specified permission</param>
/// <returns></returns> /// <returns></returns>
IAdminPageGenerator<TModel> ViewPermission(string permission); IAdminPageGenerator<TModel> ReadPermission(string permission);
/// <summary> /// <summary>
/// Sets the permission needed to create a new Entry /// Sets the permission needed to create a new Entry

View File

@@ -48,8 +48,8 @@ internal sealed class AdminPageGenerator<TModel> : IAdminPageGenerator<TModel>,
return this; return this;
} }
public IAdminPageGenerator<TModel> ViewPermission(string permission) { public IAdminPageGenerator<TModel> ReadPermission(string permission) {
Page.Permissions.View = permission; Page.Permissions.Read = permission;
return this; return this;
} }
@@ -165,7 +165,7 @@ internal sealed class AdminPageGenerator<TModel> : IAdminPageGenerator<TModel>,
var attribute = attributes.Single(a => a is AdminPermissionsAttribute) as AdminPermissionsAttribute; var attribute = attributes.Single(a => a is AdminPermissionsAttribute) as AdminPermissionsAttribute;
CreatePermission(attribute?.Permissions.Create); CreatePermission(attribute?.Permissions.Create);
UpdatePermission(attribute?.Permissions.Update); UpdatePermission(attribute?.Permissions.Update);
ViewPermission(attribute?.Permissions.View); ReadPermission(attribute?.Permissions.Read);
DeletePermission(attribute?.Permissions.Delete); DeletePermission(attribute?.Permissions.Delete);
} }

View File

@@ -1,7 +1,7 @@
namespace HopFrame.Web.Admin.Models; namespace HopFrame.Web.Admin.Models;
public sealed class AdminPagePermissions { public sealed class AdminPagePermissions {
public string View { get; set; } public string Read { get; set; }
public string Create { get; set; } public string Create { get; set; }
public string Update { get; set; } public string Update { get; set; }
public string Delete { get; set; } public string Delete { get; set; }

View File

@@ -1,15 +1,17 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using HopFrame.Database.Models; using HopFrame.Database.Models;
using HopFrame.Security; using HopFrame.Security;
using HopFrame.Security.Authorization;
using HopFrame.Web.Admin; using HopFrame.Web.Admin;
using HopFrame.Web.Admin.Attributes; using HopFrame.Web.Admin.Attributes;
using HopFrame.Web.Admin.Generators; using HopFrame.Web.Admin.Generators;
using HopFrame.Web.Admin.Models; using HopFrame.Web.Admin.Models;
using HopFrame.Web.Provider; using HopFrame.Web.Provider;
using Microsoft.Extensions.Options;
namespace HopFrame.Web; namespace HopFrame.Web;
internal class HopAdminContext : AdminPagesContext { internal class HopAdminContext(IOptions<AdminPermissionOptions> options) : AdminPagesContext {
[AdminPageUrl("users")] [AdminPageUrl("users")]
public AdminPage<User> Users { get; set; } public AdminPage<User> Users { get; set; }
@@ -21,10 +23,10 @@ internal class HopAdminContext : AdminPagesContext {
generator.Page<User>() generator.Page<User>()
.Description("On this page you can manage all user accounts.") .Description("On this page you can manage all user accounts.")
.ConfigureProvider<UserProvider>() .ConfigureProvider<UserProvider>()
.ViewPermission(AdminPermissions.ViewUsers) .ReadPermission(options.Value.Users.Read)
.CreatePermission(AdminPermissions.AddUser) .CreatePermission(options.Value.Users.Create)
.UpdatePermission(AdminPermissions.EditUser) .UpdatePermission(options.Value.Users.Update)
.DeletePermission(AdminPermissions.DeleteUser); .DeletePermission(options.Value.Users.Delete);
generator.Page<User>().Property(u => u.Password) generator.Page<User>().Property(u => u.Password)
.DisplayInListing(false) .DisplayInListing(false)
@@ -64,10 +66,10 @@ internal class HopAdminContext : AdminPagesContext {
generator.Page<PermissionGroup>() generator.Page<PermissionGroup>()
.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.")
.ConfigureProvider<GroupProvider>() .ConfigureProvider<GroupProvider>()
.ViewPermission(AdminPermissions.ViewGroups) .ReadPermission(options.Value.Groups.Read)
.CreatePermission(AdminPermissions.AddGroup) .CreatePermission(options.Value.Groups.Create)
.UpdatePermission(AdminPermissions.EditGroup) .UpdatePermission(options.Value.Groups.Update)
.DeletePermission(AdminPermissions.DeleteGroup) .DeletePermission(options.Value.Groups.Delete)
.ListingProperty(g => g.Name); .ListingProperty(g => g.Name);
generator.Page<PermissionGroup>().Property(g => g.Name) generator.Page<PermissionGroup>().Property(g => g.Name)

View File

@@ -5,25 +5,26 @@
@using BlazorStrap @using BlazorStrap
@using HopFrame.Web.Pages.Administration.Layout @using HopFrame.Web.Pages.Administration.Layout
@using BlazorStrap.V5 @using BlazorStrap.V5
@using HopFrame.Security @using HopFrame.Security.Authorization
@using HopFrame.Web.Admin.Providers @using HopFrame.Web.Admin.Providers
@using HopFrame.Web.Components @using HopFrame.Web.Components
@using Microsoft.AspNetCore.Components.Web @using Microsoft.AspNetCore.Components.Web
@using Microsoft.Extensions.Options
@layout AdminLayout @layout AdminLayout
<AuthorizedView Permission="@AdminPermissions.IsAdmin" RedirectIfUnauthorized="/administration/login" /> <AuthorizedView Permission="@Options.Value.Dashboard" RedirectIfUnauthorized="/administration/login" />
<PageTitle>Admin Dashboard</PageTitle> <PageTitle>Admin Dashboard</PageTitle>
<BSContainer> <BSContainer>
<BSRow Justify="Justify.Center"> <BSRow Justify="Justify.Center">
@foreach (var adminPage in Pages.LoadRegisteredAdminPages()) { @foreach (var adminPage in Pages.LoadRegisteredAdminPages()) {
<AuthorizedView Permission="@adminPage.Permissions.View"> <AuthorizedView Permission="@adminPage.Permissions.Read">
<BSCol Column="4" style="margin-bottom: 10px"> <BSCol Column="4" style="margin-bottom: 10px">
<BSCard CardType="CardType.Card" Color="BSColor.Dark" style="min-height: 200px; min-width: 200px"> <BSCard CardType="CardType.Card" Color="BSColor.Dark" style="min-height: 200px; min-width: 200px">
<BSCard CardType="CardType.Body" style="display: flex; flex-direction: column"> <BSCard CardType="CardType.Body" style="display: flex; flex-direction: column">
<BSCard CardType="CardType.Title">@adminPage.Title</BSCard> <BSCard CardType="CardType.Title">@adminPage.Title</BSCard>
<BSCard CardType="CardType.Subtitle"><span style="color: gray">@adminPage.Permissions.View</span></BSCard> <BSCard CardType="CardType.Subtitle"><span style="color: gray">@adminPage.Permissions.Read</span></BSCard>
<BSCard CardType="CardType.Text">@adminPage.Description</BSCard> <BSCard CardType="CardType.Text">@adminPage.Description</BSCard>
<BSButton IsOutlined="true" MarginTop="Margins.Auto" style="width: max-content; align-self: center" OnClick="() => NavigateTo(adminPage.Url)" Color="BSColor.Light">Open</BSButton> <BSButton IsOutlined="true" MarginTop="Margins.Auto" style="width: max-content; align-self: center" OnClick="() => NavigateTo(adminPage.Url)" Color="BSColor.Light">Open</BSButton>
</BSCard> </BSCard>
@@ -36,6 +37,7 @@
@inject NavigationManager Navigator @inject NavigationManager Navigator
@inject IAdminPagesProvider Pages @inject IAdminPagesProvider Pages
@inject IOptions<AdminPermissionOptions> Options
@code { @code {

View File

@@ -18,7 +18,7 @@
@using HopFrame.Web.Components @using HopFrame.Web.Components
<PageTitle>@_pageData.Title</PageTitle> <PageTitle>@_pageData.Title</PageTitle>
<AuthorizedView Permission="@_pageData.Permissions.View" RedirectIfUnauthorized="@GenerateRedirectString()" /> <AuthorizedView Permission="@_pageData.Permissions.Read" RedirectIfUnauthorized="@GenerateRedirectString()" />
<AdminPageModal ReloadDelegate="Reload" @ref="_modal"/> <AdminPageModal ReloadDelegate="Reload" @ref="_modal"/>

View File

@@ -24,7 +24,7 @@
<BSNavItem IsActive="IsDashboardActive()" OnClick="NavigateToDashboard">Dashboard</BSNavItem> <BSNavItem IsActive="IsDashboardActive()" OnClick="NavigateToDashboard">Dashboard</BSNavItem>
@foreach (var adminPage in Pages.LoadRegisteredAdminPages()) { @foreach (var adminPage in Pages.LoadRegisteredAdminPages()) {
<AuthorizedView Permission="@adminPage.Permissions.View"> <AuthorizedView Permission="@adminPage.Permissions.Read">
<BSNavItem IsActive="IsNavItemActive(adminPage.Url)" OnClick="() => Navigate(adminPage.Url)">@adminPage.Title</BSNavItem> <BSNavItem IsActive="IsNavItemActive(adminPage.Url)" OnClick="() => Navigate(adminPage.Url)">@adminPage.Title</BSNavItem>
</AuthorizedView> </AuthorizedView>
} }