diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuItem.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuItem.cs index 1c41727f7b..c1e4896513 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuItem.cs +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuItem.cs @@ -71,6 +71,9 @@ namespace Volo.Abp.UI.Navigation [NotNull] public ApplicationMenuItemList Items { get; } + [CanBeNull] + public string RequiredPermissionName { get; } + /// /// Can be used to store a custom object related to this menu item. Optional. /// @@ -95,7 +98,8 @@ namespace Volo.Abp.UI.Navigation object customData = null, string target = null, string elementId = null, - string cssClass = null) + string cssClass = null, + string requiredPermissionName = null) { Check.NotNullOrWhiteSpace(name, nameof(name)); Check.NotNullOrWhiteSpace(displayName, nameof(displayName)); @@ -109,6 +113,7 @@ namespace Volo.Abp.UI.Navigation Target = target; ElementId = elementId ?? GetDefaultElementId(); CssClass = cssClass; + RequiredPermissionName = requiredPermissionName; Items = new ApplicationMenuItemList(); } diff --git a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuManager.cs b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuManager.cs index 5c33866aa2..6fe6102622 100644 --- a/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuManager.cs +++ b/framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/MenuManager.cs @@ -2,7 +2,9 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using Volo.Abp.Authorization.Permissions; using Volo.Abp.DependencyInjection; namespace Volo.Abp.UI.Navigation @@ -13,7 +15,7 @@ namespace Volo.Abp.UI.Navigation protected IHybridServiceScopeFactory ServiceScopeFactory { get; } public MenuManager( - IOptions options, + IOptions options, IHybridServiceScopeFactory serviceScopeFactory) { ServiceScopeFactory = serviceScopeFactory; @@ -32,6 +34,8 @@ namespace Volo.Abp.UI.Navigation { await contributor.ConfigureMenuAsync(context); } + + await CheckPermissionsAsync(scope.ServiceProvider, menu); } NormalizeMenu(menu); @@ -39,6 +43,52 @@ namespace Volo.Abp.UI.Navigation return menu; } + protected virtual async Task CheckPermissionsAsync(IServiceProvider serviceProvider, IHasMenuItems menuWithItems) + { + var requiredPermissionMenus = new List(); + GetRequiredPermissionNameMenus(menuWithItems, requiredPermissionMenus); + + if (requiredPermissionMenus.Any()) + { + var permissionChecker = serviceProvider.GetRequiredService(); + var grantResult = await permissionChecker.IsGrantedAsync(requiredPermissionMenus.Select(x => x.RequiredPermissionName).ToArray()); + + var toBeDeletedMenus = new List(); + foreach (var menu in requiredPermissionMenus) + { + if (grantResult.Result[menu.RequiredPermissionName!] != PermissionGrantResult.Granted) + { + toBeDeletedMenus.Add(menu); + } + } + + RemoveMenus(menuWithItems, toBeDeletedMenus); + } + } + + protected virtual void GetRequiredPermissionNameMenus(IHasMenuItems menuWithItems, List output) + { + foreach (var menuItem in menuWithItems.Items) + { + if (!menuItem.RequiredPermissionName.IsNullOrWhiteSpace()) + { + output.Add(menuItem); + } + + GetRequiredPermissionNameMenus(menuItem, output); + } + } + + protected virtual void RemoveMenus(IHasMenuItems menuWithItems, List toBeDeleted) + { + menuWithItems.Items.RemoveAll(toBeDeleted.Contains); + + foreach (var menuItem in menuWithItems.Items) + { + RemoveMenus(menuItem, toBeDeleted); + } + } + protected virtual void NormalizeMenu(IHasMenuItems menuWithItems) { foreach (var menuItem in menuWithItems.Items) @@ -49,4 +99,4 @@ namespace Volo.Abp.UI.Navigation menuWithItems.Items.Normalize(); } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.UI.Navigation.Tests/Volo.Abp.UI.Navigation.Tests.csproj b/framework/test/Volo.Abp.UI.Navigation.Tests/Volo.Abp.UI.Navigation.Tests.csproj index ecfd73c615..a59bb11e0f 100644 --- a/framework/test/Volo.Abp.UI.Navigation.Tests/Volo.Abp.UI.Navigation.Tests.csproj +++ b/framework/test/Volo.Abp.UI.Navigation.Tests/Volo.Abp.UI.Navigation.Tests.csproj @@ -9,6 +9,8 @@ + + diff --git a/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/AbpUiNavigationTestModule.cs b/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/AbpUiNavigationTestModule.cs new file mode 100644 index 0000000000..35b368cc1b --- /dev/null +++ b/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/AbpUiNavigationTestModule.cs @@ -0,0 +1,21 @@ +using Volo.Abp.Authorization; +using Volo.Abp.Autofac; +using Volo.Abp.Modularity; + +namespace Volo.Abp.UI.Navigation +{ + [DependsOn(typeof(AbpUiNavigationModule))] + [DependsOn(typeof(AbpAuthorizationModule))] + [DependsOn(typeof(AbpAutofacModule))] + public class AbpUiNavigationTestModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.MenuContributors.Add(new MenuManager_Tests.TestMenuContributor1()); + options.MenuContributors.Add(new MenuManager_Tests.TestMenuContributor2()); + }); + } + } +} diff --git a/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/FakePermissionStore.cs b/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/FakePermissionStore.cs new file mode 100644 index 0000000000..ac074220f5 --- /dev/null +++ b/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/FakePermissionStore.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.UI.Navigation +{ + public class FakePermissionStore : IPermissionStore, ITransientDependency + { + public Task IsGrantedAsync(string name, string providerName, string providerKey) + { + var result = (name.Contains("Administration") || name.Contains("Dashboard")) && !name.Contains("SubMenu1"); + return Task.FromResult(result); + } + + public Task IsGrantedAsync(string[] names, string providerName, string providerKey) + { + var result = new MultiplePermissionGrantResult(); + foreach (var name in names) + { + result.Result.Add(name, (name.Contains("Administration") || name.Contains("Dashboard")) && !name.Contains("SubMenu1") + ? PermissionGrantResult.Granted + : PermissionGrantResult.Prohibited); + } + + return Task.FromResult(result); + } + } +} diff --git a/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/MenuManager_Tests.cs b/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/MenuManager_Tests.cs index 96db1622b5..c72082945b 100644 --- a/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/MenuManager_Tests.cs +++ b/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/MenuManager_Tests.cs @@ -1,13 +1,17 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using NSubstitute; using Shouldly; -using Volo.Abp.Modularity; +using Volo.Abp.Security.Claims; using Volo.Abp.Testing; using Xunit; namespace Volo.Abp.UI.Navigation { - public class MenuManager_Tests : AbpIntegratedTest + public class MenuManager_Tests : AbpIntegratedTest { private readonly IMenuManager _menuManager; @@ -16,6 +20,24 @@ namespace Volo.Abp.UI.Navigation _menuManager = ServiceProvider.GetRequiredService(); } + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + + protected override void AfterAddApplication(IServiceCollection services) + { + var claims = new List() { + new Claim(AbpClaimTypes.UserId, "1fcf46b2-28c3-48d0-8bac-fa53268a2775"), + }; + + var identity = new ClaimsIdentity(claims); + var claimsPrincipal = new ClaimsPrincipal(identity); + var principalAccessor = Substitute.For(); + principalAccessor.Principal.Returns(ci => claimsPrincipal); + Thread.CurrentPrincipal = claimsPrincipal; + } + [Fact] public async Task Should_Get_Menu() { @@ -29,22 +51,8 @@ namespace Volo.Abp.UI.Navigation mainMenu.Items[1].Items[0].Name.ShouldBe("Administration.UserManagement"); mainMenu.Items[1].Items[1].Name.ShouldBe("Administration.RoleManagement"); mainMenu.Items[1].Items[2].Name.ShouldBe("Administration.DashboardSettings"); - mainMenu.Items[1].Items[3].Name.ShouldBe("Administration.SubMenu1"); - mainMenu.Items[1].Items[3].Items[0].Name.ShouldBe("Administration.SubMenu1.1"); - mainMenu.Items[1].Items[3].Items[1].Name.ShouldBe("Administration.SubMenu1.2"); - } - - [DependsOn(typeof(AbpUiNavigationModule))] - public class TestModule : AbpModule - { - public override void ConfigureServices(ServiceConfigurationContext context) - { - Configure(options => - { - options.MenuContributors.Add(new TestMenuContributor1()); - options.MenuContributors.Add(new TestMenuContributor2()); - }); - } + mainMenu.Items[1].Items[3].Name.ShouldBe("Administration.SubMenu1"); //No need permission. + // Administration.SubMenu1.1 and Administration.SubMenu1.2 are removed because of don't have permissions. } /* Adds menu items: @@ -65,8 +73,8 @@ namespace Volo.Abp.UI.Navigation var administration = context.Menu.GetAdministration(); - administration.AddItem(new ApplicationMenuItem("Administration.UserManagement", "User Management", url: "/admin/users")); - administration.AddItem(new ApplicationMenuItem("Administration.RoleManagement", "Role Management", url: "/admin/roles")); + administration.AddItem(new ApplicationMenuItem("Administration.UserManagement", "User Management", url: "/admin/users", requiredPermissionName: "Administration.UserManagement")); + administration.AddItem(new ApplicationMenuItem("Administration.RoleManagement", "Role Management", url: "/admin/roles", requiredPermissionName: "Administration.RoleManagement")); return Task.CompletedTask; } @@ -86,16 +94,16 @@ namespace Volo.Abp.UI.Navigation return Task.CompletedTask; } - context.Menu.Items.Insert(0, new ApplicationMenuItem("Dashboard", "Dashboard", url: "/dashboard")); + context.Menu.Items.Insert(0, new ApplicationMenuItem("Dashboard", "Dashboard", url: "/dashboard", requiredPermissionName: "Dashboard")); var administration = context.Menu.GetAdministration(); - administration.AddItem(new ApplicationMenuItem("Administration.DashboardSettings", "Dashboard Settings", url: "/admin/settings/dashboard")); + administration.AddItem(new ApplicationMenuItem("Administration.DashboardSettings", "Dashboard Settings", url: "/admin/settings/dashboard", requiredPermissionName: "Administration.DashboardSettings")); administration.AddItem( - new ApplicationMenuItem("Administration.SubMenu1", "Sub menu 1") - .AddItem(new ApplicationMenuItem("Administration.SubMenu1.1", "Sub menu 1.1", url: "/submenu1/submenu1_1")) - .AddItem(new ApplicationMenuItem("Administration.SubMenu1.2", "Sub menu 1.2", url: "/submenu1/submenu1_2")) + new ApplicationMenuItem("Administration.SubMenu1", "Sub menu 1", url: "/submenu1") + .AddItem(new ApplicationMenuItem("Administration.SubMenu1.1", "Sub menu 1.1", url: "/submenu1/submenu1_1", requiredPermissionName: "Administration.SubMenu1.1")) + .AddItem(new ApplicationMenuItem("Administration.SubMenu1.2", "Sub menu 1.2", url: "/submenu1/submenu1_2", requiredPermissionName: "Administration.SubMenu1.2")) ); return Task.CompletedTask; diff --git a/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/TestPermissionDefinitionProvider.cs b/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/TestPermissionDefinitionProvider.cs new file mode 100644 index 0000000000..c9afe5a1f0 --- /dev/null +++ b/framework/test/Volo.Abp.UI.Navigation.Tests/Volo/Abp/Ui/Navigation/TestPermissionDefinitionProvider.cs @@ -0,0 +1,24 @@ +using Volo.Abp.Authorization.Permissions; + +namespace Volo.Abp.UI.Navigation +{ + public class TestPermissionDefinitionProvider : PermissionDefinitionProvider + { + public override void Define(IPermissionDefinitionContext context) + { + var group = context.AddGroup("TestGroup"); + + group.AddPermission("Dashboard"); + + group.AddPermission("Administration"); + group.AddPermission("Administration.UserManagement"); + group.AddPermission("Administration.RoleManagement"); + + group.AddPermission("Administration.DashboardSettings"); + + group.AddPermission("Administration.SubMenu1"); + group.AddPermission("Administration.SubMenu1.1"); + group.AddPermission("Administration.SubMenu1.2"); + } + } +}