From d79292baef93dfabeb4a5f37979d7a4103d31107 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 2 Sep 2026 16:35:21 +0800 Subject: [PATCH] Check the permission state of each tree level in its own batch - Keep the posted permission names untrimmed so they still match their definition --- .../PermissionAppService.cs | 36 ++++++++++--------- .../PermissionManagementModal.cshtml.cs | 4 +-- .../PermissionAppService_Tests.cs | 24 +++++++++++++ .../AbpPermissionManagementTestModule.cs | 6 ---- .../AbpPermissionManagementTestBaseModule.cs | 6 ++++ .../TestGlobalPermissionStateChecker.cs | 16 +++++++-- 6 files changed, 64 insertions(+), 28 deletions(-) rename modules/permission-management/test/{Volo.Abp.PermissionManagement.Domain.Tests => Volo.Abp.PermissionManagement.TestBase}/Volo/Abp/PermissionManagement/TestGlobalPermissionStateChecker.cs (69%) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs index b429908640..679017d1e1 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Application/Volo/Abp/PermissionManagement/PermissionAppService.cs @@ -78,27 +78,29 @@ public class PermissionAppService : ApplicationService, IPermissionAppService .Where(x => !x.Providers.Any() || x.Providers.Contains(providerName)) .Where(x => x.MultiTenancySide.HasFlag(multiTenancySide)); - var stateCheckPermissions = permissions.Distinct().ToArray(); - var stateCheckResult = stateCheckPermissions.Any() - ? await SimpleStateCheckerManager.IsEnabledAsync(stateCheckPermissions) - : new SimpleStateCheckerResult(); - - var neededCheckPermissions = new List(); - var neededCheckPermissionSet = new HashSet(); - foreach (var permission in stateCheckPermissions) + var candidatePermissions = permissions.Distinct().ToArray(); + var childrenByParent = candidatePermissions + .Where(x => x.Parent != null) + .GroupBy(x => x.Parent!) + .ToDictionary(x => x.Key, x => x.ToArray()); + + /* The state checkers of a permission only run when its parent is enabled, + so each tree level is checked in its own batch. */ + var enabledPermissions = new HashSet(); + var currentLevel = candidatePermissions.Where(x => x.Parent == null).ToArray(); + while (currentLevel.Any()) { - if (permission.Parent != null && !neededCheckPermissionSet.Contains(permission.Parent)) - { - continue; - } + var levelResult = await SimpleStateCheckerManager.IsEnabledAsync(currentLevel); + var enabledLevelPermissions = currentLevel.Where(x => levelResult[x]).ToArray(); + enabledPermissions.UnionWith(enabledLevelPermissions); - if (stateCheckResult[permission]) - { - neededCheckPermissions.Add(permission); - neededCheckPermissionSet.Add(permission); - } + currentLevel = enabledLevelPermissions + .SelectMany(x => childrenByParent.GetOrDefault(x) ?? Array.Empty()) + .ToArray(); } + var neededCheckPermissions = candidatePermissions.Where(enabledPermissions.Contains).ToList(); + if (!neededCheckPermissions.Any()) { continue; diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/PermissionManagementModal.cshtml.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/PermissionManagementModal.cshtml.cs index 9d3d317632..ee1a3846d6 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/PermissionManagementModal.cshtml.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Web/Pages/AbpPermissionManagement/PermissionManagementModal.cshtml.cs @@ -158,9 +158,7 @@ public class PermissionManagementModal : AbpPageModel { return names.IsNullOrWhiteSpace() ? Array.Empty() - : names.Split( - new[] { '\r', '\n' }, - StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + : names.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); } public class PermissionGroupViewModel diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Application.Tests/Volo/Abp/PermissionManagement/PermissionAppService_Tests.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Application.Tests/Volo/Abp/PermissionManagement/PermissionAppService_Tests.cs index 1fddf07181..aac6e05647 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.Application.Tests/Volo/Abp/PermissionManagement/PermissionAppService_Tests.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Application.Tests/Volo/Abp/PermissionManagement/PermissionAppService_Tests.cs @@ -17,6 +17,7 @@ public class PermissionAppService_Tests : AbpPermissionManagementApplicationTest private readonly IPermissionGrantRepository _permissionGrantRepository; private readonly ICurrentPrincipalAccessor _currentPrincipalAccessor; private readonly FakePermissionChecker _fakePermissionChecker; + private readonly TestGlobalPermissionStateCheckerCounter _stateCheckerCounter; public PermissionAppService_Tests() { @@ -24,6 +25,29 @@ public class PermissionAppService_Tests : AbpPermissionManagementApplicationTest _permissionGrantRepository = GetRequiredService(); _currentPrincipalAccessor = GetRequiredService(); _fakePermissionChecker = GetRequiredService(); + _stateCheckerCounter = GetRequiredService(); + } + + [Fact] + public async Task Get_Should_Not_Check_The_State_Of_A_Permission_Whose_Parent_Is_Not_Enabled() + { + _stateCheckerCounter.Reset(); + + await _permissionAppService.GetAsync(UserPermissionValueProvider.ProviderName, + PermissionTestDataBuilder.User1Id.ToString()); + + _stateCheckerCounter.CheckedPermissionNames.ShouldContain("MyPermission5"); + _stateCheckerCounter.CheckedPermissionNames.ShouldNotContain("MyPermission5.ChildPermission1"); + + using (_currentPrincipalAccessor.Change(new Claim(AbpClaimTypes.Role, "super-admin"))) + { + _stateCheckerCounter.Reset(); + + await _permissionAppService.GetAsync(UserPermissionValueProvider.ProviderName, + PermissionTestDataBuilder.User1Id.ToString()); + + _stateCheckerCounter.CheckedPermissionNames.ShouldContain("MyPermission5.ChildPermission1"); + } } [Fact] diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/AbpPermissionManagementTestModule.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/AbpPermissionManagementTestModule.cs index d851aac1fd..122aed491d 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/AbpPermissionManagementTestModule.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/AbpPermissionManagementTestModule.cs @@ -5,9 +5,7 @@ using Volo.Abp.EntityFrameworkCore; using Volo.Abp.Features; using Volo.Abp.GlobalFeatures; using Volo.Abp.Modularity; -using Volo.Abp.Authorization.Permissions; using Volo.Abp.PermissionManagement.EntityFrameworkCore; -using Volo.Abp.SimpleStateChecking; using Volo.Abp.Uow; namespace Volo.Abp.PermissionManagement; @@ -24,10 +22,6 @@ public class AbpPermissionManagementTestModule : AbpModule { context.Services.AddEntityFrameworkInMemoryDatabase(); - Configure>(options => - { - options.GlobalStateCheckers.Add(); - }); var databaseName = Guid.NewGuid().ToString(); diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/AbpPermissionManagementTestBaseModule.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/AbpPermissionManagementTestBaseModule.cs index 0372fa41d6..6a7d208971 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/AbpPermissionManagementTestBaseModule.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/AbpPermissionManagementTestBaseModule.cs @@ -4,6 +4,7 @@ using Volo.Abp.Authorization.Permissions; using Volo.Abp.Autofac; using Volo.Abp.DistributedLocking; using Volo.Abp.Modularity; +using Volo.Abp.SimpleStateChecking; using Volo.Abp.Threading; namespace Volo.Abp.PermissionManagement; @@ -19,6 +20,11 @@ public class AbpPermissionManagementTestBaseModule : AbpModule { context.Services.Replace(ServiceDescriptor.Singleton()); + context.Services.Configure>(options => + { + options.GlobalStateCheckers.Add(); + }); + context.Services.Configure(options => { options.ManagementProviders.Add(); diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/TestGlobalPermissionStateChecker.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestGlobalPermissionStateChecker.cs similarity index 69% rename from modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/TestGlobalPermissionStateChecker.cs rename to modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestGlobalPermissionStateChecker.cs index 18c867f7a6..9f0a8ecff3 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.Domain.Tests/Volo/Abp/PermissionManagement/TestGlobalPermissionStateChecker.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.TestBase/Volo/Abp/PermissionManagement/TestGlobalPermissionStateChecker.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Authorization.Permissions; @@ -13,10 +14,13 @@ public class TestGlobalPermissionStateCheckerCounter : ISingletonDependency public int SingleCheckCount { get; set; } + public HashSet CheckedPermissionNames { get; } = new HashSet(); + public void Reset() { BatchCheckCount = 0; SingleCheckCount = 0; + CheckedPermissionNames.Clear(); } } @@ -24,13 +28,21 @@ public class TestGlobalPermissionStateChecker : ISimpleBatchStateChecker IsEnabledAsync(SimpleStateCheckerContext context) { - GetCounter(context.ServiceProvider).SingleCheckCount++; + var counter = GetCounter(context.ServiceProvider); + counter.SingleCheckCount++; + counter.CheckedPermissionNames.Add(context.State.Name); return Task.FromResult(true); } public Task> IsEnabledAsync(SimpleBatchStateCheckerContext context) { - GetCounter(context.ServiceProvider).BatchCheckCount++; + var counter = GetCounter(context.ServiceProvider); + counter.BatchCheckCount++; + foreach (var state in context.States) + { + counter.CheckedPermissionNames.Add(state.Name); + } + return Task.FromResult(new SimpleStateCheckerResult(context.States)); }