From b2e12f400c904d77e6ca82f045ce986a4068459a Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sat, 16 May 2020 19:17:08 +0200 Subject: [PATCH] Migrated OnlyLocalRequestsAttribute into netcore --- .../Umbraco.Tests.UnitTests.csproj | 1 + .../OnlyLocalRequestsAttributeTests.cs | 127 ++++++++++++++++++ .../Filters/OnlyLocalRequestsAttribute.cs | 18 +++ .../Extensions/HttpRequestExtensions.cs | 32 ++++- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- .../Filters/OnlyLocalRequestsAttribute.cs | 1 + 6 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttributeTests.cs create mode 100644 src/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttribute.cs diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj index c5732870f3..67d822fb03 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj @@ -20,6 +20,7 @@ + diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttributeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttributeTests.cs new file mode 100644 index 0000000000..f00713f6a4 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttributeTests.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Net; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Routing; +using Moq; +using NUnit.Framework; +using Umbraco.Web.BackOffice.Filters; + +namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters +{ + [TestFixture] + public class OnlyLocalRequestsAttributeTests + { + [Test] + public void Does_Not_Set_Result_When_No_Remote_Address() + { + // Arrange + var context = CreateContext(); + var attribute = new OnlyLocalRequestsAttribute(); + + // Act + attribute.OnActionExecuting(context); + + // Assert + Assert.IsNull(context.Result); + } + + [Test] + public void Does_Not_Set_Result_When_Remote_Address_Is_Null_Ip_Address() + { + // Arrange + var context = CreateContext(remoteIpAddress: "::1"); + var attribute = new OnlyLocalRequestsAttribute(); + + // Act + attribute.OnActionExecuting(context); + + // Assert + Assert.IsNull(context.Result); + } + + [Test] + public void Does_Not_Set_Result_When_Remote_Address_Matches_Local_Address() + { + // Arrange + var context = CreateContext(remoteIpAddress: "100.1.2.3", localIpAddress: "100.1.2.3"); + var attribute = new OnlyLocalRequestsAttribute(); + + // Act + attribute.OnActionExecuting(context); + + // Assert + Assert.IsNull(context.Result); + } + + [Test] + public void Returns_Not_Found_When_Remote_Address_Does_Not_Match_Local_Address() + { + // Arrange + var context = CreateContext(remoteIpAddress: "100.1.2.3", localIpAddress: "100.1.2.2"); + var attribute = new OnlyLocalRequestsAttribute(); + + // Act + attribute.OnActionExecuting(context); + + // Assert + var typedResult = context.Result as NotFoundResult; + Assert.IsNotNull(typedResult); + } + + [Test] + public void Does_Not_Set_Result_When_Remote_Address_Matches_LoopBack_Address() + { + // Arrange + var context = CreateContext(remoteIpAddress: "127.0.0.1", localIpAddress: "::1"); + var attribute = new OnlyLocalRequestsAttribute(); + + // Act + attribute.OnActionExecuting(context); + + // Assert + Assert.IsNull(context.Result); + } + + [Test] + public void Returns_Not_Found_When_Remote_Address_Does_Not_Match_LoopBack_Address() + { + // Arrange + var context = CreateContext(remoteIpAddress: "100.1.2.3", localIpAddress: "::1"); + var attribute = new OnlyLocalRequestsAttribute(); + + // Act + attribute.OnActionExecuting(context); + + // Assert + var typedResult = context.Result as NotFoundResult; + Assert.IsNotNull(typedResult); + } + + private static ActionExecutingContext CreateContext(string remoteIpAddress = null, string localIpAddress = null) + { + var httpContext = new DefaultHttpContext(); + if (!string.IsNullOrEmpty(remoteIpAddress)) + { + httpContext.Connection.RemoteIpAddress = IPAddress.Parse(remoteIpAddress); + } + + if (!string.IsNullOrEmpty(localIpAddress)) + { + httpContext.Connection.LocalIpAddress = IPAddress.Parse(localIpAddress); + } + + var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); + + return new ActionExecutingContext( + actionContext, + new List(), + new Dictionary(), + new Mock().Object); + } + } +} diff --git a/src/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttribute.cs new file mode 100644 index 0000000000..9ee289a39c --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttribute.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Web.Common.Extensions; + +namespace Umbraco.Web.BackOffice.Filters +{ + public class OnlyLocalRequestsAttribute : ActionFilterAttribute + { + public override void OnActionExecuting(ActionExecutingContext context) + { + if (!context.HttpContext.Request.IsLocal()) + { + context.Result = new NotFoundResult(); + } + } + } +} diff --git a/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs b/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs index 9dab445992..c346f0ddc9 100644 --- a/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Http; +using System.Net; +using Microsoft.AspNetCore.Http; namespace Umbraco.Web.Common.Extensions { @@ -8,5 +9,34 @@ namespace Umbraco.Web.Common.Extensions { return request.Headers.TryGetValue("X-UMB-CULTURE", out var values) ? values[0] : null; } + + /// + /// Determines if a request is local. + /// + /// True if request is local + /// + /// Hat-tip: https://stackoverflow.com/a/41242493/489433 + /// + public static bool IsLocal(this HttpRequest request) + { + var connection = request.HttpContext.Connection; + if (connection.RemoteIpAddress.IsSet()) + { + // We have a remote address set up + return connection.LocalIpAddress.IsSet() + // Is local is same as remote, then we are local + ? connection.RemoteIpAddress.Equals(connection.LocalIpAddress) + // else we are remote if the remote IP address is not a loopback address + : IPAddress.IsLoopback(connection.RemoteIpAddress); + } + + return true; + } + + private static bool IsSet(this IPAddress address) + { + const string NullIpAddress = "::1"; + return address != null && address.ToString() != NullIpAddress; + } } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 88833c38de..9fb19447de 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -147,6 +147,7 @@ + @@ -225,7 +226,6 @@ - diff --git a/src/Umbraco.Web/WebApi/Filters/OnlyLocalRequestsAttribute.cs b/src/Umbraco.Web/WebApi/Filters/OnlyLocalRequestsAttribute.cs index 6906519b17..710749b39f 100644 --- a/src/Umbraco.Web/WebApi/Filters/OnlyLocalRequestsAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/OnlyLocalRequestsAttribute.cs @@ -6,6 +6,7 @@ using System.Web.Http.Filters; namespace Umbraco.Web.WebApi.Filters { + // Migrated to .NET Core public class OnlyLocalRequestsAttribute : ActionFilterAttribute { public override void OnActionExecuting(HttpActionContext actionContext)