Merge pull request #8122 from AndyButland/feature/7799-netcore-web-api-component-migration
Netcore: Implemented various Web API components
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Umbraco.Tests.Common\Umbraco.Tests.Common.csproj" />
|
||||
<ProjectReference Include="..\Umbraco.Web.BackOffice\Umbraco.Web.BackOffice.csproj" />
|
||||
<ProjectReference Include="..\Umbraco.Web.Common\Umbraco.Web.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.BackOffice.Filters;
|
||||
using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Web.BackOffice.Filters
|
||||
{
|
||||
[TestFixture]
|
||||
public class AppendUserModifiedHeaderAttributeTests
|
||||
{
|
||||
[Test]
|
||||
public void Appends_Header_When_No_User_Parameter_Provider()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext();
|
||||
var attribute = new AppendUserModifiedHeaderAttribute();
|
||||
|
||||
// Act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// Assert
|
||||
context.HttpContext.Response.Headers.TryGetValue("X-Umb-User-Modified", out var headerValue);
|
||||
Assert.AreEqual("1", headerValue[0]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Does_Not_Append_Header_If_Already_Exists()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(headerValue: "0");
|
||||
var attribute = new AppendUserModifiedHeaderAttribute();
|
||||
|
||||
// Act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// Assert
|
||||
context.HttpContext.Response.Headers.TryGetValue("X-Umb-User-Modified", out var headerValue);
|
||||
Assert.AreEqual("0", headerValue[0]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Does_Not_Append_Header_When_User_Id_Parameter_Provided_And_Does_Not_Match_Current_User()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(actionArgument: new KeyValuePair<string, object>("UserId", 99));
|
||||
var userIdParameter = "UserId";
|
||||
var attribute = new AppendUserModifiedHeaderAttribute(userIdParameter);
|
||||
|
||||
// Act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(context.HttpContext.Response.Headers.ContainsKey("X-Umb-User-Modified"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Appends_Header_When_User_Id_Parameter_Provided_And_Does_Not_Match_Current_User()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(actionArgument: new KeyValuePair<string, object>("UserId", 100));
|
||||
var userIdParameter = "UserId";
|
||||
var attribute = new AppendUserModifiedHeaderAttribute(userIdParameter);
|
||||
|
||||
// Act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// Assert
|
||||
context.HttpContext.Response.Headers.TryGetValue("X-Umb-User-Modified", out var headerValue);
|
||||
Assert.AreEqual("1", headerValue[0]);
|
||||
}
|
||||
|
||||
private static ActionExecutingContext CreateContext(string headerValue = null, KeyValuePair<string, object> actionArgument = default)
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
if (!string.IsNullOrEmpty(headerValue))
|
||||
{
|
||||
httpContext.Response.Headers.Add("X-Umb-User-Modified", headerValue);
|
||||
}
|
||||
|
||||
var currentUserMock = new Mock<IUser>();
|
||||
currentUserMock
|
||||
.SetupGet(x => x.Id)
|
||||
.Returns(100);
|
||||
|
||||
var webSecurityMock = new Mock<IWebSecurity>();
|
||||
webSecurityMock
|
||||
.SetupGet(x => x.CurrentUser)
|
||||
.Returns(currentUserMock.Object);
|
||||
|
||||
var umbracoContextMock = new Mock<IUmbracoContext>();
|
||||
umbracoContextMock
|
||||
.SetupGet(x => x.Security)
|
||||
.Returns(webSecurityMock.Object);
|
||||
|
||||
var umbracoContextAccessorMock = new Mock<IUmbracoContextAccessor>();
|
||||
umbracoContextAccessorMock
|
||||
.SetupGet(x => x.UmbracoContext)
|
||||
.Returns(umbracoContextMock.Object);
|
||||
|
||||
var serviceProviderMock = new Mock<IServiceProvider>();
|
||||
serviceProviderMock
|
||||
.Setup(x => x.GetService(typeof(IUmbracoContextAccessor)))
|
||||
.Returns(umbracoContextAccessorMock.Object);
|
||||
|
||||
httpContext.RequestServices = serviceProviderMock.Object;
|
||||
|
||||
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||
|
||||
var context = new ActionExecutingContext(
|
||||
actionContext,
|
||||
new List<IFilterMetadata>(),
|
||||
new Dictionary<string, object>(),
|
||||
new Mock<Controller>().Object);
|
||||
|
||||
if (!EqualityComparer<KeyValuePair<string, object>>.Default.Equals(actionArgument, default))
|
||||
{
|
||||
context.ActionArguments.Add(actionArgument);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
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.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<IFilterMetadata>(),
|
||||
new Dictionary<string, object>(),
|
||||
new Mock<Controller>().Object);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
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.ModelBinding;
|
||||
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 ValidationFilterAttributeTests
|
||||
{
|
||||
[Test]
|
||||
public void Does_Not_Set_Result_When_No_Errors_In_Model_State()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext();
|
||||
var attribute = new ValidationFilterAttribute();
|
||||
|
||||
// Act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.IsNull(context.Result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Returns_Bad_Request_When_Errors_In_Model_State()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(withError: true);
|
||||
var attribute = new ValidationFilterAttribute();
|
||||
|
||||
// Act
|
||||
attribute.OnActionExecuting(context);
|
||||
|
||||
// Assert
|
||||
var typedResult = context.Result as BadRequestObjectResult;
|
||||
Assert.IsNotNull(typedResult);
|
||||
}
|
||||
|
||||
private static ActionExecutingContext CreateContext(bool withError = false)
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
|
||||
var modelState = new ModelStateDictionary();
|
||||
if (withError)
|
||||
{
|
||||
modelState.AddModelError(string.Empty, "Error");
|
||||
}
|
||||
|
||||
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor(), modelState);
|
||||
|
||||
return new ActionExecutingContext(
|
||||
actionContext,
|
||||
new List<IFilterMetadata>(),
|
||||
new Dictionary<string, object>(),
|
||||
new Mock<Controller>().Object);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
@@ -12,7 +11,7 @@ using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Web.Common.ModelBinders;
|
||||
using Umbraco.Web.Models;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders
|
||||
{
|
||||
[TestFixture]
|
||||
public class ContentModelBinderTests
|
||||
@@ -92,9 +91,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common
|
||||
var httpContext = new DefaultHttpContext();
|
||||
var routeData = new RouteData();
|
||||
if (withUmbracoDataToken)
|
||||
{
|
||||
routeData.DataTokens.Add(Constants.Web.UmbracoDataToken, source);
|
||||
}
|
||||
|
||||
var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor());
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
@@ -0,0 +1,90 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Web.Common.ModelBinders;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.ModelBinders
|
||||
{
|
||||
[TestFixture]
|
||||
public class HttpQueryStringModelBinderTests
|
||||
{
|
||||
[Test]
|
||||
public void Binds_Query_To_FormCollection()
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = CreateBindingContext("?foo=bar&baz=buzz");
|
||||
var binder = new HttpQueryStringModelBinder();
|
||||
|
||||
// Act
|
||||
binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(bindingContext.Result.IsModelSet);
|
||||
|
||||
var typedModel = bindingContext.Result.Model as FormCollection;
|
||||
Assert.IsNotNull(typedModel);
|
||||
Assert.AreEqual(typedModel["foo"], "bar");
|
||||
Assert.AreEqual(typedModel["baz"], "buzz");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Sets_Culture_Form_Value_From_Query_If_Provided()
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = CreateBindingContext("?foo=bar&baz=buzz&culture=en-gb");
|
||||
var binder = new HttpQueryStringModelBinder();
|
||||
|
||||
// Act
|
||||
binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(bindingContext.Result.IsModelSet);
|
||||
|
||||
var typedModel = bindingContext.Result.Model as FormCollection;
|
||||
Assert.IsNotNull(typedModel);
|
||||
Assert.AreEqual(typedModel["culture"], "en-gb");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Sets_Culture_Form_Value_From_Header_If_Not_Provided_In_Query()
|
||||
{
|
||||
// Arrange
|
||||
var bindingContext = CreateBindingContext("?foo=bar&baz=buzz");
|
||||
var binder = new HttpQueryStringModelBinder();
|
||||
|
||||
// Act
|
||||
binder.BindModelAsync(bindingContext);
|
||||
|
||||
// Assert
|
||||
Assert.True(bindingContext.Result.IsModelSet);
|
||||
|
||||
var typedModel = bindingContext.Result.Model as FormCollection;
|
||||
Assert.IsNotNull(typedModel);
|
||||
Assert.AreEqual(typedModel["culture"], "en-gb");
|
||||
}
|
||||
|
||||
private ModelBindingContext CreateBindingContext(string querystring)
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Request.QueryString = new QueryString(querystring);
|
||||
httpContext.Request.Headers.Add("X-UMB-CULTURE", new StringValues("en-gb"));
|
||||
var routeData = new RouteData();
|
||||
var actionContext = new ActionContext(httpContext, routeData, new ActionDescriptor());
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
var routeValueDictionary = new RouteValueDictionary();
|
||||
var valueProvider = new RouteValueProvider(BindingSource.Path, routeValueDictionary);
|
||||
var modelType = typeof(FormCollection);
|
||||
return new DefaultModelBindingContext
|
||||
{
|
||||
ActionContext = actionContext,
|
||||
ModelMetadata = metadataProvider.GetMetadataForType(modelType),
|
||||
ModelName = modelType.Name,
|
||||
ValueProvider = valueProvider,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Appends a custom response header to notify the UI that the current user data has been modified
|
||||
/// </summary>
|
||||
public sealed class AppendUserModifiedHeaderAttribute : ActionFilterAttribute
|
||||
{
|
||||
private readonly string _userIdParameter;
|
||||
|
||||
/// <summary>
|
||||
/// An empty constructor which will always set the header.
|
||||
/// </summary>
|
||||
public AppendUserModifiedHeaderAttribute()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A constructor specifying the action parameter name containing the user id to match against the
|
||||
/// current user and if they match the header will be appended.
|
||||
/// </summary>
|
||||
/// <param name="userIdParameter"></param>
|
||||
public AppendUserModifiedHeaderAttribute(string userIdParameter)
|
||||
{
|
||||
_userIdParameter = userIdParameter ?? throw new ArgumentNullException(nameof(userIdParameter));
|
||||
}
|
||||
|
||||
public override void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
if (_userIdParameter.IsNullOrWhiteSpace())
|
||||
{
|
||||
AppendHeader(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!context.ActionArguments.ContainsKey(_userIdParameter))
|
||||
{
|
||||
throw new InvalidOperationException($"No argument found for the current action with the name: {_userIdParameter}");
|
||||
}
|
||||
|
||||
var umbracoContextAccessor = context.HttpContext.RequestServices.GetService<IUmbracoContextAccessor>();
|
||||
var user = umbracoContextAccessor.UmbracoContext.Security.CurrentUser;
|
||||
if (user == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var userId = GetUserIdFromParameter(context.ActionArguments[_userIdParameter]);
|
||||
if (userId == user.Id)
|
||||
{
|
||||
AppendHeader(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void AppendHeader(ActionExecutingContext context)
|
||||
{
|
||||
const string HeaderName = "X-Umb-User-Modified";
|
||||
if (context.HttpContext.Response.Headers.ContainsKey(HeaderName) == false)
|
||||
{
|
||||
context.HttpContext.Response.Headers.Add(HeaderName, "1");
|
||||
}
|
||||
}
|
||||
|
||||
private int GetUserIdFromParameter(object parameterValue)
|
||||
{
|
||||
if (parameterValue is int)
|
||||
{
|
||||
return (int)parameterValue;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"The id type: {parameterValue.GetType()} is not a supported id.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// An action filter used to do basic validation against the model and return a result
|
||||
/// straight away if it fails.
|
||||
/// </summary>
|
||||
internal sealed class ValidationFilterAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
var modelState = context.ModelState;
|
||||
if (!modelState.IsValid)
|
||||
{
|
||||
context.Result = new BadRequestObjectResult(modelState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,12 @@
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>Umbraco.Tests.UnitTests</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Umbraco.Configuration\Umbraco.Configuration.csproj" />
|
||||
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
|
||||
|
||||
42
src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs
Normal file
42
src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Umbraco.Web.Common.Extensions
|
||||
{
|
||||
public static class HttpRequestExtensions
|
||||
{
|
||||
internal static string ClientCulture(this HttpRequest request)
|
||||
{
|
||||
return request.Headers.TryGetValue("X-UMB-CULTURE", out var values) ? values[0] : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a request is local.
|
||||
/// </summary>
|
||||
/// <returns>True if request is local</returns>
|
||||
/// <remarks>
|
||||
/// Hat-tip: https://stackoverflow.com/a/41242493/489433
|
||||
/// </remarks>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Common.Extensions;
|
||||
|
||||
namespace Umbraco.Web.Common.ModelBinders
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows an Action to execute with an arbitrary number of QueryStrings
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Just like you can POST an arbitrary number of parameters to an Action, you can't GET an arbitrary number
|
||||
/// but this will allow you to do it.
|
||||
/// </remarks>
|
||||
public sealed class HttpQueryStringModelBinder : IModelBinder
|
||||
{
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
var queryStrings = GetQueryAsDictionary(bindingContext.ActionContext.HttpContext.Request.Query);
|
||||
var queryStringKeys = queryStrings.Select(kvp => kvp.Key).ToArray();
|
||||
if (queryStringKeys.InvariantContains("culture") == false)
|
||||
{
|
||||
queryStrings.Add("culture", new StringValues(bindingContext.ActionContext.HttpContext.Request.ClientCulture()));
|
||||
}
|
||||
|
||||
var formData = new FormCollection(queryStrings);
|
||||
bindingContext.Result = ModelBindingResult.Success(formData);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Dictionary<string, StringValues> GetQueryAsDictionary(IQueryCollection query)
|
||||
{
|
||||
var result = new Dictionary<string, StringValues>();
|
||||
if (query == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var item in query)
|
||||
{
|
||||
result.Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,6 +147,7 @@
|
||||
<Compile Include="Composing\CompositionExtensions\Installer.cs" />
|
||||
<Compile Include="Composing\LightInject\LightInjectContainer.cs" />
|
||||
<Compile Include="Security\IdentityFactoryMiddleware.cs" />
|
||||
<Compile Include="WebApi\Filters\OnlyLocalRequestsAttribute.cs" />
|
||||
<Compile Include="WebAssets\CDF\ClientDependencyRuntimeMinifier.cs" />
|
||||
<Compile Include="Models\NoNodesViewModel.cs" />
|
||||
<Compile Include="Mvc\RenderNoContentController.cs" />
|
||||
@@ -225,7 +226,6 @@
|
||||
<Compile Include="UmbracoDbProviderFactoryCreator.cs" />
|
||||
<Compile Include="ViewDataExtensions.cs" />
|
||||
<Compile Include="WebApi\Filters\AdminUsersAuthorizeAttribute.cs" />
|
||||
<Compile Include="WebApi\Filters\OnlyLocalRequestsAttribute.cs" />
|
||||
<Compile Include="Runtime\WebInitialComposer.cs" />
|
||||
<Compile Include="Security\ActiveDirectoryBackOfficeUserPasswordChecker.cs" />
|
||||
<Compile Include="Security\BackOfficeUserPasswordCheckerResult.cs" />
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
/// <summary>
|
||||
/// Appends a custom response header to notify the UI that the current user data has been modified
|
||||
/// </summary>
|
||||
/// Migrated to NET core
|
||||
public sealed class AppendUserModifiedHeaderAttribute : ActionFilterAttribute
|
||||
{
|
||||
private readonly string _userIdParameter;
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
/// Just like you can POST an arbitrary number of parameters to an Action, you can't GET an arbitrary number
|
||||
/// but this will allow you to do it
|
||||
/// </remarks>
|
||||
/// Migrated to .NET core
|
||||
public sealed class HttpQueryStringModelBinder : IModelBinder
|
||||
{
|
||||
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
/// An action filter used to do basic validation against the model and return a result
|
||||
/// straight away if it fails.
|
||||
/// </summary>
|
||||
/// Migrated to .NET core
|
||||
internal sealed class ValidationFilterAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnActionExecuting(HttpActionContext actionContext)
|
||||
|
||||
Reference in New Issue
Block a user