From fa492e027d4b89d3e3423be706feb8aee55af7f6 Mon Sep 17 00:00:00 2001 From: maliming Date: Sat, 14 Feb 2026 13:38:28 +0800 Subject: [PATCH] Add authorization support with policies and roles in API description models --- .../AspNetCoreApiDescriptionModelProvider.cs | 17 ++--- .../Modeling/ActionApiDescriptionModel.cs | 6 +- .../AuthorizeDataApiDescriptionModel.cs | 8 +++ .../AbpApiDefinitionController_Tests.cs | 66 ++++++++++++++++++- .../TestApp/Application/IPeopleAppService.cs | 4 ++ .../TestApp/Application/PeopleAppService.cs | 13 ++++ 6 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/AuthorizeDataApiDescriptionModel.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs index ce0205eb6b..2df5dea048 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs @@ -121,19 +121,20 @@ public class AspNetCoreApiDescriptionModelProvider : IApiDescriptionModelProvide Logger.LogDebug($"ActionApiDescriptionModel.Create: {controllerModel.ControllerName}.{uniqueMethodName}"); bool? allowAnonymous = null; - string? requiredPolicy = null; + var authorizeModels = new List(); if (apiDescription.ActionDescriptor.EndpointMetadata.Any(x => x is IAllowAnonymous)) { allowAnonymous = true; } - else + else if (apiDescription.ActionDescriptor.EndpointMetadata.Any(x => x is IAuthorizeData)) { - var authorizeData = apiDescription.ActionDescriptor.EndpointMetadata.FirstOrDefault(x => x is IAuthorizeData); - if (authorizeData != null) + allowAnonymous = false; + var authorizeDatas = apiDescription.ActionDescriptor.EndpointMetadata.Where(x => x is IAuthorizeData).Cast().ToList(); + authorizeModels.AddRange(authorizeDatas.Select(authorizeData => new AuthorizeDataApiDescriptionModel { - allowAnonymous = false; - requiredPolicy = (authorizeData as IAuthorizeData)?.Policy; - } + Policy = authorizeData.Policy, + Roles = authorizeData.Roles + })); } var implementFrom = controllerType.FullName; @@ -153,7 +154,7 @@ public class AspNetCoreApiDescriptionModelProvider : IApiDescriptionModelProvide apiDescription.HttpMethod, GetSupportedVersions(controllerType, method, setting), allowAnonymous, - requiredPolicy, + authorizeModels, implementFrom ) ); diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ActionApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ActionApiDescriptionModel.cs index 446653b81a..83bacddd8c 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ActionApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ActionApiDescriptionModel.cs @@ -28,7 +28,7 @@ public class ActionApiDescriptionModel public bool? AllowAnonymous { get; set; } - public string? RequiredPolicy { get; set; } + public IList AuthorizeDatas { get; set; } = default!; public string? ImplementFrom { get; set; } @@ -37,7 +37,7 @@ public class ActionApiDescriptionModel } - public static ActionApiDescriptionModel Create([NotNull] string uniqueName, [NotNull] MethodInfo method, [NotNull] string url, string? httpMethod, [NotNull] IList supportedVersions, bool? allowAnonymous = null, string? requiredPolicy = null, string? implementFrom = null) + public static ActionApiDescriptionModel Create([NotNull] string uniqueName, [NotNull] MethodInfo method, [NotNull] string url, string? httpMethod, [NotNull] IList supportedVersions, bool? allowAnonymous = null, IList? authorizeDatas = null, string? implementFrom = null) { Check.NotNull(uniqueName, nameof(uniqueName)); Check.NotNull(method, nameof(method)); @@ -58,7 +58,7 @@ public class ActionApiDescriptionModel .ToList(), SupportedVersions = supportedVersions, AllowAnonymous = allowAnonymous, - RequiredPolicy = requiredPolicy, + AuthorizeDatas = authorizeDatas ?? new List(), ImplementFrom = implementFrom }; } diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/AuthorizeDataApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/AuthorizeDataApiDescriptionModel.cs new file mode 100644 index 0000000000..d218b5508d --- /dev/null +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/AuthorizeDataApiDescriptionModel.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.Http.Modeling; + +public class AuthorizeDataApiDescriptionModel +{ + public string? Policy { get; set; } + + public string? Roles { get; set; } +} diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController_Tests.cs index 4ddd057889..5404555ffb 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController_Tests.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Shouldly; using Volo.Abp.Http.Modeling; @@ -23,4 +24,67 @@ public class AbpApiDefinitionController_Tests : AspNetCoreMvcTestBase model.ShouldNotBeNull(); model.Types.IsNullOrEmpty().ShouldBeFalse(); } + + [Fact] + public async Task Should_Have_Null_AllowAnonymous_For_Actions_Without_Authorization() + { + var model = await GetResponseAsObjectAsync("/api/abp/api-definition"); + + var peopleController = GetPeopleController(model); + var action = GetAction(peopleController, "GetPhones"); + + action.AllowAnonymous.ShouldBeNull(); + action.AuthorizeDatas.ShouldBeEmpty(); + } + + [Fact] + public async Task Should_Set_AllowAnonymous_True_For_AllowAnonymous_Actions() + { + var model = await GetResponseAsObjectAsync("/api/abp/api-definition"); + + var peopleController = GetPeopleController(model); + var action = GetAction(peopleController, "GetWithAllowAnonymous"); + + action.AllowAnonymous.ShouldBe(true); + action.AuthorizeDatas.ShouldBeEmpty(); + } + + [Fact] + public async Task Should_Set_AllowAnonymous_False_And_AuthorizeDatas_For_Authorize_Actions() + { + var model = await GetResponseAsObjectAsync("/api/abp/api-definition"); + + var peopleController = GetPeopleController(model); + var action = GetAction(peopleController, "GetWithAuthorized"); + + action.AllowAnonymous.ShouldBe(false); + action.AuthorizeDatas.ShouldNotBeEmpty(); + } + + [Fact] + public async Task Should_Contain_Policy_And_Roles_In_AuthorizeDatas() + { + var model = await GetResponseAsObjectAsync("/api/abp/api-definition"); + + var peopleController = GetPeopleController(model); + var action = GetAction(peopleController, "GetWithAuthorizePolicy"); + + action.AllowAnonymous.ShouldBe(false); + action.AuthorizeDatas.Count.ShouldBe(2); + action.AuthorizeDatas.ShouldContain(a => a.Policy == "TestPolicy" && a.Roles == "Admin"); + action.AuthorizeDatas.ShouldContain(a => a.Policy == "TestPolicy2" && a.Roles == "Manager"); + } + + private static ControllerApiDescriptionModel GetPeopleController(ApplicationApiDescriptionModel model) + { + return model.Modules.Values + .SelectMany(m => m.Controllers.Values) + .First(c => c.ControllerName == "People"); + } + + private static ActionApiDescriptionModel GetAction(ControllerApiDescriptionModel controller, string actionName) + { + return controller.Actions.Values + .First(a => a.Name == actionName + "Async" || a.Name == actionName); + } } diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs index 7aea19c141..a39216add5 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/IPeopleAppService.cs @@ -20,6 +20,10 @@ public interface IPeopleAppService : ICrudAppService Task GetWithAuthorized(); + Task GetWithAllowAnonymous(); + + Task GetWithAuthorizePolicy(); + Task GetWithComplexType(GetWithComplexTypeInput input); Task DownloadAsync(); diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs index 6981eb1e57..a3920ec8ca 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs @@ -67,6 +67,19 @@ public class PeopleAppService : CrudAppService, IPeople return Task.CompletedTask; } + [AllowAnonymous] + public Task GetWithAllowAnonymous() + { + return Task.CompletedTask; + } + + [Authorize("TestPolicy", Roles = "Admin")] + [Authorize("TestPolicy2", Roles = "Manager")] + public Task GetWithAuthorizePolicy() + { + return Task.CompletedTask; + } + public Task GetWithComplexType(GetWithComplexTypeInput input) { return Task.FromResult(input);