From 92afc85dbaa72914fbbe1a3632bd5040c84b9049 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sat, 21 Dec 2024 14:59:04 +0100 Subject: [PATCH] added permission configuration --- docs/permissions.md | 80 +++++++++++++++++++ docs/readme.md | 1 + src/HopFrame.Security/AdminPermissions.cs | 15 ---- .../HopFrameAuthenticationExtensions.cs | 2 + .../Authorization/AdminPermissionOptions.cs | 30 +++++++ .../Classes/AdminPermissionsAttribute.cs | 2 +- .../Generators/IAdminPageGenerator.cs | 2 +- .../Implementation/AdminPageGenerator.cs | 6 +- .../Models/AdminPagePermissions.cs | 2 +- src/HopFrame.Web/HopAdminContext.cs | 20 ++--- .../Pages/Administration/AdminDashboard.razor | 10 ++- .../Pages/Administration/AdminPageList.razor | 2 +- .../Administration/Layout/AdminMenu.razor | 2 +- 13 files changed, 138 insertions(+), 36 deletions(-) create mode 100644 docs/permissions.md delete mode 100644 src/HopFrame.Security/AdminPermissions.cs create mode 100644 src/HopFrame.Security/Authorization/AdminPermissionOptions.cs diff --git a/docs/permissions.md b/docs/permissions.md new file mode 100644 index 0000000..12a17cc --- /dev/null +++ b/docs/permissions.md @@ -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" +``` diff --git a/docs/readme.md b/docs/readme.md index 0fc3ff0..df7f363 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -8,6 +8,7 @@ The HopFrame comes in two variations, you can eiter only use the backend with so - [Repositories](./repositories.md) - [Base Models](./models.md) - [Authentication](./authentication.md) +- [Permissions](./permissions.md) ## HopFrame Web API diff --git a/src/HopFrame.Security/AdminPermissions.cs b/src/HopFrame.Security/AdminPermissions.cs deleted file mode 100644 index 7f45afc..0000000 --- a/src/HopFrame.Security/AdminPermissions.cs +++ /dev/null @@ -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"; -} \ No newline at end of file diff --git a/src/HopFrame.Security/Authentication/HopFrameAuthenticationExtensions.cs b/src/HopFrame.Security/Authentication/HopFrameAuthenticationExtensions.cs index d45b048..e0b7d37 100644 --- a/src/HopFrame.Security/Authentication/HopFrameAuthenticationExtensions.cs +++ b/src/HopFrame.Security/Authentication/HopFrameAuthenticationExtensions.cs @@ -1,3 +1,4 @@ +using HopFrame.Security.Authorization; using HopFrame.Security.Claims; using HopFrame.Security.Options; using Microsoft.AspNetCore.Authentication; @@ -20,6 +21,7 @@ public static class HopFrameAuthenticationExtensions { service.AddScoped(); service.AddOptionsFromConfiguration(configuration); + service.AddOptionsFromConfiguration(configuration); service.AddAuthentication(HopFrameAuthentication.SchemeName).AddScheme(HopFrameAuthentication.SchemeName, _ => {}); service.AddAuthorization(); diff --git a/src/HopFrame.Security/Authorization/AdminPermissionOptions.cs b/src/HopFrame.Security/Authorization/AdminPermissionOptions.cs new file mode 100644 index 0000000..46fc7f0 --- /dev/null +++ b/src/HopFrame.Security/Authorization/AdminPermissionOptions.cs @@ -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; } + } +} \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Attributes/Classes/AdminPermissionsAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Classes/AdminPermissionsAttribute.cs index 7d68eab..dc58ffb 100644 --- a/src/HopFrame.Web.Admin/Attributes/Classes/AdminPermissionsAttribute.cs +++ b/src/HopFrame.Web.Admin/Attributes/Classes/AdminPermissionsAttribute.cs @@ -8,6 +8,6 @@ public sealed class AdminPermissionsAttribute(string view = null, string create Create = create, Update = update, Delete = delete, - View = view + Read = view }; } diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs index 65998bd..05ff528 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs @@ -24,7 +24,7 @@ public interface IAdminPageGenerator { /// /// the specified permission /// - IAdminPageGenerator ViewPermission(string permission); + IAdminPageGenerator ReadPermission(string permission); /// /// Sets the permission needed to create a new Entry diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs index eb61f7d..3181c97 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs @@ -48,8 +48,8 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, return this; } - public IAdminPageGenerator ViewPermission(string permission) { - Page.Permissions.View = permission; + public IAdminPageGenerator ReadPermission(string permission) { + Page.Permissions.Read = permission; return this; } @@ -165,7 +165,7 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, var attribute = attributes.Single(a => a is AdminPermissionsAttribute) as AdminPermissionsAttribute; CreatePermission(attribute?.Permissions.Create); UpdatePermission(attribute?.Permissions.Update); - ViewPermission(attribute?.Permissions.View); + ReadPermission(attribute?.Permissions.Read); DeletePermission(attribute?.Permissions.Delete); } diff --git a/src/HopFrame.Web.Admin/Models/AdminPagePermissions.cs b/src/HopFrame.Web.Admin/Models/AdminPagePermissions.cs index e9629a6..0312aaa 100644 --- a/src/HopFrame.Web.Admin/Models/AdminPagePermissions.cs +++ b/src/HopFrame.Web.Admin/Models/AdminPagePermissions.cs @@ -1,7 +1,7 @@ namespace HopFrame.Web.Admin.Models; public sealed class AdminPagePermissions { - public string View { get; set; } + public string Read { get; set; } public string Create { get; set; } public string Update { get; set; } public string Delete { get; set; } diff --git a/src/HopFrame.Web/HopAdminContext.cs b/src/HopFrame.Web/HopAdminContext.cs index 0beffd2..198ce65 100644 --- a/src/HopFrame.Web/HopAdminContext.cs +++ b/src/HopFrame.Web/HopAdminContext.cs @@ -1,15 +1,17 @@ using System.Text.RegularExpressions; using HopFrame.Database.Models; using HopFrame.Security; +using HopFrame.Security.Authorization; using HopFrame.Web.Admin; using HopFrame.Web.Admin.Attributes; using HopFrame.Web.Admin.Generators; using HopFrame.Web.Admin.Models; using HopFrame.Web.Provider; +using Microsoft.Extensions.Options; namespace HopFrame.Web; -internal class HopAdminContext : AdminPagesContext { +internal class HopAdminContext(IOptions options) : AdminPagesContext { [AdminPageUrl("users")] public AdminPage Users { get; set; } @@ -21,10 +23,10 @@ internal class HopAdminContext : AdminPagesContext { generator.Page() .Description("On this page you can manage all user accounts.") .ConfigureProvider() - .ViewPermission(AdminPermissions.ViewUsers) - .CreatePermission(AdminPermissions.AddUser) - .UpdatePermission(AdminPermissions.EditUser) - .DeletePermission(AdminPermissions.DeleteUser); + .ReadPermission(options.Value.Users.Read) + .CreatePermission(options.Value.Users.Create) + .UpdatePermission(options.Value.Users.Update) + .DeletePermission(options.Value.Users.Delete); generator.Page().Property(u => u.Password) .DisplayInListing(false) @@ -64,10 +66,10 @@ internal class HopAdminContext : AdminPagesContext { generator.Page() .Description("On this page you can view, create, edit and delete permission groups.") .ConfigureProvider() - .ViewPermission(AdminPermissions.ViewGroups) - .CreatePermission(AdminPermissions.AddGroup) - .UpdatePermission(AdminPermissions.EditGroup) - .DeletePermission(AdminPermissions.DeleteGroup) + .ReadPermission(options.Value.Groups.Read) + .CreatePermission(options.Value.Groups.Create) + .UpdatePermission(options.Value.Groups.Update) + .DeletePermission(options.Value.Groups.Delete) .ListingProperty(g => g.Name); generator.Page().Property(g => g.Name) diff --git a/src/HopFrame.Web/Pages/Administration/AdminDashboard.razor b/src/HopFrame.Web/Pages/Administration/AdminDashboard.razor index 7ebb3cf..fe7afb1 100644 --- a/src/HopFrame.Web/Pages/Administration/AdminDashboard.razor +++ b/src/HopFrame.Web/Pages/Administration/AdminDashboard.razor @@ -5,25 +5,26 @@ @using BlazorStrap @using HopFrame.Web.Pages.Administration.Layout @using BlazorStrap.V5 -@using HopFrame.Security +@using HopFrame.Security.Authorization @using HopFrame.Web.Admin.Providers @using HopFrame.Web.Components @using Microsoft.AspNetCore.Components.Web +@using Microsoft.Extensions.Options @layout AdminLayout - + Admin Dashboard @foreach (var adminPage in Pages.LoadRegisteredAdminPages()) { - + @adminPage.Title - @adminPage.Permissions.View + @adminPage.Permissions.Read @adminPage.Description Open @@ -36,6 +37,7 @@ @inject NavigationManager Navigator @inject IAdminPagesProvider Pages +@inject IOptions Options @code { diff --git a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor index bf4c17d..d796aeb 100644 --- a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor +++ b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor @@ -18,7 +18,7 @@ @using HopFrame.Web.Components @_pageData.Title - + diff --git a/src/HopFrame.Web/Pages/Administration/Layout/AdminMenu.razor b/src/HopFrame.Web/Pages/Administration/Layout/AdminMenu.razor index a47bafb..409b002 100644 --- a/src/HopFrame.Web/Pages/Administration/Layout/AdminMenu.razor +++ b/src/HopFrame.Web/Pages/Administration/Layout/AdminMenu.razor @@ -24,7 +24,7 @@ Dashboard @foreach (var adminPage in Pages.LoadRegisteredAdminPages()) { - + @adminPage.Title }