From ff36c1ee6f2656b91038d5078b17c9ddec774627 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Tue, 4 Mar 2025 17:58:38 +0100 Subject: [PATCH] Merge commit from fork --- README.md | 5 +++ .../Types/CustomHandlebarHelpers.cs | 16 ++++++++ .../Settings/WireMockServerSettings.cs | 9 ++++ .../Settings/WireMockServerSettingsParser.cs | 1 + .../Transformers/Handlebars/FileHelpers.cs | 8 ++-- .../Handlebars/HandlebarsContextFactory.cs | 2 +- .../Handlebars/WireMockHandlebarsHelpers.cs | 24 +++++------ .../ResponseWithHandlebarsFileTests.cs | 41 +++++++++++++++++-- 8 files changed, 86 insertions(+), 20 deletions(-) create mode 100644 src/WireMock.Net.Abstractions/Types/CustomHandlebarHelpers.cs diff --git a/README.md b/README.md index 4129b7ca..11a148e0 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,11 @@ A breaking change is introduced which is related to System.Linq.Dynamic.Core Dyn - The `LinqMatcher` is not allowed. - The [Handlebars.Net.Helpers.DynamicLinq](https://www.nuget.org/packages/Handlebars.Net.Helpers.DynamicLinq) package is not included anymore. +### 1.8.0 +A breaking change is introduced which is related to the usage of the custom Handlebars.Net `File`-helper. +By default this is not allowed anymore because of security reasons (insecure server-side template injection). +To still enable this feature, you need to set the `AllowedCustomHandlebarHelpers` property to `File` in the `WireMockServerSettings` class. + --- ## :memo: Development diff --git a/src/WireMock.Net.Abstractions/Types/CustomHandlebarHelpers.cs b/src/WireMock.Net.Abstractions/Types/CustomHandlebarHelpers.cs new file mode 100644 index 00000000..4f45753c --- /dev/null +++ b/src/WireMock.Net.Abstractions/Types/CustomHandlebarHelpers.cs @@ -0,0 +1,16 @@ +using System; + +namespace WireMock.Types; + +/// +/// A enum defining the supported Handlebar helpers. +/// +[Flags] +public enum CustomHandlebarHelpers +{ + None = 0, + + File = 1, + + All = File +} \ No newline at end of file diff --git a/src/WireMock.Net/Settings/WireMockServerSettings.cs b/src/WireMock.Net/Settings/WireMockServerSettings.cs index bf8479b6..2eb23a41 100644 --- a/src/WireMock.Net/Settings/WireMockServerSettings.cs +++ b/src/WireMock.Net/Settings/WireMockServerSettings.cs @@ -329,4 +329,13 @@ public class WireMockServerSettings /// [PublicAPI] public string? AdminPath { get; set; } + + /// + /// Defines the allowed custom HandlebarHelpers which can be used. Possible values are: + /// - (Default) + /// - + /// - + /// + [PublicAPI] + public CustomHandlebarHelpers AllowedCustomHandlebarHelpers { get; set; } = CustomHandlebarHelpers.None; } \ No newline at end of file diff --git a/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs b/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs index 03d44262..23acf5d6 100644 --- a/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs +++ b/src/WireMock.Net/Settings/WireMockServerSettingsParser.cs @@ -50,6 +50,7 @@ public static class WireMockServerSettingsParser AdminPath = parser.GetStringValue(nameof(WireMockServerSettings.AdminPath), "/__admin"), AllowBodyForAllHttpMethods = parser.GetBoolValue(nameof(WireMockServerSettings.AllowBodyForAllHttpMethods)), AllowCSharpCodeMatcher = parser.GetBoolValue(nameof(WireMockServerSettings.AllowCSharpCodeMatcher)), + AllowedCustomHandlebarHelpers = parser.GetEnumValue(nameof(WireMockServerSettings.AllowedCustomHandlebarHelpers), CustomHandlebarHelpers.None), AllowOnlyDefinedHttpStatusCodeInResponse = parser.GetBoolValue(nameof(WireMockServerSettings.AllowOnlyDefinedHttpStatusCodeInResponse)), AllowPartialMapping = parser.GetBoolValue(nameof(WireMockServerSettings.AllowPartialMapping)), Culture = parser.GetValue(nameof(WireMockServerSettings.Culture), strings => CultureInfoUtils.Parse(strings.FirstOrDefault()), CultureInfo.CurrentCulture), diff --git a/src/WireMock.Net/Transformers/Handlebars/FileHelpers.cs b/src/WireMock.Net/Transformers/Handlebars/FileHelpers.cs index f70e27fc..df135e59 100644 --- a/src/WireMock.Net/Transformers/Handlebars/FileHelpers.cs +++ b/src/WireMock.Net/Transformers/Handlebars/FileHelpers.cs @@ -11,6 +11,8 @@ namespace WireMock.Transformers.Handlebars; internal class FileHelpers : BaseHelpers, IHelpers { + internal const string Name = "File"; + private readonly IFileSystemHandler _fileSystemHandler; public FileHelpers(IHandlebars context, IFileSystemHandler fileSystemHandler) : base(context) @@ -18,12 +20,12 @@ internal class FileHelpers : BaseHelpers, IHelpers _fileSystemHandler = Guard.NotNull(fileSystemHandler); } - [HandlebarsWriter(WriterType.String, usage: HelperUsage.Both, passContext: true, name: "File")] + [HandlebarsWriter(WriterType.String, usage: HelperUsage.Both, passContext: true, name: Name)] public string Read(Context context, string path) { var templateFunc = Context.Compile(path); - var transformed = templateFunc(context.Value); - return _fileSystemHandler.ReadResponseBodyAsString(transformed); + var transformedPath = templateFunc(context.Value); + return _fileSystemHandler.ReadResponseBodyAsString(transformedPath); } public Category Category => Category.Custom; diff --git a/src/WireMock.Net/Transformers/Handlebars/HandlebarsContextFactory.cs b/src/WireMock.Net/Transformers/Handlebars/HandlebarsContextFactory.cs index 1c911135..05a9889b 100644 --- a/src/WireMock.Net/Transformers/Handlebars/HandlebarsContextFactory.cs +++ b/src/WireMock.Net/Transformers/Handlebars/HandlebarsContextFactory.cs @@ -23,7 +23,7 @@ internal class HandlebarsContextFactory : ITransformerContextFactory }; var handlebars = HandlebarsDotNet.Handlebars.Create(config); - WireMockHandlebarsHelpers.Register(handlebars, _settings.FileSystemHandler); + WireMockHandlebarsHelpers.Register(handlebars, _settings); _settings.HandlebarsRegistrationCallback?.Invoke(handlebars, _settings.FileSystemHandler); diff --git a/src/WireMock.Net/Transformers/Handlebars/WireMockHandlebarsHelpers.cs b/src/WireMock.Net/Transformers/Handlebars/WireMockHandlebarsHelpers.cs index 37362bcb..96331c77 100644 --- a/src/WireMock.Net/Transformers/Handlebars/WireMockHandlebarsHelpers.cs +++ b/src/WireMock.Net/Transformers/Handlebars/WireMockHandlebarsHelpers.cs @@ -2,21 +2,20 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; -using System.Reflection; using HandlebarsDotNet; using HandlebarsDotNet.Helpers; using HandlebarsDotNet.Helpers.Helpers; -using WireMock.Handlers; +using WireMock.Settings; +using WireMock.Types; namespace WireMock.Transformers.Handlebars; internal static class WireMockHandlebarsHelpers { - public static void Register(IHandlebars handlebarsContext, IFileSystemHandler fileSystemHandler) + internal static void Register(IHandlebars handlebarsContext, WireMockServerSettings settings) { - // Register https://github.com/StefH/Handlebars.Net.Helpers + // Register https://github.com/Handlebars.Net/Handlebars.Net.Helpers HandlebarsHelpers.Register(handlebarsContext, o => { var paths = new List @@ -33,17 +32,18 @@ internal static class WireMockHandlebarsHelpers customHelperPaths.Add(path!); } } - Add(Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location), paths); - Add(Path.GetDirectoryName(Assembly.GetCallingAssembly().Location), paths); - Add(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), paths); - Add(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule?.FileName), paths); + Add(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly()?.Location), paths); + Add(Path.GetDirectoryName(System.Reflection.Assembly.GetCallingAssembly().Location), paths); + Add(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), paths); + Add(Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName), paths); #endif o.CustomHelperPaths = paths; - o.CustomHelpers = new Dictionary + o.CustomHelpers = new Dictionary(); + if (settings.AllowedCustomHandlebarHelpers.HasFlag(CustomHandlebarHelpers.File)) { - { "File", new FileHelpers(handlebarsContext, fileSystemHandler) } - }; + o.CustomHelpers.Add(FileHelpers.Name, new FileHelpers(handlebarsContext, settings.FileSystemHandler)); + } }); } diff --git a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsFileTests.cs b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsFileTests.cs index 8b328043..952a6ecc 100644 --- a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsFileTests.cs +++ b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsFileTests.cs @@ -1,7 +1,10 @@ // Copyright © WireMock.Net +using System; using System.Threading.Tasks; +using FluentAssertions; using HandlebarsDotNet; +using HandlebarsDotNet.Helpers; using Moq; using Newtonsoft.Json.Linq; using NFluent; @@ -9,15 +12,17 @@ using WireMock.Handlers; using WireMock.Models; using WireMock.ResponseBuilders; using WireMock.Settings; +using WireMock.Transformers.Handlebars; +using WireMock.Types; using Xunit; namespace WireMock.Net.Tests.ResponseBuilders; public class ResponseWithHandlebarsFileTests { - private readonly WireMockServerSettings _settings = new(); private const string ClientIp = "::1"; + private readonly WireMockServerSettings _settings; private readonly Mock _mappingMock; private readonly Mock _filesystemHandlerMock; @@ -28,7 +33,11 @@ public class ResponseWithHandlebarsFileTests _filesystemHandlerMock = new Mock(MockBehavior.Strict); _filesystemHandlerMock.Setup(fs => fs.ReadResponseBodyAsString(It.IsAny())).Returns("abc"); - _settings.FileSystemHandler = _filesystemHandlerMock.Object; + _settings = new() + { + AllowedCustomHandlebarHelpers = CustomHandlebarHelpers.File, + FileSystemHandler = _filesystemHandlerMock.Object + }; } [Fact] @@ -48,7 +57,7 @@ public class ResponseWithHandlebarsFileTests var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, request, _settings).ConfigureAwait(false); // Assert - JObject j = JObject.FromObject(response.Message.BodyData.BodyAsJson); + var j = JObject.FromObject(response.Message.BodyData.BodyAsJson); Check.That(j["Data"].Value()).Equals("abc"); // Verify @@ -73,7 +82,7 @@ public class ResponseWithHandlebarsFileTests var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, request, _settings).ConfigureAwait(false); // Assert - JObject j = JObject.FromObject(response.Message.BodyData.BodyAsJson); + var j = JObject.FromObject(response.Message.BodyData.BodyAsJson); Check.That(j["Data"].Value()).Equals("abc"); // Verify @@ -101,4 +110,28 @@ public class ResponseWithHandlebarsFileTests _filesystemHandlerMock.Verify(fs => fs.ReadResponseBodyAsString(It.IsAny()), Times.Never); _filesystemHandlerMock.VerifyNoOtherCalls(); } + + [Fact] + public void Response_ProvideResponseAsync_Handlebars_File_NotAllowed_Throws_HandlebarsRuntimeException() + { + // Assign + var settings = new WireMockServerSettings + { + AllowedCustomHandlebarHelpers = CustomHandlebarHelpers.None, + FileSystemHandler = _filesystemHandlerMock.Object + }; + var request = new RequestMessage(new UrlDetails("http://localhost:1234?id=x"), "GET", ClientIp); + + var responseBuilder = Response.Create() + .WithBody("{{File \"{{request.query.id}}.json\"}}") + .WithTransformer(); + + // Act + Func action = () => responseBuilder.ProvideResponseAsync(_mappingMock.Object, request, settings); + + action.Should().ThrowAsync(); + + // Verify + _filesystemHandlerMock.VerifyNoOtherCalls(); + } } \ No newline at end of file