From ed8f65b74e400ab506044ce28a8dbfbe41ccbff2 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sun, 17 May 2020 08:52:28 +0200 Subject: [PATCH] Migrated AppendUserModifiedHeaderAttribute into netcore --- .../AppendUserModifiedHeaderAttributeTests.cs | 121 ++++++++++++++++++ .../OnlyLocalRequestsAttributeTests.cs | 4 +- .../AppendUserModifiedHeaderAttribute.cs | 80 ++++++++++++ .../AppendUserModifiedHeaderAttribute.cs | 1 + 4 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttributeTests.cs create mode 100644 src/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttribute.cs diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttributeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttributeTests.cs new file mode 100644 index 0000000000..f9c7c72b59 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttributeTests.cs @@ -0,0 +1,121 @@ +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.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("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("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 actionArgument = default) + { + var httpContext = new DefaultHttpContext(); + if (!string.IsNullOrEmpty(headerValue)) + { + httpContext.Response.Headers.Add("X-Umb-User-Modified", headerValue); + } + + var currentUserMock = new Mock(); + currentUserMock + .SetupGet(x => x.Id) + .Returns(100); + + var webSecurityMock = new Mock(); + webSecurityMock + .SetupGet(x => x.CurrentUser) + .Returns(currentUserMock.Object); + + var serviceProviderMock = new Mock(); + serviceProviderMock + .Setup(x => x.GetService(typeof(IWebSecurity))) + .Returns(webSecurityMock.Object); + + httpContext.RequestServices = serviceProviderMock.Object; + + var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); + + var context = new ActionExecutingContext( + actionContext, + new List(), + new Dictionary(), + new Mock().Object); + + if (!EqualityComparer>.Default.Equals(actionArgument, default)) + { + context.ActionArguments.Add(actionArgument); + } + + return context; + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttributeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttributeTests.cs index f00713f6a4..7d1fbdddf1 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttributeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Filters/OnlyLocalRequestsAttributeTests.cs @@ -1,11 +1,9 @@ -using System; -using System.Collections.Generic; +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; diff --git a/src/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttribute.cs new file mode 100644 index 0000000000..b967e51d52 --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Filters/AppendUserModifiedHeaderAttribute.cs @@ -0,0 +1,80 @@ +using System; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Core; +using Umbraco.Web.Security; + +namespace Umbraco.Web.BackOffice.Filters +{ + /// + /// Appends a custom response header to notify the UI that the current user data has been modified + /// + public sealed class AppendUserModifiedHeaderAttribute : ActionFilterAttribute + { + private readonly string _userIdParameter; + + /// + /// An empty constructor which will always set the header. + /// + public AppendUserModifiedHeaderAttribute() + { + } + + /// + /// 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. + /// + /// + 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 webSecurityService = context.HttpContext.RequestServices.GetService(); + var user = webSecurityService.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."); + } + } +} diff --git a/src/Umbraco.Web/WebApi/Filters/AppendUserModifiedHeaderAttribute.cs b/src/Umbraco.Web/WebApi/Filters/AppendUserModifiedHeaderAttribute.cs index 5ac8d886f0..95e1a94787 100644 --- a/src/Umbraco.Web/WebApi/Filters/AppendUserModifiedHeaderAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/AppendUserModifiedHeaderAttribute.cs @@ -9,6 +9,7 @@ namespace Umbraco.Web.WebApi.Filters /// /// Appends a custom response header to notify the UI that the current user data has been modified /// + /// Migrated to NET core public sealed class AppendUserModifiedHeaderAttribute : ActionFilterAttribute { private readonly string _userIdParameter;