From 0262da9c0e6860b0eb0cc2c44916d2837e63296e Mon Sep 17 00:00:00 2001 From: cKey <35512826+colinin@users.noreply.github.com> Date: Mon, 29 Nov 2021 20:46:10 +0800 Subject: [PATCH] =?UTF-8?q?feat(open-api):=20=E5=A2=9E=E5=8A=A0=E5=AF=B9?= =?UTF-8?q?=E4=BA=8E=E5=BC=80=E6=94=BE=E5=B9=B3=E5=8F=B0=E7=AD=BE=E5=90=8D?= =?UTF-8?q?=E4=BF=9D=E6=8A=A4=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- aspnet-core/LINGYUN.MicroService.Common.sln | 33 ++- aspnet-core/LINGYUN.MicroService.Platform.sln | 18 +- .../LINGYUN.Abp.OpenApi.Authorization.csproj | 19 ++ .../AbpOpenApiAuthorizationModule.cs | 16 ++ .../IOpenApiAuthorizationService.cs | 10 + .../OpenApiAuthorizationMiddleware.cs | 24 ++ .../OpenApiAuthorizationService.cs | 234 ++++++++++++++++++ ...thorizationApplicationBuilderExtensions.cs | 12 + .../LINGYUN.Abp.OpenApi.csproj | 25 ++ .../LINGYUN/Abp/OpenApi/AbpOpenApiConsts.cs | 22 ++ .../LINGYUN/Abp/OpenApi/AbpOpenApiModule.cs | 40 +++ .../LINGYUN/Abp/OpenApi/AbpOpenApiOptions.cs | 34 +++ .../LINGYUN/Abp/OpenApi/AppDescriptor.cs | 42 ++++ .../AbpDefaultAppKeyStoreOptions.cs | 12 + .../ConfigurationStore/DefaultAppKeyStore.cs | 28 +++ .../LINGYUN/Abp/OpenApi/IAppKeyStore.cs | 9 + .../OpenApi/Localization/OpenApiResource.cs | 9 + .../OpenApi/Localization/Resources/en.json | 11 + .../Localization/Resources/zh-Hans.json | 11 + .../ClientProxyServiceCollectionExtensions.cs | 19 ++ .../open-api/OpenApi.Sdk/OpenApi.Sdk.csproj | 13 + .../OpenApi.Sdk/OpenApi/ApiResponse.cs | 25 ++ .../OpenApi.Sdk/OpenApi/ApiResponse`T.cs | 45 ++++ .../OpenApi.Sdk/OpenApi/ClientProxy.cs | 159 ++++++++++++ .../OpenApi.Sdk/OpenApi/IClientProxy.cs | 22 ++ .../OpenApi/IClientProxyExtensions.cs | 19 ++ .../OpenApi.Sdk/System/StringMd5Extensions.cs | 25 ++ .../LINGYUN.Abp.OpenApi.Tests.csproj | 38 +++ .../LINGYUN/Abp/OpenApi/AbpOpenApiTestBase.cs | 33 +++ .../Abp/OpenApi/AbpOpenApiTestModule.cs | 114 +++++++++ .../LINGYUN/Abp/OpenApi/FakeAppKeyStore.cs | 28 +++ .../Abp/OpenApi/FakeInvokeController.cs | 16 ++ .../Abp/OpenApi/FakeInvokeController_Tests.cs | 75 ++++++ .../LINGYUN/Abp/OpenApi/Startup.cs | 20 ++ .../Properties/launchSettings.json | 27 ++ 35 files changed, 1284 insertions(+), 3 deletions(-) create mode 100644 aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN.Abp.OpenApi.Authorization.csproj create mode 100644 aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/AbpOpenApiAuthorizationModule.cs create mode 100644 aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/IOpenApiAuthorizationService.cs create mode 100644 aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/OpenApiAuthorizationMiddleware.cs create mode 100644 aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/OpenApiAuthorizationService.cs create mode 100644 aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/Microsoft/AspNetCore/Builder/OpenApiAuthorizationApplicationBuilderExtensions.cs create mode 100644 aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN.Abp.OpenApi.csproj create mode 100644 aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiConsts.cs create mode 100644 aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiModule.cs create mode 100644 aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiOptions.cs create mode 100644 aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AppDescriptor.cs create mode 100644 aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/ConfigurationStore/AbpDefaultAppKeyStoreOptions.cs create mode 100644 aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/ConfigurationStore/DefaultAppKeyStore.cs create mode 100644 aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/IAppKeyStore.cs create mode 100644 aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/OpenApiResource.cs create mode 100644 aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/en.json create mode 100644 aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/zh-Hans.json create mode 100644 aspnet-core/modules/open-api/OpenApi.Sdk/Microsoft/Extensions/DependencyInjection/ClientProxyServiceCollectionExtensions.cs create mode 100644 aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi.Sdk.csproj create mode 100644 aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/ApiResponse.cs create mode 100644 aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/ApiResponse`T.cs create mode 100644 aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/ClientProxy.cs create mode 100644 aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/IClientProxy.cs create mode 100644 aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/IClientProxyExtensions.cs create mode 100644 aspnet-core/modules/open-api/OpenApi.Sdk/System/StringMd5Extensions.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN.Abp.OpenApi.Tests.csproj create mode 100644 aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/AbpOpenApiTestBase.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/AbpOpenApiTestModule.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/FakeAppKeyStore.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/FakeInvokeController.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/FakeInvokeController_Tests.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/Startup.cs create mode 100644 aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/Properties/launchSettings.json diff --git a/aspnet-core/LINGYUN.MicroService.Common.sln b/aspnet-core/LINGYUN.MicroService.Common.sln index f40b45866..533f69ae7 100644 --- a/aspnet-core/LINGYUN.MicroService.Common.sln +++ b/aspnet-core/LINGYUN.MicroService.Common.sln @@ -206,7 +206,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.AspNetCore.Mvc. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Wrapper.Tests", "tests\LINGYUN.Abp.Wrapper.Tests\LINGYUN.Abp.Wrapper.Tests.csproj", "{31AED9ED-29BD-4F2F-8D3A-F00CBB9FC73C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.HttpClient.Wrapper", "modules\common\LINGYUN.Abp.HttpClient.Wrapper\LINGYUN.Abp.HttpClient.Wrapper.csproj", "{F6CABEE7-DE34-458A-8787-95DFB1DA4762}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.HttpClient.Wrapper", "modules\common\LINGYUN.Abp.HttpClient.Wrapper\LINGYUN.Abp.HttpClient.Wrapper.csproj", "{F6CABEE7-DE34-458A-8787-95DFB1DA4762}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "open-api", "open-api", "{8C688427-DD35-4F0B-86DA-6F536D3852D5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.OpenApi", "modules\open-api\LINGYUN.Abp.OpenApi\LINGYUN.Abp.OpenApi.csproj", "{2C8A6B4C-D6B2-44FE-9EF5-EC7480D2A6B2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.OpenApi.Authorization", "modules\open-api\LINGYUN.Abp.OpenApi.Authorization\LINGYUN.Abp.OpenApi.Authorization.csproj", "{3CE350AF-5574-46EC-8120-8542350AED20}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenApi.Sdk", "modules\open-api\OpenApi.Sdk\OpenApi.Sdk.csproj", "{108192F3-3780-423F-9871-A1BBE323413E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.OpenApi.Tests", "tests\LINGYUN.Abp.OpenApi.Tests\LINGYUN.Abp.OpenApi.Tests.csproj", "{6C75799E-4B46-434D-BE1B-4AD71DF49686}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -542,6 +552,22 @@ Global {F6CABEE7-DE34-458A-8787-95DFB1DA4762}.Debug|Any CPU.Build.0 = Debug|Any CPU {F6CABEE7-DE34-458A-8787-95DFB1DA4762}.Release|Any CPU.ActiveCfg = Release|Any CPU {F6CABEE7-DE34-458A-8787-95DFB1DA4762}.Release|Any CPU.Build.0 = Release|Any CPU + {2C8A6B4C-D6B2-44FE-9EF5-EC7480D2A6B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2C8A6B4C-D6B2-44FE-9EF5-EC7480D2A6B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2C8A6B4C-D6B2-44FE-9EF5-EC7480D2A6B2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2C8A6B4C-D6B2-44FE-9EF5-EC7480D2A6B2}.Release|Any CPU.Build.0 = Release|Any CPU + {3CE350AF-5574-46EC-8120-8542350AED20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3CE350AF-5574-46EC-8120-8542350AED20}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3CE350AF-5574-46EC-8120-8542350AED20}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3CE350AF-5574-46EC-8120-8542350AED20}.Release|Any CPU.Build.0 = Release|Any CPU + {108192F3-3780-423F-9871-A1BBE323413E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {108192F3-3780-423F-9871-A1BBE323413E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {108192F3-3780-423F-9871-A1BBE323413E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {108192F3-3780-423F-9871-A1BBE323413E}.Release|Any CPU.Build.0 = Release|Any CPU + {6C75799E-4B46-434D-BE1B-4AD71DF49686}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C75799E-4B46-434D-BE1B-4AD71DF49686}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C75799E-4B46-434D-BE1B-4AD71DF49686}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6C75799E-4B46-434D-BE1B-4AD71DF49686}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -646,6 +672,11 @@ Global {AE5E6DE8-FC02-4633-BA49-C4B8ABADB502} = {B86C21A4-73B7-471E-B73A-B4B905EC9435} {31AED9ED-29BD-4F2F-8D3A-F00CBB9FC73C} = {B86C21A4-73B7-471E-B73A-B4B905EC9435} {F6CABEE7-DE34-458A-8787-95DFB1DA4762} = {086BE5BE-8594-4DA7-8819-935FEF76DABD} + {8C688427-DD35-4F0B-86DA-6F536D3852D5} = {02EA4E78-5891-43BC-944F-3E52FEE032E4} + {2C8A6B4C-D6B2-44FE-9EF5-EC7480D2A6B2} = {8C688427-DD35-4F0B-86DA-6F536D3852D5} + {3CE350AF-5574-46EC-8120-8542350AED20} = {8C688427-DD35-4F0B-86DA-6F536D3852D5} + {108192F3-3780-423F-9871-A1BBE323413E} = {8C688427-DD35-4F0B-86DA-6F536D3852D5} + {6C75799E-4B46-434D-BE1B-4AD71DF49686} = {B86C21A4-73B7-471E-B73A-B4B905EC9435} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {06C707C6-02C0-411A-AD3B-2D0E13787CB8} diff --git a/aspnet-core/LINGYUN.MicroService.Platform.sln b/aspnet-core/LINGYUN.MicroService.Platform.sln index 31ed1fd60..ee2efa879 100644 --- a/aspnet-core/LINGYUN.MicroService.Platform.sln +++ b/aspnet-core/LINGYUN.MicroService.Platform.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30011.22 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "modules", "modules", "{15BDA03E-DE8E-46E4-96A8-CA3F2872E812}" EndProject @@ -92,6 +92,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "navigation", "navigation", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.UI.Navigation", "modules\navigation\LINGYUN.Abp.UI.Navigation\LINGYUN.Abp.UI.Navigation.csproj", "{BE9131C0-7DD4-4032-A5F7-4B9D76EC16CF}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.Wrapper", "modules\common\LINGYUN.Abp.Wrapper\LINGYUN.Abp.Wrapper.csproj", "{36EFF070-3400-448E-A912-82930D6E1828}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LINGYUN.Abp.AspNetCore.Tests", "tests\LINGYUN.Abp.AspNetCore.Tests\LINGYUN.Abp.AspNetCore.Tests.csproj", "{3B363320-B679-4D29-B428-28311C576E2E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -226,6 +230,14 @@ Global {BE9131C0-7DD4-4032-A5F7-4B9D76EC16CF}.Debug|Any CPU.Build.0 = Debug|Any CPU {BE9131C0-7DD4-4032-A5F7-4B9D76EC16CF}.Release|Any CPU.ActiveCfg = Release|Any CPU {BE9131C0-7DD4-4032-A5F7-4B9D76EC16CF}.Release|Any CPU.Build.0 = Release|Any CPU + {36EFF070-3400-448E-A912-82930D6E1828}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {36EFF070-3400-448E-A912-82930D6E1828}.Debug|Any CPU.Build.0 = Debug|Any CPU + {36EFF070-3400-448E-A912-82930D6E1828}.Release|Any CPU.ActiveCfg = Release|Any CPU + {36EFF070-3400-448E-A912-82930D6E1828}.Release|Any CPU.Build.0 = Release|Any CPU + {3B363320-B679-4D29-B428-28311C576E2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B363320-B679-4D29-B428-28311C576E2E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B363320-B679-4D29-B428-28311C576E2E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B363320-B679-4D29-B428-28311C576E2E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -271,6 +283,8 @@ Global {2A69A38F-E98A-490D-BEAE-1BBBD87121DD} = {4096EC6A-EEAD-4E5B-B087-393D7A4E5874} {D61721B8-B0F7-4C3D-AD1B-44FBE81DFA79} = {15BDA03E-DE8E-46E4-96A8-CA3F2872E812} {BE9131C0-7DD4-4032-A5F7-4B9D76EC16CF} = {D61721B8-B0F7-4C3D-AD1B-44FBE81DFA79} + {36EFF070-3400-448E-A912-82930D6E1828} = {265D5E44-682B-49BC-984A-BDD8CA45E60E} + {3B363320-B679-4D29-B428-28311C576E2E} = {CCEFF583-4EEE-433F-8568-9E64166B41FE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {03D3B66F-8926-4C00-B7AB-A21761EC859E} diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN.Abp.OpenApi.Authorization.csproj b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN.Abp.OpenApi.Authorization.csproj new file mode 100644 index 000000000..c58e6bb0d --- /dev/null +++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN.Abp.OpenApi.Authorization.csproj @@ -0,0 +1,19 @@ + + + + + + net5.0 + + + + + + + + + + + + + diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/AbpOpenApiAuthorizationModule.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/AbpOpenApiAuthorizationModule.cs new file mode 100644 index 000000000..43afaa467 --- /dev/null +++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/AbpOpenApiAuthorizationModule.cs @@ -0,0 +1,16 @@ +using LINGYUN.Abp.Wrapper; +using Volo.Abp.AspNetCore; +using Volo.Abp.Modularity; +using Volo.Abp.Timing; + +namespace LINGYUN.Abp.OpenApi.Authorization +{ + [DependsOn( + typeof(AbpWrapperModule), + typeof(AbpTimingModule), + typeof(AbpOpenApiModule), + typeof(AbpAspNetCoreModule))] + public class AbpOpenApiAuthorizationModule : AbpModule + { + } +} diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/IOpenApiAuthorizationService.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/IOpenApiAuthorizationService.cs new file mode 100644 index 000000000..0003d71e4 --- /dev/null +++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/IOpenApiAuthorizationService.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Http; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.OpenApi.Authorization +{ + public interface IOpenApiAuthorizationService + { + Task AuthorizeAsync(HttpContext httpContext); + } +} diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/OpenApiAuthorizationMiddleware.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/OpenApiAuthorizationMiddleware.cs new file mode 100644 index 000000000..5d614d3af --- /dev/null +++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/OpenApiAuthorizationMiddleware.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Http; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.OpenApi.Authorization +{ + public class OpenApiAuthorizationMiddleware : IMiddleware, ITransientDependency + { + private readonly IOpenApiAuthorizationService _authorizationService; + public OpenApiAuthorizationMiddleware( + IOpenApiAuthorizationService authorizationService) + { + _authorizationService = authorizationService; + } + + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + if (await _authorizationService.AuthorizeAsync(context)) + { + await next(context); + } + } + } +} diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/OpenApiAuthorizationService.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/OpenApiAuthorizationService.cs new file mode 100644 index 000000000..7ecfd1930 --- /dev/null +++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/LINGYUN/Abp/OpenApi/Authorization/OpenApiAuthorizationService.cs @@ -0,0 +1,234 @@ +using LINGYUN.Abp.Wrapper; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using Volo.Abp; +using Volo.Abp.AspNetCore.ExceptionHandling; +using Volo.Abp.AspNetCore.WebClientInfo; +using Volo.Abp.Clients; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http; +using Volo.Abp.Json; + +namespace LINGYUN.Abp.OpenApi.Authorization +{ + public class OpenApiAuthorizationService : IOpenApiAuthorizationService, ITransientDependency + { + private readonly IAppKeyStore _appKeyStore; + private readonly AbpOpenApiOptions _openApiOptions; + private readonly ICurrentClient _currentClient; + private readonly IWebClientInfoProvider _clientInfoProvider; + + public OpenApiAuthorizationService( + IAppKeyStore appKeyStore, + ICurrentClient currentClient, + IWebClientInfoProvider clientInfoProvider, + IOptionsMonitor options) + { + _appKeyStore = appKeyStore; + _currentClient = currentClient; + _clientInfoProvider = clientInfoProvider; + _openApiOptions = options.CurrentValue; + } + + public virtual async Task AuthorizeAsync(HttpContext httpContext) + { + if (!_openApiOptions.IsEnabled) + { + return true; + } + + if (_currentClient.IsAuthenticated && + _openApiOptions.HasWhiteClient(_currentClient.Id)) + { + return true; + } + + if (!string.IsNullOrWhiteSpace(_clientInfoProvider.ClientIpAddress) && + _openApiOptions.HasWhiteIpAddress(_clientInfoProvider.ClientIpAddress)) + { + return true; + } + + BusinessException exception; + if (!httpContext.Request.QueryString.HasValue) + { + exception = new BusinessException( + AbpOpenApiConsts.InvalidAccessWithAppKeyNotFound, + $"{AbpOpenApiConsts.AppKeyFieldName} Not Found", + $"{AbpOpenApiConsts.AppKeyFieldName} Not Found"); + await Unauthorized(httpContext, exception); + return false; + } + + httpContext.Request.Query.TryGetValue(AbpOpenApiConsts.AppKeyFieldName, out var appKey); + httpContext.Request.Query.TryGetValue(AbpOpenApiConsts.SignatureFieldName, out var sign); + httpContext.Request.Query.TryGetValue(AbpOpenApiConsts.TimeStampFieldName, out var timeStampString); + + if (StringValues.IsNullOrEmpty(appKey)) + { + exception = new BusinessException( + AbpOpenApiConsts.InvalidAccessWithAppKeyNotFound, + $"{AbpOpenApiConsts.AppKeyFieldName} Not Found", + $"{AbpOpenApiConsts.AppKeyFieldName} Not Found"); + await Unauthorized(httpContext, exception); + return false; + } + + if (StringValues.IsNullOrEmpty(sign)) + { + exception = new BusinessException( + AbpOpenApiConsts.InvalidAccessWithSignNotFound, + $"{AbpOpenApiConsts.SignatureFieldName} Not Found", + $"{AbpOpenApiConsts.SignatureFieldName} Not Found"); + + await Unauthorized(httpContext, exception); + return false; + } + + if (StringValues.IsNullOrEmpty(timeStampString)) + { + exception = new BusinessException( + AbpOpenApiConsts.InvalidAccessWithTimestampNotFound, + $"{AbpOpenApiConsts.TimeStampFieldName} Not Found", + $"{AbpOpenApiConsts.TimeStampFieldName} Not Found"); + + await Unauthorized(httpContext, exception); + return false; + } + + if (!long.TryParse(timeStampString.ToString(), out long timeStamp)) + { + exception = new BusinessException( + AbpOpenApiConsts.InvalidAccessWithTimestamp, + "invalid timestamp", + "invalid timestamp"); + + await Unauthorized(httpContext, exception); + return false; + } + + var appDescriptor = await _appKeyStore.FindAsync(appKey.ToString()); + if (appDescriptor == null) + { + exception = new BusinessException( + AbpOpenApiConsts.InvalidAccessWithAppKey, + "invalid appKey", + "invalid appKey") + .WithData("AppKey", appKey.ToString()); + + await Unauthorized(httpContext, exception); + return false; + } + + var queryDictionary = new Dictionary(); + var queryStringCollection = httpContext.Request.Query.OrderBy(q => q.Key); + foreach (var queryString in queryStringCollection) + { + if (queryString.Key.Equals(AbpOpenApiConsts.SignatureFieldName)) + { + continue; + } + queryDictionary.Add(queryString.Key, queryString.Value.ToString()); + } + + var requiredSign = CalculationSignature(httpContext.Request.Path.Value, appDescriptor.AppSecret, queryDictionary); + if (!string.Equals(requiredSign, sign.ToString())) + { + exception = new BusinessException( + AbpOpenApiConsts.InvalidAccessWithSign, + "invalid signature", + "invalid signature"); + + await Unauthorized(httpContext, exception); + return false; + } + + if (appDescriptor.SignLifetime.HasValue && appDescriptor.SignLifetime > 0) + { + var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + + if ((now - timeStamp) / 1000 > appDescriptor.SignLifetime.Value) + { + exception = new BusinessException( + AbpOpenApiConsts.InvalidAccessWithTimestamp, + "session timed out or expired", + "session timed out or expired"); + + await Unauthorized(httpContext, exception); + return false; + } + } + + return true; + } + + protected virtual async Task Unauthorized(HttpContext context, BusinessException exception) + { + var errorInfoConverter = context.RequestServices.GetRequiredService(); + var errorInfo = errorInfoConverter.Convert(exception, false); + + var exceptionWrapHandlerFactory = context.RequestServices.GetRequiredService(); + var exceptionWrapContext = new ExceptionWrapContext( + exception, + errorInfo, + context.RequestServices); + exceptionWrapHandlerFactory.CreateFor(exceptionWrapContext).Wrap(exceptionWrapContext); + + if (context.Request.CanAccept(MimeTypes.Application.Json) || + context.Request.IsAjax()) + { + var wrapResult = new WrapResult( + exceptionWrapContext.ErrorInfo.Code, + exceptionWrapContext.ErrorInfo.Message, + exceptionWrapContext.ErrorInfo.Details); + + var jsonSerializer = context.RequestServices.GetRequiredService(); + + context.Response.Headers.Add(AbpHttpWrapConsts.AbpWrapResult, "true"); + context.Response.StatusCode = (int)HttpStatusCode.OK; + + await context.Response.WriteAsync(jsonSerializer.Serialize(wrapResult)); + return; + } + + context.Response.StatusCode = (int)HttpStatusCode.BadRequest; + await context.Response.WriteAsync(errorInfo.Message); + } + + private static string CalculationSignature(string url, string appKey, IDictionary queryDictionary) + { + var queryString = BuildQuery(queryDictionary); + var encodeUrl = UrlEncode(string.Concat(url, "?", queryString, appKey)); + + return encodeUrl.ToMd5(); + } + + private static string BuildQuery(IDictionary queryStringDictionary) + { + StringBuilder sb = new StringBuilder(); + foreach (var queryString in queryStringDictionary) + { + sb.Append(queryString.Key) + .Append('=') + .Append(queryString.Value) + .Append('&'); + } + sb.Remove(sb.Length - 1, 1); + return sb.ToString(); + } + + private static string UrlEncode(string str) + { + return HttpUtility.UrlEncode(str); + } + } +} diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/Microsoft/AspNetCore/Builder/OpenApiAuthorizationApplicationBuilderExtensions.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/Microsoft/AspNetCore/Builder/OpenApiAuthorizationApplicationBuilderExtensions.cs new file mode 100644 index 000000000..686482c02 --- /dev/null +++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi.Authorization/Microsoft/AspNetCore/Builder/OpenApiAuthorizationApplicationBuilderExtensions.cs @@ -0,0 +1,12 @@ +using LINGYUN.Abp.OpenApi.Authorization; + +namespace Microsoft.AspNetCore.Builder +{ + public static class OpenApiAuthorizationApplicationBuilderExtensions + { + public static IApplicationBuilder UseOpenApiAuthorization(this IApplicationBuilder app) + { + return app.UseMiddleware(); + } + } +} diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN.Abp.OpenApi.csproj b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN.Abp.OpenApi.csproj new file mode 100644 index 000000000..2f65cac0e --- /dev/null +++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN.Abp.OpenApi.csproj @@ -0,0 +1,25 @@ + + + + + + netstandard2.0 + + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiConsts.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiConsts.cs new file mode 100644 index 000000000..e636f4c88 --- /dev/null +++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiConsts.cs @@ -0,0 +1,22 @@ +namespace LINGYUN.Abp.OpenApi +{ + public static class AbpOpenApiConsts + { + public const string SecurityChecking = "_AbpOpenApiSecurityChecking"; + + public const string AppKeyFieldName = "appKey"; + public const string SignatureFieldName = "sign"; + public const string TimeStampFieldName = "t"; + + public const string KeyPrefix = "AbpOpenApi"; + + public const string InvalidAccessWithAppKey = KeyPrefix + ":9100"; + public const string InvalidAccessWithAppKeyNotFound = KeyPrefix + ":9101"; + + public const string InvalidAccessWithSign = KeyPrefix + ":9110"; + public const string InvalidAccessWithSignNotFound = KeyPrefix + ":9111"; + + public const string InvalidAccessWithTimestamp = KeyPrefix + ":9210"; + public const string InvalidAccessWithTimestampNotFound = KeyPrefix + ":9211"; + } +} diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiModule.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiModule.cs new file mode 100644 index 000000000..e3eae6d14 --- /dev/null +++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiModule.cs @@ -0,0 +1,40 @@ +using LINGYUN.Abp.OpenApi.Localization; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Localization; +using Volo.Abp.Localization.ExceptionHandling; +using Volo.Abp.Modularity; +using Volo.Abp.Security; +using Volo.Abp.VirtualFileSystem; + +namespace LINGYUN.Abp.OpenApi +{ + [DependsOn( + typeof(AbpSecurityModule), + typeof(AbpLocalizationModule))] + public class AbpOpenApiModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + + Configure(configuration.GetSection("OpenApi")); + + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Add() + .AddVirtualJson("/LINGYUN/Abp/OpenApi/Localization/Resources"); + }); + + Configure(options => + { + options.MapCodeNamespace(AbpOpenApiConsts.KeyPrefix, typeof(OpenApiResource)); + }); + } + } +} diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiOptions.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiOptions.cs new file mode 100644 index 000000000..bb6c4808b --- /dev/null +++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AbpOpenApiOptions.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LINGYUN.Abp.OpenApi +{ + public class AbpOpenApiOptions + { + public bool IsEnabled { get; set; } + public string[] WhiteIpAddress { get; set; } + public string[] WhiteClient { get; set; } + public AbpOpenApiOptions() + { + IsEnabled = true; + WhiteIpAddress = new string[0]; + WhiteClient = new string[0]; + } + + public bool HasWhiteIpAddress(string ipAddress) + { + return WhiteIpAddress?.Contains(ipAddress) == true; + } + + public bool HasWhiteClient(string clientId) + { + if (clientId.IsNullOrWhiteSpace()) + { + return false; + } + return WhiteClient?.Contains(clientId) == true; + } + } +} diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AppDescriptor.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AppDescriptor.cs new file mode 100644 index 000000000..903048140 --- /dev/null +++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/AppDescriptor.cs @@ -0,0 +1,42 @@ +namespace LINGYUN.Abp.OpenApi +{ + public class AppDescriptor + { + /// + /// 应用名称 + /// + public string AppName { get; set; } + /// + /// 应用标识 + /// + public string AppKey { get; set; } + /// + /// 应用密钥 + /// + public string AppSecret { get; set; } + /// + /// 应用token + /// + public string AppToken { get; set; } + /// + /// 签名有效时间 + /// 单位: s + /// + public int? SignLifetime { get; set; } + + public AppDescriptor() { } + public AppDescriptor( + string appName, + string appKey, + string appSecret, + string appToken = null, + int? signLifeTime = null) + { + AppName = appName; + AppKey = appKey; + AppSecret = appSecret; + AppToken = appToken; + SignLifetime = signLifeTime; + } + } +} diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/ConfigurationStore/AbpDefaultAppKeyStoreOptions.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/ConfigurationStore/AbpDefaultAppKeyStoreOptions.cs new file mode 100644 index 000000000..ac89b6d69 --- /dev/null +++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/ConfigurationStore/AbpDefaultAppKeyStoreOptions.cs @@ -0,0 +1,12 @@ +namespace LINGYUN.Abp.OpenApi.ConfigurationStore +{ + public class AbpDefaultAppKeyStoreOptions + { + public AppDescriptor[] Apps { get; set; } + + public AbpDefaultAppKeyStoreOptions() + { + Apps = new AppDescriptor[0]; + } + } +} diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/ConfigurationStore/DefaultAppKeyStore.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/ConfigurationStore/DefaultAppKeyStore.cs new file mode 100644 index 000000000..c3d1d9561 --- /dev/null +++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/ConfigurationStore/DefaultAppKeyStore.cs @@ -0,0 +1,28 @@ +using Microsoft.Extensions.Options; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.OpenApi.ConfigurationStore +{ + [Dependency(TryRegister = true)] + public class DefaultAppKeyStore : IAppKeyStore, ITransientDependency + { + private readonly AbpDefaultAppKeyStoreOptions _options; + + public DefaultAppKeyStore(IOptionsMonitor options) + { + _options = options.CurrentValue; + } + + public Task FindAsync(string appKey) + { + return Task.FromResult(Find(appKey)); + } + + public AppDescriptor Find(string appKey) + { + return _options.Apps?.FirstOrDefault(t => t.AppKey == appKey); + } + } +} diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/IAppKeyStore.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/IAppKeyStore.cs new file mode 100644 index 000000000..047d81f76 --- /dev/null +++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/IAppKeyStore.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace LINGYUN.Abp.OpenApi +{ + public interface IAppKeyStore + { + Task FindAsync(string appKey); + } +} diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/OpenApiResource.cs b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/OpenApiResource.cs new file mode 100644 index 000000000..3215ca85d --- /dev/null +++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/OpenApiResource.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.OpenApi.Localization +{ + [LocalizationResourceName("OpenApi")] + public class OpenApiResource + { + } +} diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/en.json b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/en.json new file mode 100644 index 000000000..cffd482c2 --- /dev/null +++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/en.json @@ -0,0 +1,11 @@ +{ + "culture": "en", + "texts": { + "AbpOpenApi:9100": "Invalid appKey {AppKey}.", + "AbpOpenApi:9101": "appKey not found.", + "AbpOpenApi:9110": "Invalid sign.", + "AbpOpenApi:9111": "sign not found.", + "AbpOpenApi:9210": "Request timed out or the session expired.", + "AbpOpenApi:9211": "timestamp not found." + } +} \ No newline at end of file diff --git a/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/zh-Hans.json b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/zh-Hans.json new file mode 100644 index 000000000..0853fa4df --- /dev/null +++ b/aspnet-core/modules/open-api/LINGYUN.Abp.OpenApi/LINGYUN/Abp/OpenApi/Localization/Resources/zh-Hans.json @@ -0,0 +1,11 @@ +{ + "culture": "zh-Hans", + "texts": { + "AbpOpenApi:9100": "无效的应用标识 {AppKey}.", + "AbpOpenApi:9101": "未携带应用标识(appKey).", + "AbpOpenApi:9110": "无效的签名 sign.", + "AbpOpenApi:9111": "未携带签名(sign).", + "AbpOpenApi:9210": "请求超时或会话已过期.", + "AbpOpenApi:9211": "未携带时间戳标识." + } +} \ No newline at end of file diff --git a/aspnet-core/modules/open-api/OpenApi.Sdk/Microsoft/Extensions/DependencyInjection/ClientProxyServiceCollectionExtensions.cs b/aspnet-core/modules/open-api/OpenApi.Sdk/Microsoft/Extensions/DependencyInjection/ClientProxyServiceCollectionExtensions.cs new file mode 100644 index 000000000..409c762db --- /dev/null +++ b/aspnet-core/modules/open-api/OpenApi.Sdk/Microsoft/Extensions/DependencyInjection/ClientProxyServiceCollectionExtensions.cs @@ -0,0 +1,19 @@ +using OpenApi; +using System; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class ClientProxyServiceCollectionExtensions + { + public static IServiceCollection AddClientProxy(this IServiceCollection services, string serverUrl) + { + services.AddHttpClient("opensdk", options => + { + options.BaseAddress = new Uri(serverUrl); + }); + services.AddSingleton(); + + return services; + } + } +} diff --git a/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi.Sdk.csproj b/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi.Sdk.csproj new file mode 100644 index 000000000..5b2285436 --- /dev/null +++ b/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi.Sdk.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + + + + + + + + + diff --git a/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/ApiResponse.cs b/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/ApiResponse.cs new file mode 100644 index 000000000..4f9111d68 --- /dev/null +++ b/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/ApiResponse.cs @@ -0,0 +1,25 @@ +using System; + +namespace OpenApi +{ + [Serializable] + public class ApiResponse : ApiResponse + { + public ApiResponse() { } + public ApiResponse( + string code, + string message, + string details = null) + : base(code, message, details) + { + } + + public ApiResponse( + string code, + object result, + string message = "OK") + : base(code, result, message) + { + } + } +} diff --git a/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/ApiResponse`T.cs b/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/ApiResponse`T.cs new file mode 100644 index 000000000..4cc7434b9 --- /dev/null +++ b/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/ApiResponse`T.cs @@ -0,0 +1,45 @@ +using System; + +namespace OpenApi +{ + [Serializable] + public class ApiResponse + { + /// + /// 错误代码 + /// + public string Code { get; set; } + /// + /// 错误提示消息 + /// + public string Message { get; set; } + /// + /// 补充消息 + /// + public string Details { get; set; } + /// + /// 返回值 + /// + public TResult Result { get; set; } + public ApiResponse() { } + public ApiResponse( + string code, + string message, + string details = null) + { + Code = code; + Message = message; + Details = details; + } + + public ApiResponse( + string code, + TResult result, + string message = "OK") + { + Code = code; + Result = result; + Message = message; + } + } +} diff --git a/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/ClientProxy.cs b/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/ClientProxy.cs new file mode 100644 index 000000000..b132b10ad --- /dev/null +++ b/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/ClientProxy.cs @@ -0,0 +1,159 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using System.Web; + +namespace OpenApi +{ + public class ClientProxy : IClientProxy + { + private readonly IHttpClientFactory _httpClientFactory; + + public ClientProxy(IHttpClientFactory httpClientFactory) + { + _httpClientFactory = httpClientFactory; + } + + public virtual async Task> DeleteAsync(string url, string appKey, string appSecret) + { + return await RequestAsync(url, appKey, appSecret, HttpMethod.Delete); + } + + public virtual async Task> GetAsync(string url, string appKey, string appSecret) + { + return await RequestAsync(url, appKey, appSecret, HttpMethod.Get); + } + + public virtual async Task> PostAsync(string url, string appKey, string appSecret, TRequest request) + { + return await RequestAsync(url, appKey, appSecret, request, HttpMethod.Post); + } + + public virtual async Task> PutAsync(string url, string appKey, string appSecret, TRequest request) + { + return await RequestAsync(url, appKey, appSecret, request, HttpMethod.Put); + } + + public virtual async Task> RequestAsync(string url, string appKey, string appSecret, HttpMethod httpMethod) + { + return await RequestAsync(url, appKey, appSecret, null, httpMethod); + } + + public virtual async Task> RequestAsync(string url, string appKey, string appSecret, TRequest request, HttpMethod httpMethod) + { + // 构建请求客户端 + var client = _httpClientFactory.CreateClient("opensdk"); + + return await RequestAsync(client, url, appKey, appSecret, request, httpMethod); + } + + public virtual async Task> RequestAsync(HttpClient client, string url, string appKey, string appSecret, TRequest request, HttpMethod httpMethod) + { + // UTC时间戳 + var timeStamp = GetUtcTimeStampString(); + // 取出api地址 + var baseUrl = url.Split('?')[0]; + // 组装请求参数 + var requestUrl = string.Concat( + url, + url.Contains('?') ? "&" : "?", + "appKey=", + appKey, + "&t=", + timeStamp); + var quertString = ReverseQueryString(requestUrl); + // 对请求参数签名 + var sign = CalculationSignature(baseUrl, appSecret, quertString); + // 签名随请求传递 + quertString.Add("sign", sign); + // 重新拼接请求参数 + requestUrl = string.Concat(baseUrl, "?", BuildQuery(quertString)); + // 构建请求体 + var requestMessage = new HttpRequestMessage(httpMethod, requestUrl); + + if (request != null) + { + // Request Payload + requestMessage.Content = new StringContent(JsonConvert.SerializeObject(request)); + } + + // 返回中文错误提示 + requestMessage.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(CultureInfo.CurrentUICulture.Name)); + // 返回错误消息可序列化 + requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + // 序列化响应 + var response = await client.SendAsync(requestMessage); + var stringContent = await response.Content.ReadAsStringAsync(); + + return JsonConvert.DeserializeObject>(stringContent); + } + + protected virtual string GetUtcTimeStampString() + { + return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString(); + } + + private static IDictionary ReverseQueryString(string requestUri) + { + if (!requestUri.Contains("?")) + { + throw new Exception("查询路径格式不合法"); + } + + var queryString = requestUri.Split('?')[1]; + // 按照首字母排序 + var paramters = queryString.Split('&').OrderBy(p => p); + + var queryDic = new Dictionary(); + foreach (var parm in paramters) + { + var thisParams = parm.Split('='); + if (thisParams.Length == 0) + { + continue; + } + queryDic.Add(thisParams[0], thisParams.Length > 0 ? thisParams[1] : ""); + } + + // 返回参数列表 + return queryDic; + } + + private static string CalculationSignature(string url, string appSecret, IDictionary queryDictionary) + { + var queryString = BuildQuery(queryDictionary); + var requestUrl = string.Concat( + url, + url.Contains('?') ? "" : "?", + queryString, + appSecret); + var encodeUrl = UrlEncode(requestUrl); + return encodeUrl.ToMd5(); + } + + private static string BuildQuery(IDictionary queryStringDictionary) + { + StringBuilder sb = new StringBuilder(); + foreach (var queryString in queryStringDictionary) + { + sb.Append(queryString.Key) + .Append('=') + .Append(queryString.Value) + .Append('&'); + } + sb.Remove(sb.Length - 1, 1); + return sb.ToString(); + } + + private static string UrlEncode(string str) + { + return HttpUtility.UrlEncode(str); + } + } +} diff --git a/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/IClientProxy.cs b/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/IClientProxy.cs new file mode 100644 index 000000000..3a8789ca9 --- /dev/null +++ b/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/IClientProxy.cs @@ -0,0 +1,22 @@ +using System.Net.Http; +using System.Threading.Tasks; + +namespace OpenApi +{ + public interface IClientProxy + { + Task> GetAsync(string url, string appKey, string appSecret); + + Task> DeleteAsync(string url, string appKey, string appSecret); + + Task> PutAsync(string url, string appKey, string appSecret, TRequest request); + + Task> PostAsync(string url, string appKey, string appSecret, TRequest request); + + Task> RequestAsync(string url, string appKey, string appSecret, HttpMethod httpMethod); + + Task> RequestAsync(string url, string appKey, string appSecret, TRequest request, HttpMethod httpMethod); + + Task> RequestAsync(HttpClient client, string url, string appKey, string appSecret, TRequest request, HttpMethod httpMethod); + } +} diff --git a/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/IClientProxyExtensions.cs b/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/IClientProxyExtensions.cs new file mode 100644 index 000000000..68acc6aed --- /dev/null +++ b/aspnet-core/modules/open-api/OpenApi.Sdk/OpenApi/IClientProxyExtensions.cs @@ -0,0 +1,19 @@ +using System.Net.Http; +using System.Threading.Tasks; + +namespace OpenApi +{ + public static class IClientProxyExtensions + { + public static async Task> RequestAsync( + this IClientProxy proxy, + HttpClient client, + string url, + string appKey, + string appSecret, + HttpMethod httpMethod) + { + return await proxy.RequestAsync(client, url, appKey, appSecret, null, httpMethod); + } + } +} diff --git a/aspnet-core/modules/open-api/OpenApi.Sdk/System/StringMd5Extensions.cs b/aspnet-core/modules/open-api/OpenApi.Sdk/System/StringMd5Extensions.cs new file mode 100644 index 000000000..d5502c5e4 --- /dev/null +++ b/aspnet-core/modules/open-api/OpenApi.Sdk/System/StringMd5Extensions.cs @@ -0,0 +1,25 @@ +using System.Security.Cryptography; +using System.Text; + +namespace System +{ + internal static class StringMd5Extensions + { + public static string ToMd5(this string str) + { + using (MD5 mD = MD5.Create()) + { + byte[] bytes = Encoding.UTF8.GetBytes(str); + byte[] array = mD.ComputeHash(bytes); + StringBuilder stringBuilder = new StringBuilder(); + byte[] array2 = array; + foreach (byte b in array2) + { + stringBuilder.Append(b.ToString("X2")); + } + + return stringBuilder.ToString(); + } + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN.Abp.OpenApi.Tests.csproj b/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN.Abp.OpenApi.Tests.csproj new file mode 100644 index 000000000..d6dd1d242 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN.Abp.OpenApi.Tests.csproj @@ -0,0 +1,38 @@ + + + + net5.0 + + false + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + diff --git a/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/AbpOpenApiTestBase.cs b/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/AbpOpenApiTestBase.cs new file mode 100644 index 000000000..60297bb7d --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/AbpOpenApiTestBase.cs @@ -0,0 +1,33 @@ +using LINGYUN.Abp.AspNetCore; +using Microsoft.Extensions.Hosting; +using System.IO; +using System.Linq; + +namespace LINGYUN.Abp.OpenApi +{ + public abstract class AbpOpenApiTestBase : AbpAspNetCoreTestBase + { + protected override IHostBuilder CreateHostBuilder() + { + return base.CreateHostBuilder(); + } + + private static string CalculateContentRootPath(string projectFileName, string contentPath) + { + var currentDirectory = Directory.GetCurrentDirectory(); + while (!ContainsFile(currentDirectory, projectFileName)) + { + currentDirectory = new DirectoryInfo(currentDirectory).Parent.FullName; + } + + return Path.Combine(currentDirectory, contentPath); + } + + private static bool ContainsFile(string currentDirectory, string projectFileName) + { + return Directory + .GetFiles(currentDirectory, "*.*", SearchOption.TopDirectoryOnly) + .Any(f => Path.GetFileName(f) == projectFileName); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/AbpOpenApiTestModule.cs b/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/AbpOpenApiTestModule.cs new file mode 100644 index 000000000..c6cdeb6d7 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/AbpOpenApiTestModule.cs @@ -0,0 +1,114 @@ +using LINGYUN.Abp.AspNetCore.Mvc.Wrapper; +using LINGYUN.Abp.OpenApi.Authorization; +using LINGYUN.Abp.OpenApi.Localization; +using Localization.Resources.AbpUi; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.DependencyInjection; +using OpenApi; +using Volo.Abp; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.Localization; +using Volo.Abp.AspNetCore.Security.Claims; +using Volo.Abp.AspNetCore.TestBase; +using Volo.Abp.Autofac; +using Volo.Abp.Localization; +using Volo.Abp.Localization.ExceptionHandling; +using Volo.Abp.Modularity; +using Volo.Abp.Validation.Localization; +using Volo.Abp.VirtualFileSystem; + +namespace LINGYUN.Abp.OpenApi +{ + [DependsOn( + typeof(AbpOpenApiAuthorizationModule), + typeof(AbpAspNetCoreTestBaseModule), + typeof(AbpAspNetCoreMvcWrapperModule), + typeof(AbpAutofacModule) + )] + public class AbpOpenApiTestModule : AbpModule + { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + context.Services.PreConfigure(options => + { + options.AddAssemblyResource( + typeof(OpenApiResource), + typeof(AbpOpenApiTestModule).Assembly + ); + }); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + // 测试环境不需要主机地址 + context.Services.AddHttpClient("opensdk"); + context.Services.AddSingleton(); + + context.Services.AddAuthentication(options => + { + options.DefaultChallengeScheme = "Bearer"; + options.DefaultForbidScheme = "Cookie"; + }).AddCookie("Cookie").AddJwtBearer("Bearer", _ => { }); + + context.Services.AddAuthorization(options => + { + }); + + Configure(options => + { + }); + + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Get() + .AddBaseTypes( + typeof(AbpUiResource), + typeof(AbpValidationResource) + ).AddVirtualJson("/LINGYUN/Abp/AspNetCore/Mvc/Localization/Resources"); + + options.Languages.Add(new LanguageInfo("en", "en", "English")); + options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文")); + }); + + Configure(options => + { + options.RootDirectory = "/LINGYUN/Abp/AspNetCore/Mvc"; + }); + + Configure(options => + { + + }); + + Configure(options => + { + options.ErrorCodeNamespaceMappings.Add("Test", typeof(OpenApiResource)); + }); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + var app = context.GetApplicationBuilder(); + + app.UseCorrelationId(); + app.UseStaticFiles(); + app.UseAbpRequestLocalization(); + app.UseAbpSecurityHeaders(); + app.UseRouting(); + app.UseAbpClaimsMap(); + app.UseAuthentication(); + app.UseOpenApiAuthorization(); + app.UseAuthorization(); + app.UseAuditing(); + app.UseUnitOfWork(); + app.UseConfiguredEndpoints(); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/FakeAppKeyStore.cs b/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/FakeAppKeyStore.cs new file mode 100644 index 000000000..a9e53f40a --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/FakeAppKeyStore.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.OpenApi +{ + [Dependency(ReplaceServices = true)] + public class FakeAppKeyStore : IAppKeyStore, ISingletonDependency + { + public readonly static AppDescriptor AppDescriptor = + new AppDescriptor( + "TEST", + Guid.NewGuid().ToString("N"), + Guid.NewGuid().ToString("N"), + signLifeTime: 5); + + public virtual Task FindAsync(string appKey) + { + AppDescriptor app = null; + if (AppDescriptor.AppKey.Equals(appKey)) + { + app = AppDescriptor; + } + + return Task.FromResult(app); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/FakeInvokeController.cs b/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/FakeInvokeController.cs new file mode 100644 index 000000000..98c03ea17 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/FakeInvokeController.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Mvc; + +namespace LINGYUN.Abp.OpenApi +{ + [Route("/api/invoke")] + public class FakeInvokeController : AbpController + { + [HttpGet] + public Task Index() + { + return Task.FromResult("Hello"); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/FakeInvokeController_Tests.cs b/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/FakeInvokeController_Tests.cs new file mode 100644 index 000000000..4fdccfae4 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/FakeInvokeController_Tests.cs @@ -0,0 +1,75 @@ +using OpenApi; +using Shouldly; +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Volo.Abp.Localization; +using Xunit; + +namespace LINGYUN.Abp.OpenApi +{ + public class FakeInvokeController_Tests : AbpOpenApiTestBase + { + private readonly IClientProxy _clientProxy; + + public FakeInvokeController_Tests() + { + _clientProxy = GetRequiredService(); + } + + [Fact] + public async Task Should_Invoke_Controller_And_Result_With_Hello() + { + var result = await _clientProxy.RequestAsync( + Client, + "/api/invoke", + FakeAppKeyStore.AppDescriptor.AppKey, + FakeAppKeyStore.AppDescriptor.AppSecret, + HttpMethod.Get); + + result.Code.ShouldBe("0"); + result.Message.ShouldBe("OK"); + result.Result.ShouldBe("Hello"); + result.Details.ShouldBeNull(); + } + + [Fact] + public async Task Should_Invoke_Controller_With_Invalid_App_Key() + { + using (CultureHelper.Use("en")) + { + var appKey = Guid.NewGuid().ToString("N"); + var result = await _clientProxy.RequestAsync( + Client, + "/api/invoke", + appKey, + Guid.NewGuid().ToString("N"), + HttpMethod.Get); + + result.Code.ShouldBe("9100"); + result.Message.ShouldBe($"Invalid appKey {appKey}."); + result.Result.ShouldBeNull(); + result.Details.ShouldBeNull(); + } + } + + [Fact] + public async Task Should_Invoke_Controller_With_Invalid_Signature() + { + using (CultureHelper.Use("en")) + { + var result = await _clientProxy.RequestAsync( + Client, + "/api/invoke", + FakeAppKeyStore.AppDescriptor.AppKey, + Guid.NewGuid().ToString("N"), + HttpMethod.Get); + + result.Code.ShouldBe("9110"); + result.Message.ShouldBe("Invalid sign."); + result.Result.ShouldBeNull(); + result.Details.ShouldBeNull(); + } + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/Startup.cs b/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/Startup.cs new file mode 100644 index 000000000..de2e83615 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/LINGYUN/Abp/OpenApi/Startup.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace LINGYUN.Abp.OpenApi +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddApplication(); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) + { + app.InitializeApplication(); + } + } +} diff --git a/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/Properties/launchSettings.json b/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/Properties/launchSettings.json new file mode 100644 index 000000000..fea581dc8 --- /dev/null +++ b/aspnet-core/tests/LINGYUN.Abp.OpenApi.Tests/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:49160/", + "sslPort": 44396 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "LINGYUN.Abp.OpenApi.Tests": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} \ No newline at end of file