From 549b76158c3a6ddf89cac941f4532c162464b00d Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 27 Sep 2021 10:29:29 +0800 Subject: [PATCH] Add `PermissionsRequirement` and `PermissionsRequirementHandler` to check multiple permissions. Resolve #10151 --- .../Authorization/PermissionsRequirement.cs | 25 +++++++++++++++ .../PermissionsRequirementHandler.cs | 31 +++++++++++++++++++ .../Authorization/AbpAuthorizationModule.cs | 1 + .../Mvc/AbpAspNetCoreMvcTestModule.cs | 11 +++++++ .../Mvc/Authorization/AuthTestController.cs | 14 +++++++++ .../Authorization/AuthTestController_Tests.cs | 27 ++++++++++++++-- .../TestPermissionDefinitionProvider.cs | 1 + .../Abp/AspNetCore/AbpAspNetCoreTestBase.cs | 7 ++++- 8 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/PermissionsRequirement.cs create mode 100644 framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/PermissionsRequirementHandler.cs diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/PermissionsRequirement.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/PermissionsRequirement.cs new file mode 100644 index 0000000000..0f1540378e --- /dev/null +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/PermissionsRequirement.cs @@ -0,0 +1,25 @@ +using JetBrains.Annotations; +using Microsoft.AspNetCore.Authorization; + +namespace Volo.Abp.Authorization +{ + public class PermissionsRequirement : IAuthorizationRequirement + { + public string[] PermissionNames { get; } + + public bool RequiresAll { get; } + + public PermissionsRequirement([NotNull]string[] permissionNames, bool requiresAll) + { + Check.NotNull(permissionNames, nameof(permissionNames)); + + PermissionNames = permissionNames; + RequiresAll = requiresAll; + } + + public override string ToString() + { + return $"PermissionsRequirement: {string.Join(", ", PermissionNames)}"; + } + } +} diff --git a/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/PermissionsRequirementHandler.cs b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/PermissionsRequirementHandler.cs new file mode 100644 index 0000000000..d3b2678da3 --- /dev/null +++ b/framework/src/Volo.Abp.Authorization.Abstractions/Volo/Abp/Authorization/PermissionsRequirementHandler.cs @@ -0,0 +1,31 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Volo.Abp.Authorization.Permissions; + +namespace Volo.Abp.Authorization +{ + public class PermissionsRequirementHandler : AuthorizationHandler + { + private readonly IPermissionChecker _permissionChecker; + + public PermissionsRequirementHandler(IPermissionChecker permissionChecker) + { + _permissionChecker = permissionChecker; + } + + protected override async Task HandleRequirementAsync( + AuthorizationHandlerContext context, + PermissionsRequirement requirement) + { + var multiplePermissionGrantResult = await _permissionChecker.IsGrantedAsync(context.User, requirement.PermissionNames); + + if (requirement.RequiresAll ? + multiplePermissionGrantResult.AllGranted : + multiplePermissionGrantResult.Result.Any(x => x.Value == PermissionGrantResult.Granted)) + { + context.Succeed(requirement); + } + } + } +} diff --git a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs index a821401c4b..e9f6ea6d20 100644 --- a/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs +++ b/framework/src/Volo.Abp.Authorization/Volo/Abp/Authorization/AbpAuthorizationModule.cs @@ -31,6 +31,7 @@ namespace Volo.Abp.Authorization context.Services.AddAuthorizationCore(); context.Services.AddSingleton(); + context.Services.AddSingleton(); context.Services.TryAddTransient(); diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs index fc84b9c496..0b84b00f7d 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs @@ -11,6 +11,7 @@ using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.Localization.Resource; using Volo.Abp.AspNetCore.Security.Claims; using Volo.Abp.AspNetCore.TestBase; +using Volo.Abp.Authorization; using Volo.Abp.Autofac; using Volo.Abp.GlobalFeatures; using Volo.Abp.Localization; @@ -65,6 +66,16 @@ namespace Volo.Abp.AspNetCore.Mvc { policy.RequireClaim("MyCustomClaimType", "42"); }); + + options.AddPolicy("TestPermission1_And_TestPermission2", policy => + { + policy.Requirements.Add(new PermissionsRequirement(new []{"TestPermission1", "TestPermission2"}, requiresAll: true)); + }); + + options.AddPolicy("TestPermission1_Or_TestPermission2", policy => + { + policy.Requirements.Add(new PermissionsRequirement(new []{"TestPermission1", "TestPermission2"}, requiresAll: false)); + }); }); Configure(options => diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestController.cs index a66fcdc644..a0cd07a728 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestController.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestController.cs @@ -38,5 +38,19 @@ namespace Volo.Abp.AspNetCore.Mvc.Authorization CurrentUser.Id.ShouldBe(FakeUserId); return Content("OK"); } + + [Authorize("TestPermission1_And_TestPermission2")] + public ActionResult Custom_And_PolicyTest() + { + CurrentUser.Id.ShouldBe(FakeUserId); + return Content("OK"); + } + + [Authorize("TestPermission1_Or_TestPermission2")] + public ActionResult Custom_Or_PolicyTest() + { + CurrentUser.Id.ShouldBe(FakeUserId); + return Content("OK"); + } } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestController_Tests.cs index 3129ccf55d..8790620e04 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/AuthTestController_Tests.cs @@ -1,5 +1,4 @@ -using System; -using System.Net; +using System.Net; using System.Security.Claims; using System.Threading.Tasks; using Shouldly; @@ -72,5 +71,29 @@ namespace Volo.Abp.AspNetCore.Mvc.Authorization var result = await GetResponseAsStringAsync("/AuthTest/PermissionTest"); result.ShouldBe("OK"); } + + [Fact] + public async Task Custom_And_Policy_Should_Not_Work_When_Permissions_Not_Granted() + { + _fakeRequiredService.Claims.AddRange(new[] + { + new Claim(AbpClaimTypes.UserId, AuthTestController.FakeUserId.ToString()) + }); + + var response = await GetResponseAsync("/AuthTest/Custom_And_PolicyTest", HttpStatusCode.Forbidden, xmlHttpRequest: true); + response.StatusCode.ShouldBe(HttpStatusCode.Forbidden); + } + + [Fact] + public async Task Custom_Or_Policy_Should_Work_When_Permissions_Are_Granted() + { + _fakeRequiredService.Claims.AddRange(new[] + { + new Claim(AbpClaimTypes.UserId, AuthTestController.FakeUserId.ToString()) + }); + + var result = await GetResponseAsStringAsync("/AuthTest/Custom_Or_PolicyTest"); + result.ShouldBe("OK"); + } } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/TestPermissionDefinitionProvider.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/TestPermissionDefinitionProvider.cs index ca2f627f0c..8472013268 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/TestPermissionDefinitionProvider.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Authorization/TestPermissionDefinitionProvider.cs @@ -9,6 +9,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Authorization var testGroup = context.AddGroup("TestGroup"); testGroup.AddPermission("TestPermission1"); + testGroup.AddPermission("TestPermission2"); } } } diff --git a/framework/test/Volo.Abp.AspNetCore.Tests/Volo/Abp/AspNetCore/AbpAspNetCoreTestBase.cs b/framework/test/Volo.Abp.AspNetCore.Tests/Volo/Abp/AspNetCore/AbpAspNetCoreTestBase.cs index 3c5571f115..cee7cd8b2d 100644 --- a/framework/test/Volo.Abp.AspNetCore.Tests/Volo/Abp/AspNetCore/AbpAspNetCoreTestBase.cs +++ b/framework/test/Volo.Abp.AspNetCore.Tests/Volo/Abp/AspNetCore/AbpAspNetCoreTestBase.cs @@ -3,6 +3,7 @@ using System.Net; using System.Net.Http; using System.Text.Json; using System.Threading.Tasks; +using Microsoft.Net.Http.Headers; using Shouldly; using Volo.Abp.AspNetCore.TestBase; @@ -30,11 +31,15 @@ namespace Volo.Abp.AspNetCore } } - protected virtual async Task GetResponseAsync(string url, HttpStatusCode expectedStatusCode = HttpStatusCode.OK) + protected virtual async Task GetResponseAsync(string url, HttpStatusCode expectedStatusCode = HttpStatusCode.OK, bool xmlHttpRequest = false) { using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, url)) { requestMessage.Headers.Add("Accept-Language", CultureInfo.CurrentUICulture.Name); + if (xmlHttpRequest) + { + requestMessage.Headers.Add(HeaderNames.XRequestedWith, "XMLHttpRequest"); + } var response = await Client.SendAsync(requestMessage); response.StatusCode.ShouldBe(expectedStatusCode); return response;