造輪子之許可權管理

2023-10-10 12:01:43

上文已經完成了自定義授權策略,那麼接下來就得完善我們的許可權管理了。不然沒有資料,如何鑑權~

表設計

建立我們的表實體類:

namespace Wheel.Domain.Permissions
{
    public class PermissionGrant : Entity<Guid>
    {
        public string Permission { get; set; }
        public string GrantType { get; set; }
        public string GrantValue { get; set; }
    }
}

Permission表示許可權名稱,結構為"{controllerName}:{actionName}"。
GrantType表示許可權型別,如角色許可權則R表示,方便後續在新增別的許可權型別時可以靈活擴充套件。
GrantValue則表示許可權型別對應的值,比如GrantType是R時,GrantValue是admin則表示admin角色的授權。

修改DbContext

在WheelDbContext新增程式碼:

#region Permission
public DbSet<PermissionGrant> PermissionGrants { get; set; }
#endregion
void ConfigurePermissionGrants(ModelBuilder builder)
{
    builder.Entity<PermissionGrant>(b =>
    {
        b.HasKey(o => o.Id);
        b.Property(o => o.Permission).HasMaxLength(128);
        b.Property(o => o.GrantValue).HasMaxLength(128);
        b.Property(o => o.GrantType).HasMaxLength(32);
    });
}

在OnModelCreating新增ConfigurePermissionGrants方法。

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);

    ConfigurePermissionGrants(builder);
}

接下來執行資料庫遷移命令即可完成表建立。

實現許可權管理

接下來就是實現我們的許可權管理功能。
我們的PermissionManageAppService只需定義三個API即可滿足管理需求。分別是獲取當前使用者所有許可權,修改使用者許可權,獲取指定角色許可權。

namespace Wheel.Services.PermissionManage
{
    public interface IPermissionManageAppService : ITransientDependency
    {
        Task<R<List<GetAllPermissionDto>>> GetPermission();
        Task<R> UpdatePermission(UpdatePermissionDto dto);
        Task<R<List<GetAllPermissionDto>>> GetRolePermission(string RoleName);
    }
}

具體實現程式碼如下:

namespace Wheel.Services.PermissionManage
{
    public class PermissionManageAppService : WheelServiceBase, IPermissionManageAppService
    {
        private readonly IBasicRepository<PermissionGrant, Guid> _permissionGrantRepository;
        private readonly RoleManager<Role> _roleManager;
        private readonly XmlCommentHelper _xmlCommentHelper;
        public PermissionManageAppService(XmlCommentHelper xmlCommentHelper, IBasicRepository<PermissionGrant, Guid> permissionGrantRepository, RoleManager<Role> roleManager)
        {
            _xmlCommentHelper = xmlCommentHelper;
            _permissionGrantRepository = permissionGrantRepository;
            _roleManager = roleManager;
        }
        public async Task<R<List<GetAllPermissionDto>>> GetPermission()
        {
            var result = await GetAllDefinePermission();
            if (CurrentUser.IsInRoles("admin"))
            {
                result.ForEach(p => p.Permissions.ForEach(a => a.IsGranted = true));
            }
            else
            {
                var grantPermissions = (await _permissionGrantRepository
                    .SelectListAsync(a => a.GrantType == "R" && CurrentUser.Roles.Contains(a.GrantValue), a => a.Permission))
                    .Distinct().ToList();

                foreach (var group in result)
                {
                    foreach (var permission in group.Permissions)
                    {
                        if (grantPermissions.Any(b => b == $"{group.Group}:{permission.Name}"))
                            permission.IsGranted = true;
                        else
                            permission.IsGranted = false;
                    }
                }
            }
            return new R<List<GetAllPermissionDto>>(result);
        }
        public async Task<R<List<GetAllPermissionDto>>> GetRolePermission(string RoleName)
        {
            var result = await GetAllDefinePermission();

            var grantPermissions = (await _permissionGrantRepository
                .SelectListAsync(a => a.GrantType == "R" && RoleName == a.GrantValue, a => a.Permission))
                .Distinct().ToList();

            foreach (var group in result)
            {
                foreach (var permission in group.Permissions)
                {
                    if (grantPermissions.Any(b => b == $"{group.Group}:{permission.Name}"))
                        permission.IsGranted = true;
                    else
                        permission.IsGranted = false;
                }
            }

            return new R<List<GetAllPermissionDto>>(result);
        }
        public async Task<R> UpdatePermission(UpdatePermissionDto dto)
        {
            if(dto.Type == "R") 
            {
                var exsit = await _roleManager.RoleExistsAsync(dto.Value);
                if (!exsit)
                    throw new BusinessException(ErrorCode.RoleNotExist, "RoleNotExist")
                        .WithMessageDataData(dto.Value);
            }
            using (var tran = await UnitOfWork.BeginTransactionAsync())
            {
                await _permissionGrantRepository.DeleteAsync(a => a.GrantType == dto.Type && a.GrantValue == dto.Value);
                await _permissionGrantRepository.InsertManyAsync(dto.Permissions.Select(a=> new PermissionGrant 
                {
                    Id = GuidGenerator.Create(),
                    GrantType = dto.Type,
                    GrantValue = dto.Value,
                    Permission = a
                }).ToList());
                await DistributedCache.SetAsync($"Permission:{dto.Type}:{dto.Value}", dto.Permissions);
                await tran.CommitAsync();
            }
            return new R();
        }

        private ValueTask<List<GetAllPermissionDto>> GetAllDefinePermission()
        {

            var result = MemoryCache.Get<List<GetAllPermissionDto>>("AllDefinePermission");
            if (result == null)
            {
                result = new List<GetAllPermissionDto>();
                var apiDescriptionGroupCollectionProvider = ServiceProvider.GetRequiredService<IApiDescriptionGroupCollectionProvider>();
                var apiDescriptionGroups = apiDescriptionGroupCollectionProvider.ApiDescriptionGroups.Items.SelectMany(group => group.Items)
                    .Where(a => a.ActionDescriptor is ControllerActionDescriptor)
                    .GroupBy(a => (a.ActionDescriptor as ControllerActionDescriptor).ControllerTypeInfo);

                foreach (var apiDescriptions in apiDescriptionGroups)
                {
                    var permissionGroup = new GetAllPermissionDto();
                    var controllerTypeInfo = apiDescriptions.Key;

                    var controllerAllowAnonymous = controllerTypeInfo.GetAttribute<AllowAnonymousAttribute>();

                    var controllerComment = _xmlCommentHelper.GetTypeComment(controllerTypeInfo);

                    permissionGroup.Group = controllerTypeInfo.Name;
                    permissionGroup.Summary = controllerComment;
                    foreach (var apiDescription in apiDescriptions)
                    {
                        var method = controllerTypeInfo.GetMethod(apiDescription.ActionDescriptor.RouteValues["action"]);
                        var actionAllowAnonymous = method.GetAttribute<AllowAnonymousAttribute>();
                        var actionAuthorize = method.GetAttribute<AuthorizeAttribute>();
                        if ((controllerAllowAnonymous == null && actionAllowAnonymous == null) || actionAuthorize != null)
                        {
                            var methodComment = _xmlCommentHelper.GetMethodComment(method);
                            permissionGroup.Permissions.Add(new PermissionDto { Name = method.Name, Summary = methodComment });
                        }
                    }
                    if (permissionGroup.Permissions.Count > 0)
                        result.Add(permissionGroup);
                }
                MemoryCache.Set("AllDefinePermission", result);
            }
            return ValueTask.FromResult(result);
        }
    }
}

控制器程式碼如下:

namespace Wheel.Controllers
{
    /// <summary>
    /// 許可權管理
    /// </summary>
    [Route("api/[controller]")]
    [ApiController]
    public class PermissionManageController : WheelControllerBase
    {
        private readonly IPermissionManageAppService _permissionManageAppService;
        public PermissionManageController(IPermissionManageAppService permissionManageAppService)
        {
            _permissionManageAppService = permissionManageAppService;
        }
        /// <summary>
        /// 獲取所有許可權
        /// </summary>
        /// <returns></returns>
        [HttpGet()]
        public Task<R<List<GetAllPermissionDto>>> GetPermission()
        {
            return _permissionManageAppService.GetPermission();
        }
        /// <summary>
        /// 獲取指定角色許可權
        /// </summary>
        /// <returns></returns>
        [HttpGet("{role}")]
        public Task<R<List<GetAllPermissionDto>>> GetRolePermission(string role)
        {
            return _permissionManageAppService.GetRolePermission(role);
        }
        /// <summary>
        /// 修改許可權
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPut]
        public async Task<R> UpdatePermission(UpdatePermissionDto dto)
        {
            return await _permissionManageAppService.UpdatePermission(dto);
        }
    }
}

通過讀取XML註釋檔案,自動生成Controller和Action的註釋名稱。
將許可權設定資訊寫入快取,提供給PermissionChecker使用。
許可權返回的結構如下:

namespace Wheel.Services.PermissionManage.Dtos
{
    public class GetAllPermissionDto
    {
        public string Group { get; set; } 
        public string Summary { get; set; } 

        public List<PermissionDto> Permissions { get; set; } = new ();
        
    }
    public class PermissionDto
    {
        public string Name { get; set; }
        public string Summary { get; set; }

        public bool IsGranted { get; set; }
    }
}

使用Postman測試API,可以看到,獲取了我們的許可權資訊列表,按照Controller分組,細分到每一個Action,summary是我們XML註釋的內容。

到這我們就完成了許可權管理的API。

輪子倉庫地址https://github.com/Wheel-Framework/Wheel
歡迎進群催更。