From ccd433b2025f19c4e586310c02f8d6128ae5974a Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Mon, 24 Jan 2022 12:26:19 +0100 Subject: [PATCH] Add support for Cors (#714) --- .../Program.cs | 29 ----------- .../Properties/launchSettings.json | 4 +- .../Admin/Settings/SettingsModel.cs | 6 +++ .../Types/CorsPolicyOptions.cs | 36 ++++++++++++++ .../Owin/AspNetCoreSelfHost.NETCore.cs | 48 +++++++++++++++++++ .../Owin/AspNetCoreSelfHost.NETStandard.cs | 3 +- src/WireMock.Net/Owin/AspNetCoreSelfHost.cs | 8 ++++ .../Owin/IWireMockMiddlewareOptions.cs | 4 ++ .../Owin/WireMockMiddlewareOptions.cs | 3 ++ .../Server/WireMockServer.Admin.cs | 13 ++++- src/WireMock.Net/Server/WireMockServer.cs | 2 + .../Settings/IWireMockServerSettings.cs | 7 +++ .../Settings/WireMockServerSettings.cs | 5 ++ .../Settings/WireMockServerSettingsParser.cs | 16 ++++++- .../WireMock.Net.Tests/WireMockServerTests.cs | 24 ++++++++++ 15 files changed, 173 insertions(+), 35 deletions(-) create mode 100644 src/WireMock.Net.Abstractions/Types/CorsPolicyOptions.cs create mode 100644 src/WireMock.Net/Owin/AspNetCoreSelfHost.NETCore.cs diff --git a/examples/WireMock.Net.StandAlone.NETCoreApp/Program.cs b/examples/WireMock.Net.StandAlone.NETCoreApp/Program.cs index 479d19d5..dfcdc81c 100644 --- a/examples/WireMock.Net.StandAlone.NETCoreApp/Program.cs +++ b/examples/WireMock.Net.StandAlone.NETCoreApp/Program.cs @@ -6,8 +6,6 @@ using System.Threading; using log4net; using log4net.Config; using log4net.Repository; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; using WireMock.Server; @@ -35,33 +33,6 @@ namespace WireMock.Net.StandAlone.NETCoreApp settings.Logger.Debug("WireMock.Net server arguments [{0}]", string.Join(", ", args.Select(a => $"'{a}'"))); - /* https://stackoverflow.com/questions/31942037/how-to-enable-cors-in-asp-net-core */ - /* Enable Cors */ - var policyName = "MyPolicy"; - settings.AdditionalServiceRegistration = services => - { - services.AddCors(corsOptions => - corsOptions.AddPolicy(policyName, - corsPolicyBuilder => - { - corsPolicyBuilder - .AllowAnyHeader() - .AllowAnyMethod() - .AllowAnyOrigin(); - })); - - settings.Logger.Debug("Enable Cors"); - }; - - /* Use Cors */ - settings.PreWireMockMiddlewareInit = app => - { - var appBuilder = (IApplicationBuilder)app; - appBuilder.UseCors(policyName); - - settings.Logger.Debug("Use Cors"); - }; - _server = WireMockServer.Start(settings); _server.Given(Request.Create().WithPath("/api/sap") diff --git a/examples/WireMock.Net.StandAlone.NETCoreApp/Properties/launchSettings.json b/examples/WireMock.Net.StandAlone.NETCoreApp/Properties/launchSettings.json index 1adeba28..1195cde4 100644 --- a/examples/WireMock.Net.StandAlone.NETCoreApp/Properties/launchSettings.json +++ b/examples/WireMock.Net.StandAlone.NETCoreApp/Properties/launchSettings.json @@ -1,8 +1,8 @@ { "profiles": { "WireMock.Net.StandAlone.NETCoreApp": { - "commandName": "Project", - "commandLineArgs": "--Urls http://localhost:9091 --WireMockLogger WireMockConsoleLogger" + "commandName": "Project", + "commandLineArgs": "--Urls http://localhost:9091 --CorsPolicyOptions AllowAll --WireMockLogger WireMockConsoleLogger" } } } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Admin/Settings/SettingsModel.cs b/src/WireMock.Net.Abstractions/Admin/Settings/SettingsModel.cs index 636405ad..16736557 100644 --- a/src/WireMock.Net.Abstractions/Admin/Settings/SettingsModel.cs +++ b/src/WireMock.Net.Abstractions/Admin/Settings/SettingsModel.cs @@ -1,5 +1,6 @@ using System.Text.RegularExpressions; using WireMock.Handlers; +using WireMock.Types; namespace WireMock.Admin.Settings { @@ -53,5 +54,10 @@ namespace WireMock.Admin.Settings /// Save unmatched requests to a file using the . (default set to false). /// public bool? SaveUnmatchedRequests { get; set; } + + /// + /// Policies to use when using CORS. By default CORS is disabled. [Optional] + /// + public string CorsPolicyOptions { get; set; } } } \ No newline at end of file diff --git a/src/WireMock.Net.Abstractions/Types/CorsPolicyOptions.cs b/src/WireMock.Net.Abstractions/Types/CorsPolicyOptions.cs new file mode 100644 index 00000000..c682f454 --- /dev/null +++ b/src/WireMock.Net.Abstractions/Types/CorsPolicyOptions.cs @@ -0,0 +1,36 @@ +using System; + +namespace WireMock.Types +{ + /// + /// Policies to use when using CORS. + /// + [Flags] + public enum CorsPolicyOptions + { + /// + /// Cors is disabled + /// + None = 0, + + /// + /// Ensures that the policy allows any header. + /// + AllowAnyHeader = 0b00000001, + + /// + /// Ensures that the policy allows any method. + /// + AllowAnyMethod = 0b00000010, + + /// + /// Ensures that the policy allows any origin. + /// + AllowAnyOrigin = 0b00000100, + + /// + /// Ensures that the policy allows any header, method and origin. + /// + AllowAll = AllowAnyHeader | AllowAnyMethod | AllowAnyOrigin + } +} \ No newline at end of file diff --git a/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETCore.cs b/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETCore.cs new file mode 100644 index 00000000..4076ea2f --- /dev/null +++ b/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETCore.cs @@ -0,0 +1,48 @@ +#if NETCOREAPP3_1_OR_GREATER +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using WireMock.Types; + +namespace WireMock.Owin +{ + internal partial class AspNetCoreSelfHost + { + public void AddCors(IServiceCollection services) + { + if (_wireMockMiddlewareOptions.CorsPolicyOptions > CorsPolicyOptions.None) + { + /* https://stackoverflow.com/questions/31942037/how-to-enable-cors-in-asp-net-core */ + /* Enable Cors */ + services.AddCors(corsOptions => corsOptions + .AddPolicy(CorsPolicyName, + corsPolicyBuilder => + { + if (_wireMockMiddlewareOptions.CorsPolicyOptions.Value.HasFlag(CorsPolicyOptions.AllowAnyHeader)) + { + corsPolicyBuilder.AllowAnyHeader(); + } + + if (_wireMockMiddlewareOptions.CorsPolicyOptions.Value.HasFlag(CorsPolicyOptions.AllowAnyMethod)) + { + corsPolicyBuilder.AllowAnyMethod(); + } + + if (_wireMockMiddlewareOptions.CorsPolicyOptions.Value.HasFlag(CorsPolicyOptions.AllowAnyOrigin)) + { + corsPolicyBuilder.AllowAnyOrigin(); + } + })); + } + } + + public void UseCors(IApplicationBuilder appBuilder) + { + if (_wireMockMiddlewareOptions.CorsPolicyOptions > CorsPolicyOptions.None) + { + /* Use Cors */ + appBuilder.UseCors(CorsPolicyName); + } + } + } +} +#endif \ No newline at end of file diff --git a/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETStandard.cs b/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETStandard.cs index 22653f8c..616514a5 100644 --- a/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETStandard.cs +++ b/src/WireMock.Net/Owin/AspNetCoreSelfHost.NETStandard.cs @@ -1,10 +1,11 @@ -#if USE_ASPNETCORE && !NETSTANDARD1_3 +#if USE_ASPNETCORE && !NETSTANDARD1_3 using System.Collections.Generic; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using WireMock.HttpsCertificate; +using WireMock.Types; namespace WireMock.Owin { diff --git a/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs b/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs index 3992010f..9cfef009 100644 --- a/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs +++ b/src/WireMock.Net/Owin/AspNetCoreSelfHost.cs @@ -18,6 +18,8 @@ namespace WireMock.Owin { internal partial class AspNetCoreSelfHost : IOwinSelfHost { + private const string CorsPolicyName = "WireMock.Net - Policy"; + private readonly CancellationTokenSource _cts = new CancellationTokenSource(); private readonly IWireMockMiddlewareOptions _wireMockMiddlewareOptions; private readonly IWireMockLogger _logger; @@ -68,12 +70,18 @@ namespace WireMock.Owin services.AddSingleton(); services.AddSingleton(); +#if NETCOREAPP3_1_OR_GREATER + AddCors(services); +#endif _wireMockMiddlewareOptions.AdditionalServiceRegistration?.Invoke(services); }) .Configure(appBuilder => { appBuilder.UseMiddleware(); +#if NETCOREAPP3_1_OR_GREATER + UseCors(appBuilder); +#endif _wireMockMiddlewareOptions.PreWireMockMiddlewareInit?.Invoke(appBuilder); appBuilder.UseMiddleware(); diff --git a/src/WireMock.Net/Owin/IWireMockMiddlewareOptions.cs b/src/WireMock.Net/Owin/IWireMockMiddlewareOptions.cs index 429a5286..5691514c 100644 --- a/src/WireMock.Net/Owin/IWireMockMiddlewareOptions.cs +++ b/src/WireMock.Net/Owin/IWireMockMiddlewareOptions.cs @@ -4,6 +4,8 @@ using WireMock.Handlers; using WireMock.Logging; using WireMock.Matchers; using WireMock.Util; +using JetBrains.Annotations; +using WireMock.Types; #if !USE_ASPNETCORE using Owin; #else @@ -39,6 +41,8 @@ namespace WireMock.Owin #if USE_ASPNETCORE Action AdditionalServiceRegistration { get; set; } + + CorsPolicyOptions? CorsPolicyOptions { get; set; } #endif IFileSystemHandler FileSystemHandler { get; set; } diff --git a/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs b/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs index 292175d2..7faf8dc5 100644 --- a/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs +++ b/src/WireMock.Net/Owin/WireMockMiddlewareOptions.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using WireMock.Handlers; using WireMock.Logging; using WireMock.Matchers; +using WireMock.Types; using WireMock.Util; #if !USE_ASPNETCORE using Owin; @@ -39,6 +40,8 @@ namespace WireMock.Owin #if USE_ASPNETCORE public Action AdditionalServiceRegistration { get; set; } + + public CorsPolicyOptions? CorsPolicyOptions { get; set; } #endif /// diff --git a/src/WireMock.Net/Server/WireMockServer.Admin.cs b/src/WireMock.Net/Server/WireMockServer.Admin.cs index a271a592..38341813 100644 --- a/src/WireMock.Net/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net/Server/WireMockServer.Admin.cs @@ -293,7 +293,11 @@ namespace WireMock.Server HandleRequestsSynchronously = _settings.HandleRequestsSynchronously, ThrowExceptionWhenMatcherFails = _settings.ThrowExceptionWhenMatcherFails, UseRegexExtended = _settings.UseRegexExtended, - SaveUnmatchedRequests = _settings.SaveUnmatchedRequests + SaveUnmatchedRequests = _settings.SaveUnmatchedRequests, + +#if USE_ASPNETCORE + CorsPolicyOptions = _settings.CorsPolicyOptions?.ToString() +#endif }; return ToJson(model); @@ -315,6 +319,13 @@ namespace WireMock.Server _settings.UseRegexExtended = settings.UseRegexExtended; _settings.SaveUnmatchedRequests = settings.SaveUnmatchedRequests; +#if USE_ASPNETCORE + if (Enum.TryParse(settings.CorsPolicyOptions, true, out var corsPolicyOptions)) + { + _settings.CorsPolicyOptions = corsPolicyOptions; + } +#endif + return ResponseMessageBuilder.Create("Settings updated"); } #endregion Settings diff --git a/src/WireMock.Net/Server/WireMockServer.cs b/src/WireMock.Net/Server/WireMockServer.cs index d4db11c1..602f31eb 100644 --- a/src/WireMock.Net/Server/WireMockServer.cs +++ b/src/WireMock.Net/Server/WireMockServer.cs @@ -243,6 +243,8 @@ namespace WireMock.Server #if USE_ASPNETCORE _options.AdditionalServiceRegistration = _settings.AdditionalServiceRegistration; + _options.CorsPolicyOptions = _settings.CorsPolicyOptions; + _httpServer = new AspNetCoreSelfHost(_options, urlOptions); #else _httpServer = new OwinSelfHost(_options, urlOptions); diff --git a/src/WireMock.Net/Settings/IWireMockServerSettings.cs b/src/WireMock.Net/Settings/IWireMockServerSettings.cs index 43b66d51..624fc68f 100644 --- a/src/WireMock.Net/Settings/IWireMockServerSettings.cs +++ b/src/WireMock.Net/Settings/IWireMockServerSettings.cs @@ -11,6 +11,7 @@ using WireMock.Matchers; using WireMock.RegularExpressions; #if USE_ASPNETCORE using Microsoft.Extensions.DependencyInjection; +using WireMock.Types; #endif namespace WireMock.Settings @@ -135,6 +136,12 @@ namespace WireMock.Settings /// [PublicAPI] Action AdditionalServiceRegistration { get; set; } + + /// + /// Policies to use when using CORS. By default CORS is disabled. [Optional] + /// + [PublicAPI] + CorsPolicyOptions? CorsPolicyOptions { get; set; } #endif /// diff --git a/src/WireMock.Net/Settings/WireMockServerSettings.cs b/src/WireMock.Net/Settings/WireMockServerSettings.cs index 6bb1fa28..7bf35c93 100644 --- a/src/WireMock.Net/Settings/WireMockServerSettings.cs +++ b/src/WireMock.Net/Settings/WireMockServerSettings.cs @@ -7,6 +7,7 @@ using WireMock.Admin.Mappings; using WireMock.Handlers; using WireMock.Logging; using WireMock.Matchers; +using WireMock.Types; #if USE_ASPNETCORE using Microsoft.Extensions.DependencyInjection; #endif @@ -97,6 +98,10 @@ namespace WireMock.Settings [PublicAPI] [JsonIgnore] public Action AdditionalServiceRegistration { get; set; } + + /// + [PublicAPI] + public CorsPolicyOptions? CorsPolicyOptions { get; set; } #endif /// diff --git a/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs b/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs index 24a38786..d331144d 100644 --- a/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs +++ b/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs @@ -1,6 +1,9 @@ +using System; +using System.Linq; using JetBrains.Annotations; -using WireMock.Logging; using Stef.Validation; +using WireMock.Logging; +using WireMock.Types; namespace WireMock.Settings { @@ -50,9 +53,18 @@ namespace WireMock.Settings HandleRequestsSynchronously = parser.GetBoolValue("HandleRequestsSynchronously"), ThrowExceptionWhenMatcherFails = parser.GetBoolValue("ThrowExceptionWhenMatcherFails"), UseRegexExtended = parser.GetBoolValue(nameof(IWireMockServerSettings.UseRegexExtended), true), - SaveUnmatchedRequests = parser.GetBoolValue(nameof(IWireMockServerSettings.SaveUnmatchedRequests)) + SaveUnmatchedRequests = parser.GetBoolValue(nameof(IWireMockServerSettings.SaveUnmatchedRequests)), }; +#if USE_ASPNETCORE + settings.CorsPolicyOptions = parser.GetValue( + nameof(IWireMockServerSettings.CorsPolicyOptions), values => + { + var value = string.Join(string.Empty, values); + return Enum.TryParse(value, true, out var corsPolicyOptions) ? corsPolicyOptions : CorsPolicyOptions.None; + }); +#endif + if (logger != null) { settings.Logger = logger; diff --git a/test/WireMock.Net.Tests/WireMockServerTests.cs b/test/WireMock.Net.Tests/WireMockServerTests.cs index 380dd6c8..d725478d 100644 --- a/test/WireMock.Net.Tests/WireMockServerTests.cs +++ b/test/WireMock.Net.Tests/WireMockServerTests.cs @@ -17,6 +17,7 @@ using WireMock.RequestBuilders; using WireMock.ResponseBuilders; using WireMock.Server; using WireMock.Settings; +using WireMock.Types; using WireMock.Util; using Xunit; @@ -97,6 +98,29 @@ namespace WireMock.Net.Tests server.Stop(); } +#if NETCOREAPP3_1_OR_GREATER + [Fact] + public async Task WireMockServer_WithCorsPolicyOptions_Should_Work_Correct() + { + // Arrange + var settings = new WireMockServerSettings + { + CorsPolicyOptions = CorsPolicyOptions.AllowAll + }; + var server = WireMockServer.Start(settings); + + server.Given(Request.Create().WithPath("/*")).RespondWith(Response.Create().WithBody("x")); + + // Act + var response = await new HttpClient().GetStringAsync("http://localhost:" + server.Ports[0] + "/foo").ConfigureAwait(false); + + // Asser. + response.Should().Be("x"); + + server.Stop(); + } +#endif + [Fact] public async Task WireMockServer_Should_delay_responses_for_a_given_route() {