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)