forked from xiongyuxing/tiku-backend.net
feat(security): add distributed authorization foundation
This commit is contained in:
@@ -11,7 +11,8 @@ namespace Tiku.Infrastructure.Backoffice;
|
||||
|
||||
internal sealed class BackofficeService(
|
||||
TikuDbContext dbContext,
|
||||
IOperationAuditService auditService) : IBackofficeService
|
||||
IOperationAuditService auditService,
|
||||
ICapabilityAccessEvaluator capabilityAccessEvaluator) : IBackofficeService
|
||||
{
|
||||
private static readonly BuiltinPermission[] BuiltinPermissions =
|
||||
[
|
||||
@@ -63,7 +64,11 @@ internal sealed class BackofficeService(
|
||||
}
|
||||
|
||||
await EnsureCatalogAsync(cancellationToken);
|
||||
var permissionCodes = access.TenantPermissions.Order(StringComparer.Ordinal).ToArray();
|
||||
var permissionCodes = await FilterTenantPermissionCodesAsync(
|
||||
access.TenantId.Value,
|
||||
access.TenantPermissions,
|
||||
CapabilityOperation.Read,
|
||||
cancellationToken);
|
||||
var menus = await LoadEffectiveMenusAsync(
|
||||
BackendPermissionArea.Tenant,
|
||||
permissionCodes,
|
||||
@@ -100,10 +105,18 @@ internal sealed class BackofficeService(
|
||||
.Where(item => item.Area == BackendPermissionArea.Tenant || item.Area == BackendPermissionArea.Both)
|
||||
.OrderBy(item => item.Module).ThenBy(item => item.SortOrder).ThenBy(item => item.Code)
|
||||
.ToArrayAsync(cancellationToken);
|
||||
var enabledPermissionCodes = await FilterTenantPermissionCodesAsync(
|
||||
tenantId,
|
||||
permissions.Select(item => item.Code),
|
||||
CapabilityOperation.Read,
|
||||
cancellationToken);
|
||||
permissions = permissions.Where(item => enabledPermissionCodes.Contains(item.Code, StringComparer.Ordinal)).ToArray();
|
||||
var menus = await dbContext.BackendMenus.AsNoTracking()
|
||||
.Where(item => item.IsActive && item.Area == BackendPermissionArea.Tenant)
|
||||
.OrderBy(item => item.SortOrder).ThenBy(item => item.Code)
|
||||
.ToArrayAsync(cancellationToken);
|
||||
menus = menus.Where(item => item.PermissionCode is null ||
|
||||
enabledPermissionCodes.Contains(item.PermissionCode, StringComparer.Ordinal)).ToArray();
|
||||
return new BackofficeBootstrap(
|
||||
permissions.Select(ToPermissionItem).ToArray(),
|
||||
menus.Select(ToMenuItem).ToArray(),
|
||||
@@ -350,6 +363,19 @@ internal sealed class BackofficeService(
|
||||
var normalizedPermissions = NormalizeCodes(permissionCodes);
|
||||
var normalizedMenus = NormalizeCodes(menuCodes);
|
||||
await ValidatePermissionCodesAsync(normalizedPermissions, BackendPermissionArea.Tenant, cancellationToken);
|
||||
foreach (var permissionCode in normalizedPermissions)
|
||||
{
|
||||
if (!await capabilityAccessEvaluator.IsAllowedAsync(
|
||||
tenantId,
|
||||
ResolveModuleCode(permissionCode),
|
||||
CapabilityOperation.Write,
|
||||
cancellationToken))
|
||||
{
|
||||
throw new BackofficeException(
|
||||
"One or more permissions belong to a module unavailable to this tenant.",
|
||||
"capability_not_available");
|
||||
}
|
||||
}
|
||||
await ValidateMenuCodesAsync(normalizedMenus, BackendPermissionArea.Tenant, cancellationToken);
|
||||
await dbContext.TenantBackendRolePermissions.Where(item => item.TenantId == tenantId && item.RoleId == roleId).ExecuteDeleteAsync(cancellationToken);
|
||||
await dbContext.TenantBackendRoleMenus.Where(item => item.TenantId == tenantId && item.RoleId == roleId).ExecuteDeleteAsync(cancellationToken);
|
||||
@@ -401,6 +427,33 @@ internal sealed class BackofficeService(
|
||||
return menus.Select(ToMenuItem).ToArray();
|
||||
}
|
||||
|
||||
private async Task<string[]> FilterTenantPermissionCodesAsync(
|
||||
Guid tenantId,
|
||||
IEnumerable<string> permissionCodes,
|
||||
CapabilityOperation operation,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var enabled = new List<string>();
|
||||
foreach (var permissionCode in permissionCodes.Distinct(StringComparer.Ordinal).Order(StringComparer.Ordinal))
|
||||
{
|
||||
if (await capabilityAccessEvaluator.IsAllowedAsync(
|
||||
tenantId,
|
||||
ResolveModuleCode(permissionCode),
|
||||
operation,
|
||||
cancellationToken))
|
||||
{
|
||||
enabled.Add(permissionCode);
|
||||
}
|
||||
}
|
||||
return enabled.ToArray();
|
||||
}
|
||||
|
||||
private static string ResolveModuleCode(string permissionCode)
|
||||
{
|
||||
var parts = permissionCode.Split(':', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
return parts.Length >= 2 ? parts[1].ToLowerInvariant() : permissionCode.ToLowerInvariant();
|
||||
}
|
||||
|
||||
private async Task ValidateMenuCodesAsync(string[] codes, BackendPermissionArea area, CancellationToken cancellationToken)
|
||||
{
|
||||
var count = await dbContext.BackendMenus.CountAsync(
|
||||
|
||||
Reference in New Issue
Block a user