设计权限定义和权限提供者

This commit is contained in:
hello 2024-03-16 23:05:06 +08:00
parent 6742812bf2
commit 5a170f3024
14 changed files with 418 additions and 12 deletions

View File

@ -1,4 +1,4 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.9.34414.90 VisualStudioVersion = 17.9.34414.90
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
@ -18,8 +18,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HelloShop.ProductService",
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HelloShop.BasketService", "src\HelloShop.BasketService\HelloShop.BasketService.csproj", "{02EBA5AD-84B4-4AF4-B519-72061C08800D}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HelloShop.BasketService", "src\HelloShop.BasketService\HelloShop.BasketService.csproj", "{02EBA5AD-84B4-4AF4-B519-72061C08800D}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HelloShop.HybridApp", "src\HelloShop.HybridApp\HelloShop.HybridApp.csproj", "{CC0E5839-B7E9-400E-9AF3-95863BBF518B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{1AD03316-A743-4E9D-B3BC-FB9499D15141}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{1AD03316-A743-4E9D-B3BC-FB9499D15141}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{29BE158E-825E-48AB-A02D-4E537A5DC502}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{29BE158E-825E-48AB-A02D-4E537A5DC502}"
@ -66,12 +64,6 @@ Global
{02EBA5AD-84B4-4AF4-B519-72061C08800D}.Debug|Any CPU.Build.0 = Debug|Any CPU {02EBA5AD-84B4-4AF4-B519-72061C08800D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{02EBA5AD-84B4-4AF4-B519-72061C08800D}.Release|Any CPU.ActiveCfg = Release|Any CPU {02EBA5AD-84B4-4AF4-B519-72061C08800D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{02EBA5AD-84B4-4AF4-B519-72061C08800D}.Release|Any CPU.Build.0 = Release|Any CPU {02EBA5AD-84B4-4AF4-B519-72061C08800D}.Release|Any CPU.Build.0 = Release|Any CPU
{CC0E5839-B7E9-400E-9AF3-95863BBF518B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CC0E5839-B7E9-400E-9AF3-95863BBF518B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC0E5839-B7E9-400E-9AF3-95863BBF518B}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{CC0E5839-B7E9-400E-9AF3-95863BBF518B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC0E5839-B7E9-400E-9AF3-95863BBF518B}.Release|Any CPU.Build.0 = Release|Any CPU
{CC0E5839-B7E9-400E-9AF3-95863BBF518B}.Release|Any CPU.Deploy.0 = Release|Any CPU
{2022279A-E39F-4489-82AE-39AC53C594C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2022279A-E39F-4489-82AE-39AC53C594C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2022279A-E39F-4489-82AE-39AC53C594C9}.Debug|Any CPU.Build.0 = Debug|Any CPU {2022279A-E39F-4489-82AE-39AC53C594C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2022279A-E39F-4489-82AE-39AC53C594C9}.Release|Any CPU.ActiveCfg = Release|Any CPU {2022279A-E39F-4489-82AE-39AC53C594C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -93,7 +85,6 @@ Global
{C4789097-3694-4370-9252-44268661A26E} = {1AD03316-A743-4E9D-B3BC-FB9499D15141} {C4789097-3694-4370-9252-44268661A26E} = {1AD03316-A743-4E9D-B3BC-FB9499D15141}
{B4C0ADA2-0442-4B7E-BFD9-AA39B52A0D42} = {1AD03316-A743-4E9D-B3BC-FB9499D15141} {B4C0ADA2-0442-4B7E-BFD9-AA39B52A0D42} = {1AD03316-A743-4E9D-B3BC-FB9499D15141}
{02EBA5AD-84B4-4AF4-B519-72061C08800D} = {1AD03316-A743-4E9D-B3BC-FB9499D15141} {02EBA5AD-84B4-4AF4-B519-72061C08800D} = {1AD03316-A743-4E9D-B3BC-FB9499D15141}
{CC0E5839-B7E9-400E-9AF3-95863BBF518B} = {1AD03316-A743-4E9D-B3BC-FB9499D15141}
{2022279A-E39F-4489-82AE-39AC53C594C9} = {29BE158E-825E-48AB-A02D-4E537A5DC502} {2022279A-E39F-4489-82AE-39AC53C594C9} = {29BE158E-825E-48AB-A02D-4E537A5DC502}
{45932B7F-6ED0-40F3-AA2C-F14A844FEE18} = {29BE158E-825E-48AB-A02D-4E537A5DC502} {45932B7F-6ED0-40F3-AA2C-F14A844FEE18} = {29BE158E-825E-48AB-A02D-4E537A5DC502}
EndGlobalSection EndGlobalSection

View File

@ -12,19 +12,20 @@ namespace HelloShop.IdentityService.Controllers
public class TestsController : ControllerBase public class TestsController : ControllerBase
{ {
[HttpGet(nameof(Foo))] [HttpGet(nameof(Foo))]
[Authorize(Roles = "AdminRole")] [Authorize(IdentityPermissions.Users.Update)]
public IActionResult Foo() public IActionResult Foo()
{ {
return Ok("Hello, World!"); return Ok("Hello, World!");
} }
[HttpGet(nameof(Bar))] [HttpGet(nameof(Bar))]
[Authorize(Roles = "GuestRole")] [Authorize(IdentityPermissions.Users.Create)]
public IActionResult Bar() public IActionResult Bar()
{ {
return Ok("Hello, World!"); return Ok("Hello, World!");
} }
[Authorize(IdentityPermissions.Users.Delete)]
[HttpGet(nameof(Baz))] [HttpGet(nameof(Baz))]
public IActionResult Baz() public IActionResult Baz()
{ {

View File

@ -0,0 +1,25 @@
using HelloShop.ServiceDefaults.Permissions;
namespace HelloShop.IdentityService;
public class IdentityPermissionDefinitionProvider : IPermissionDefinitionProvider
{
public void Define(PermissionDefinitionContext context)
{
var identityGroup = context.AddGroup(IdentityPermissions.GroupName, "访问控制");
var roles = identityGroup.AddPermission(IdentityPermissions.Roles.Default, "角色管理");
roles.AddChild(IdentityPermissions.Roles.Create, "创建角色");
roles.AddChild(IdentityPermissions.Roles.Update, "更新角色");
roles.AddChild(IdentityPermissions.Roles.Delete, "删除角色");
roles.AddChild(IdentityPermissions.Roles.ManagePermissions, "管理角色权限");
var users = identityGroup.AddPermission(IdentityPermissions.Users.Default, "用户管理");
users.AddChild(IdentityPermissions.Users.Create, "创建用户");
users.AddChild(IdentityPermissions.Users.Update, "更新用户");
users.AddChild(IdentityPermissions.Users.Delete, "删除用户");
users.AddChild(IdentityPermissions.Users.ManageRoles, "管理用户角色");
}
}

View File

@ -0,0 +1,24 @@
namespace HelloShop.IdentityService;
public static class IdentityPermissions
{
public const string GroupName = "Identity";
public static class Roles
{
public const string Default = GroupName + ".Roles";
public const string Create = Default + ".Create";
public const string Update = Default + ".Update";
public const string Delete = Default + ".Delete";
public const string ManagePermissions = Default + ".ManagePermissions";
}
public static class Users
{
public const string Default = GroupName + ".Users";
public const string Create = Default + ".Create";
public const string Update = Default + ".Update";
public const string Delete = Default + ".Delete";
public const string ManageRoles = Update + ".ManageRoles";
}
}

View File

@ -53,6 +53,7 @@ builder.Services.AddAuthentication(options =>
builder.Services.AddDataSeedingProviders(); builder.Services.AddDataSeedingProviders();
builder.Services.AddOpenApi(); builder.Services.AddOpenApi();
builder.Services.AddPermissionDefinitions();
var app = builder.Build(); var app = builder.Build();
@ -66,5 +67,6 @@ app.MapControllers();
app.UseDataSeedingProviders(); app.UseDataSeedingProviders();
app.UseOpenApi(); app.UseOpenApi();
app.MapGroup("api/Permissions").MapPermissionDefinitions("Permissions");
app.Run(); app.Run();

View File

@ -0,0 +1,65 @@
using HelloShop.ServiceDefaults.Permissions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
namespace HelloShop.ServiceDefaults.Extensions;
public static class PermissionExtensions
{
public static IServiceCollection AddPermissionDefinitions(this IServiceCollection services, Assembly? assembly = null)
{
assembly ??= Assembly.GetCallingAssembly();
var permissionDefinitionProviders = assembly.ExportedTypes.Where(t => t.IsAssignableTo(typeof(IPermissionDefinitionProvider)));
permissionDefinitionProviders.ToList().ForEach(t => services.AddSingleton(typeof(IPermissionDefinitionProvider), t));
services.AddSingleton<IPermissionDefinitionManager, PermissionDefinitionManager>();
return services;
}
public static IEndpointRouteBuilder MapPermissionDefinitions(this IEndpointRouteBuilder endpoints, params string[] tags)
{
var routeGroup = endpoints.MapGroup(string.Empty);
routeGroup.MapGet("PermissionDefinitions", async (IPermissionDefinitionManager permissionDefinitionManager) =>
{
List<PermissionGroupDefinitionResponse> result = [];
var permissionGroups = await permissionDefinitionManager.GetGroupsAsync();
foreach (var permissionGroup in permissionGroups)
{
PermissionGroupDefinitionResponse permissionGroupDefinition = new()
{
Name = permissionGroup.Name,
DisplayName = permissionGroup.DisplayName,
Permissions = []
};
foreach (PermissionDefinition? permission in permissionGroup.GetPermissionsWithChildren())
{
PermissionDefinitionResponse permissionDefinition = new()
{
Name = permission.Name,
DisplayName = permission.DisplayName,
ParentName = permission.Parent?.Name
};
permissionGroupDefinition.Permissions.Add(permissionDefinition);
}
result.Add(permissionGroupDefinition);
}
return result;
}).WithTags(tags);
return routeGroup;
}
}

View File

@ -0,0 +1,10 @@
namespace HelloShop.ServiceDefaults.Permissions;
public class PermissionDefinitionResponse
{
public required string Name { get; init; }
public string? DisplayName { get; set; }
public string? ParentName { get; set; }
}

View File

@ -0,0 +1,10 @@
namespace HelloShop.ServiceDefaults.Permissions;
public class PermissionGroupDefinitionResponse
{
public required string Name { get; init; }
public string? DisplayName { get; set; }
public required List<PermissionDefinitionResponse> Permissions { get; init; }
}

View File

@ -0,0 +1,12 @@
namespace HelloShop.ServiceDefaults.Permissions;
public interface IPermissionDefinitionManager
{
Task<PermissionDefinition> GetAsync(string name);
Task<PermissionDefinition?> GetOrNullAsync(string name);
Task<IReadOnlyList<PermissionDefinition>> GetPermissionsAsync();
Task<IReadOnlyList<PermissionGroupDefinition>> GetGroupsAsync();
}

View File

@ -0,0 +1,6 @@
namespace HelloShop.ServiceDefaults.Permissions;
public interface IPermissionDefinitionProvider
{
void Define(PermissionDefinitionContext context);
}

View File

@ -0,0 +1,30 @@
namespace HelloShop.ServiceDefaults.Permissions;
public class PermissionDefinition
{
public string Name { get; } = default!;
public string? DisplayName { get; set; }
public PermissionDefinition? Parent { get; private set; }
public bool IsEnabled { get; set; }
private readonly List<PermissionDefinition> _children = [];
public IReadOnlyList<PermissionDefinition> Children => [.. _children];
protected internal PermissionDefinition(string name, string? displayName = null, bool isEnabled = true)
{
Name = name;
DisplayName = displayName;
IsEnabled = isEnabled;
}
public virtual PermissionDefinition AddChild(string name, string? displayName = null, bool isEnabled = true)
{
var child = new PermissionDefinition(name, displayName, isEnabled) { Parent = this };
_children.Add(child);
return child;
}
}

View File

@ -0,0 +1,66 @@
namespace HelloShop.ServiceDefaults.Permissions;
public class PermissionDefinitionContext
{
public IServiceProvider ServiceProvider { get; }
internal Dictionary<string, PermissionGroupDefinition> Groups { get; }
internal PermissionDefinitionContext(IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
Groups = [];
}
public virtual PermissionGroupDefinition AddGroup(string name, string? displayName = null)
{
if (Groups.ContainsKey(name))
{
throw new InvalidOperationException($"There is already an existing permission group with name: {name}");
}
return Groups[name] = new PermissionGroupDefinition(name, displayName);
}
public virtual PermissionGroupDefinition GetGroup(string name)
{
PermissionGroupDefinition? group = GetGroupOrNull(name);
return group is null ? throw new InvalidOperationException($"Could not find a permission definition group with the given name: {name}") : group;
}
public virtual PermissionGroupDefinition? GetGroupOrNull(string name)
{
if (!Groups.TryGetValue(name, out PermissionGroupDefinition? value))
{
return null;
}
return value;
}
public virtual void RemoveGroup(string name)
{
if (!Groups.ContainsKey(name))
{
throw new InvalidOperationException($"Not found permission group with name: {name}");
}
Groups.Remove(name);
}
public virtual PermissionDefinition? GetPermissionOrNull(string name)
{
foreach (var groupDefinition in Groups.Values)
{
var permissionDefinition = groupDefinition.GetPermissionOrNull(name);
if (permissionDefinition != null)
{
return permissionDefinition;
}
}
return null;
}
}

View File

@ -0,0 +1,96 @@

using Microsoft.Extensions.DependencyInjection;
namespace HelloShop.ServiceDefaults.Permissions;
public class PermissionDefinitionManager : IPermissionDefinitionManager
{
private readonly Lazy<Dictionary<string, PermissionGroupDefinition>> _lazyPermissionGroupDefinitions;
protected IDictionary<string, PermissionGroupDefinition> PermissionGroupDefinitions => _lazyPermissionGroupDefinitions.Value;
private readonly Lazy<Dictionary<string, PermissionDefinition>> _lazyPermissionDefinitions;
protected IDictionary<string, PermissionDefinition> PermissionDefinitions => _lazyPermissionDefinitions.Value;
private readonly IServiceProvider _serviceProvider;
public PermissionDefinitionManager(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_lazyPermissionGroupDefinitions = new Lazy<Dictionary<string, PermissionGroupDefinition>>(CreatePermissionGroupDefinitions, isThreadSafe: true);
_lazyPermissionDefinitions = new Lazy<Dictionary<string, PermissionDefinition>>(CreatePermissionDefinitions, isThreadSafe: true);
}
protected virtual Dictionary<string, PermissionGroupDefinition> CreatePermissionGroupDefinitions()
{
using var scope = _serviceProvider.CreateScope();
var context = new PermissionDefinitionContext(scope.ServiceProvider);
var providers = _serviceProvider.GetServices<IPermissionDefinitionProvider>();
foreach (IPermissionDefinitionProvider provider in providers)
{
provider.Define(context);
}
return context.Groups;
}
protected virtual Dictionary<string, PermissionDefinition> CreatePermissionDefinitions()
{
var permissions = new Dictionary<string, PermissionDefinition>();
foreach (var groupDefinition in PermissionGroupDefinitions.Values)
{
foreach (var permission in groupDefinition.Permissions)
{
AddPermissionToDictionaryRecursively(permissions, permission);
}
}
return permissions;
}
protected virtual void AddPermissionToDictionaryRecursively(Dictionary<string, PermissionDefinition> permissions, PermissionDefinition permission)
{
if (permissions.ContainsKey(permission.Name))
{
throw new InvalidOperationException($"Duplicate permission name {permission.Name}");
}
permissions[permission.Name] = permission;
foreach (var child in permission.Children)
{
AddPermissionToDictionaryRecursively(permissions, child);
}
}
public async Task<PermissionDefinition> GetAsync(string name)
{
var permission = await GetOrNullAsync(name);
return permission ?? throw new InvalidOperationException($"Undefined permission {name}");
}
public Task<PermissionDefinition?> GetOrNullAsync(string name)
{
return Task.FromResult(PermissionDefinitions.TryGetValue(name, out var obj) ? obj : default);
}
public Task<IReadOnlyList<PermissionGroupDefinition>> GetGroupsAsync()
{
IReadOnlyList<PermissionGroupDefinition> permissionGroups = [.. PermissionGroupDefinitions.Values];
return Task.FromResult(permissionGroups);
}
public Task<IReadOnlyList<PermissionDefinition>> GetPermissionsAsync()
{
IReadOnlyList<PermissionDefinition> permissions = [.. PermissionDefinitions.Values];
return Task.FromResult(permissions);
}
}

View File

@ -0,0 +1,68 @@
namespace HelloShop.ServiceDefaults.Permissions;
public class PermissionGroupDefinition
{
public string Name { get; } = default!;
public string? DisplayName { get; set; }
private readonly List<PermissionDefinition> _permissions = [];
public IReadOnlyList<PermissionDefinition> Permissions => [.. _permissions];
protected internal PermissionGroupDefinition(string name, string? displayName = null)
{
Name = name;
DisplayName = displayName;
}
public virtual PermissionDefinition AddPermission(string name, string? displayName = null, bool isEnabled = true)
{
var permission = new PermissionDefinition(name, displayName, isEnabled);
_permissions.Add(permission);
return permission;
}
public virtual List<PermissionDefinition> GetPermissionsWithChildren()
{
var permissions = new List<PermissionDefinition>();
foreach (var permission in _permissions)
{
AddPermissionToListRecursively(permissions, permission);
}
return permissions;
}
private static void AddPermissionToListRecursively(List<PermissionDefinition> permissions, PermissionDefinition permission)
{
permissions.Add(permission);
foreach (var child in permission.Children)
{
AddPermissionToListRecursively(permissions, child);
}
}
public PermissionDefinition? GetPermissionOrNull(string name) => GetPermissionOrNullRecursively(Permissions, name);
private static PermissionDefinition? GetPermissionOrNullRecursively(IReadOnlyList<PermissionDefinition> permissions, string name)
{
foreach (var permission in permissions)
{
if (permission.Name == name)
{
return permission;
}
var childPermission = GetPermissionOrNullRecursively(permission.Children, name);
if (childPermission != null)
{
return childPermission;
}
}
return null;
}
}