From c68d7a33ca62855584bb56ef7ce988a16c6c0cc0 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Mon, 14 Aug 2017 19:57:28 +0200 Subject: [PATCH 1/6] Fix maxchars javascript errors on macro parameter editors --- .../propertyeditors/textarea/textarea.controller.js | 13 ++++++++++++- .../propertyeditors/textbox/textbox.controller.js | 13 ++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js index 2d4e114dcf..76a3ec2913 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textarea/textarea.controller.js @@ -1,4 +1,15 @@ -function textAreaController($rootScope, $scope, $log) { +function textAreaController($scope) { + + // macro parameter editor doesn't contains a config object, + // so we create a new one to hold any properties + if (!$scope.model.config) { + $scope.model.config = {}; + } + + if (!$scope.model.config.maxChars) { + $scope.model.config.maxChars = false; + } + $scope.model.maxlength = false; if($scope.model.config.maxChars) { $scope.model.maxlength = true; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js index 03f7510112..63dab278e4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js @@ -1,4 +1,15 @@ -function textboxController($rootScope, $scope, $log) { +function textboxController($scope) { + + // macro parameter editor doesn't contains a config object, + // so we create a new one to hold any properties + if (!$scope.model.config) { + $scope.model.config = {}; + } + + if (!$scope.model.config.maxChars) { + $scope.model.config.maxChars = false; + } + $scope.model.maxlength = false; if($scope.model.config.maxChars) { $scope.model.maxlength = true; From 6bf0d6e1755309651a84c8b25b71d29e82bfb4f8 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 28 Aug 2017 20:36:56 +0200 Subject: [PATCH 2/6] U4-10363 417 missing token error due to invalid cookie name Manually parse the cookie header instead of using the buggy extension method --- .../Filters/AngularAntiForgeryHelper.cs | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs b/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs index 962183f7ef..0f2a7ac442 100644 --- a/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs +++ b/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Linq; -using System.Net.Http; using System.Net.Http.Headers; using System.Web.Helpers; using Umbraco.Core; @@ -30,8 +28,6 @@ namespace Umbraco.Web.WebApi.Filters /// public const string AngularHeadername = "X-XSRF-TOKEN"; - - /// /// Returns 2 tokens - one for the cookie value and one that angular should set as the header value /// @@ -68,8 +64,8 @@ namespace Umbraco.Web.WebApi.Filters return true; } - internal static bool ValidateHeaders( - KeyValuePair>[] requestHeaders, + internal static bool ValidateHeaders( + KeyValuePair>[] requestHeaders, string cookieToken, out string failedReason) { @@ -86,7 +82,7 @@ namespace Umbraco.Web.WebApi.Filters .Select(z => z.Value) .SelectMany(z => z) .FirstOrDefault(); - + // both header and cookie must be there if (cookieToken == null || headerToken == null) { @@ -111,15 +107,41 @@ namespace Umbraco.Web.WebApi.Filters /// public static bool ValidateHeaders(HttpRequestHeaders requestHeaders, out string failedReason) { - var cookieToken = requestHeaders - .GetCookies() - .Select(c => c[CsrfValidationCookieName]) - .FirstOrDefault(); + var cookieToken = GetCookie(requestHeaders, CsrfValidationCookieName); return ValidateHeaders( requestHeaders.ToDictionary(x => x.Key, x => x.Value).ToArray(), - cookieToken == null ? null : cookieToken.Value, + cookieToken == null ? null : cookieToken, out failedReason); } + + /// + /// Retrieves an individual cookie from the cookies collection + /// Adapted from: https://stackoverflow.com/a/29057304/5018 + /// + /// + /// + /// + private static string GetCookie(HttpRequestHeaders requestHeaders, string cookieName) + { + var cookieRequestHeader = requestHeaders.Where(h => h.Key.Equals("Cookie", StringComparison.InvariantCultureIgnoreCase)).ToArray(); + if (cookieRequestHeader.Any() == false) + return null; + + var cookiesHeader = cookieRequestHeader.FirstOrDefault(); + if (cookiesHeader.Value == null) + return null; + + var cookiesHeaderValue = cookiesHeader.Value.FirstOrDefault(); + if (cookiesHeaderValue == null) + return null; + + var cookieCollection = cookiesHeaderValue.Split(';'); + var cookiePair = cookieCollection.FirstOrDefault(c => c.Trim().StartsWith(cookieName, StringComparison.InvariantCultureIgnoreCase)); + if (cookiePair == null) + return null; + + return cookiePair.Trim().Substring(cookieName.Length + 1); + } } } \ No newline at end of file From d71b8a139fb692261cedffc92396ef0ec1849931 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 29 Aug 2017 10:50:20 +1000 Subject: [PATCH 3/6] Moves GetCookieValue to an extension method, cleans up code, adds unit tests --- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../Web/HttpCookieExtensionsTests.cs | 44 +++++++++++++++++++ src/Umbraco.Web/HttpCookieExtensions.cs | 37 ++++++++++++++++ .../Filters/AngularAntiForgeryHelper.cs | 32 +------------- 4 files changed, 84 insertions(+), 30 deletions(-) create mode 100644 src/Umbraco.Tests/Web/HttpCookieExtensionsTests.cs diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index ebee151364..39ad21f15c 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -212,6 +212,7 @@ + diff --git a/src/Umbraco.Tests/Web/HttpCookieExtensionsTests.cs b/src/Umbraco.Tests/Web/HttpCookieExtensionsTests.cs new file mode 100644 index 0000000000..f13b94a669 --- /dev/null +++ b/src/Umbraco.Tests/Web/HttpCookieExtensionsTests.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Web; + +namespace Umbraco.Tests.Web +{ + [TestFixture] + public class HttpCookieExtensionsTests + { + [TestCase("hello=world;cookies=are fun;", "hello", "world", true)] + [TestCase("HELlo=world;cookies=are fun", "hello", "world", true)] + [TestCase("HELlo= world;cookies=are fun", "hello", "world", true)] + [TestCase("HELlo =world;cookies=are fun", "hello", "world", true)] + [TestCase("hello = world;cookies=are fun;", "hello", "world", true)] + [TestCase("hellos=world;cookies=are fun", "hello", "world", false)] + [TestCase("hello=world;cookies?=are fun?", "hello", "world", true)] + [TestCase("hel?lo=world;cookies=are fun?", "hel?lo", "world", true)] + public void Get_Cookie_Value_From_HttpRequestHeaders(string cookieHeaderVal, string cookieName, string cookieVal, bool matches) + { + var request = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); + var requestHeaders = request.Headers; + requestHeaders.Add("Cookie", cookieHeaderVal); + + var valueFromHeader = requestHeaders.GetCookieValue(cookieName); + + if (matches) + { + Assert.IsNotNull(valueFromHeader); + Assert.AreEqual(cookieVal, valueFromHeader); + } + else + { + Assert.IsNull(valueFromHeader); + } + } + } +} diff --git a/src/Umbraco.Web/HttpCookieExtensions.cs b/src/Umbraco.Web/HttpCookieExtensions.cs index 7aec1466da..d67e1894fd 100644 --- a/src/Umbraco.Web/HttpCookieExtensions.cs +++ b/src/Umbraco.Web/HttpCookieExtensions.cs @@ -16,6 +16,43 @@ namespace Umbraco.Web /// internal static class HttpCookieExtensions { + /// + /// Retrieves an individual cookie from the cookies collection + /// + /// + /// + /// + /// + /// Adapted from: https://stackoverflow.com/a/29057304/5018 because there's an issue with .NET WebApi cookie parsing logic + /// when using requestHeaders.GetCookies() when an invalid cookie name is present. + /// + public static string GetCookieValue(this HttpRequestHeaders requestHeaders, string cookieName) + { + var cookieRequestHeader = requestHeaders + .Where(h => h.Key.Equals("Cookie", StringComparison.InvariantCultureIgnoreCase)) + .ToArray(); + + if (cookieRequestHeader.Length == 0) + return null; + + var cookiesHeader = cookieRequestHeader[0]; + + var cookiesHeaderValue = cookiesHeader.Value.FirstOrDefault(); + if (cookiesHeaderValue == null) + return null; + + var cookieCollection = cookiesHeaderValue.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries); + foreach (var cookieNameValue in cookieCollection) + { + var parts = cookieNameValue.Split(new[] {'='}, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length != 2) continue; + if (parts[0].Trim().InvariantEquals(cookieName)) + return parts[1].Trim(); + } + + return null; + } + /// /// Removes the cookie from the request and the response if it exists /// diff --git a/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs b/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs index 0f2a7ac442..b83b4114a3 100644 --- a/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs +++ b/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs @@ -107,41 +107,13 @@ namespace Umbraco.Web.WebApi.Filters /// public static bool ValidateHeaders(HttpRequestHeaders requestHeaders, out string failedReason) { - var cookieToken = GetCookie(requestHeaders, CsrfValidationCookieName); + var cookieToken = requestHeaders.GetCookieValue(CsrfValidationCookieName); return ValidateHeaders( requestHeaders.ToDictionary(x => x.Key, x => x.Value).ToArray(), cookieToken == null ? null : cookieToken, out failedReason); } - - /// - /// Retrieves an individual cookie from the cookies collection - /// Adapted from: https://stackoverflow.com/a/29057304/5018 - /// - /// - /// - /// - private static string GetCookie(HttpRequestHeaders requestHeaders, string cookieName) - { - var cookieRequestHeader = requestHeaders.Where(h => h.Key.Equals("Cookie", StringComparison.InvariantCultureIgnoreCase)).ToArray(); - if (cookieRequestHeader.Any() == false) - return null; - - var cookiesHeader = cookieRequestHeader.FirstOrDefault(); - if (cookiesHeader.Value == null) - return null; - - var cookiesHeaderValue = cookiesHeader.Value.FirstOrDefault(); - if (cookiesHeaderValue == null) - return null; - - var cookieCollection = cookiesHeaderValue.Split(';'); - var cookiePair = cookieCollection.FirstOrDefault(c => c.Trim().StartsWith(cookieName, StringComparison.InvariantCultureIgnoreCase)); - if (cookiePair == null) - return null; - - return cookiePair.Trim().Substring(cookieName.Length + 1); - } + } } \ No newline at end of file From ca0024623691bfaa67599d0a40f25491b2109a69 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 29 Aug 2017 11:10:28 +1000 Subject: [PATCH 4/6] another update to perform a little better again (cookies are read VERY often) --- src/Umbraco.Web/HttpCookieExtensions.cs | 35 +++++++++++-------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Web/HttpCookieExtensions.cs b/src/Umbraco.Web/HttpCookieExtensions.cs index d67e1894fd..d2133927a0 100644 --- a/src/Umbraco.Web/HttpCookieExtensions.cs +++ b/src/Umbraco.Web/HttpCookieExtensions.cs @@ -28,26 +28,23 @@ namespace Umbraco.Web /// public static string GetCookieValue(this HttpRequestHeaders requestHeaders, string cookieName) { - var cookieRequestHeader = requestHeaders - .Where(h => h.Key.Equals("Cookie", StringComparison.InvariantCultureIgnoreCase)) - .ToArray(); - - if (cookieRequestHeader.Length == 0) - return null; - - var cookiesHeader = cookieRequestHeader[0]; - - var cookiesHeaderValue = cookiesHeader.Value.FirstOrDefault(); - if (cookiesHeaderValue == null) - return null; - - var cookieCollection = cookiesHeaderValue.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries); - foreach (var cookieNameValue in cookieCollection) + foreach (var header in requestHeaders) { - var parts = cookieNameValue.Split(new[] {'='}, StringSplitOptions.RemoveEmptyEntries); - if (parts.Length != 2) continue; - if (parts[0].Trim().InvariantEquals(cookieName)) - return parts[1].Trim(); + if (header.Key.Equals("Cookie", StringComparison.InvariantCultureIgnoreCase) == false) + continue; + + var cookiesHeaderValue = header.Value.FirstOrDefault(); + if (cookiesHeaderValue == null) + return null; + + var cookieCollection = cookiesHeaderValue.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var cookieNameValue in cookieCollection) + { + var parts = cookieNameValue.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length != 2) continue; + if (parts[0].Trim().Equals(cookieName, StringComparison.InvariantCultureIgnoreCase)) + return parts[1].Trim(); + } } return null; From d300bf8d6db3d6ce4485db0d2ba23566648f0f6b Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 29 Aug 2017 11:17:52 +0200 Subject: [PATCH 5/6] U4-10367 417 missing token error due to cookie being overwritten Changes the cookie and header names to be Umbraco specific --- .../src/common/security/securityinterceptor.js | 2 ++ src/Umbraco.Web.UI.Client/src/init.js | 2 +- .../umbraco_client/Application/Extensions.js | 2 +- .../WebApi/Filters/AngularAntiForgeryHelper.cs | 8 ++++---- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js b/src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js index 024e8d5a43..6596ad759f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js +++ b/src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js @@ -103,5 +103,7 @@ angular.module('umbraco.security.interceptor') // We have to add the interceptor to the queue as a string because the interceptor depends upon service instances that are not available in the config block. .config(['$httpProvider', function ($httpProvider) { + $httpProvider.defaults.xsrfHeaderName = 'X-UMB-XSRF-TOKEN'; + $httpProvider.defaults.xsrfCookieName = 'UMB-XSRF-TOKEN'; $httpProvider.responseInterceptors.push('securityInterceptor'); }]); diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js index 016c33015d..62156a8455 100644 --- a/src/Umbraco.Web.UI.Client/src/init.js +++ b/src/Umbraco.Web.UI.Client/src/init.js @@ -7,7 +7,7 @@ app.run(['userService', '$log', '$rootScope', '$location', 'navigationService', // it cannot be static $.ajaxSetup({ beforeSend: function (xhr) { - xhr.setRequestHeader("X-XSRF-TOKEN", $cookies["XSRF-TOKEN"]); + xhr.setRequestHeader("X-UMB-XSRF-TOKEN", $cookies["UMB-XSRF-TOKEN"]); } }); diff --git a/src/Umbraco.Web.UI/umbraco_client/Application/Extensions.js b/src/Umbraco.Web.UI/umbraco_client/Application/Extensions.js index 63870fad08..de12c6194f 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Application/Extensions.js +++ b/src/Umbraco.Web.UI/umbraco_client/Application/Extensions.js @@ -369,7 +369,7 @@ if (parts.length === 2) return parts.pop().split(";").shift(); } - xhr.setRequestHeader("X-XSRF-TOKEN", getCookie("XSRF-TOKEN")); + xhr.setRequestHeader("X-UMB-XSRF-TOKEN", getCookie("UMB-XSRF-TOKEN")); } }); diff --git a/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs b/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs index b83b4114a3..dfeeee536d 100644 --- a/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs +++ b/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs @@ -16,17 +16,17 @@ namespace Umbraco.Web.WebApi.Filters /// /// The cookie name that is used to store the validation value /// - public const string CsrfValidationCookieName = "XSRF-V"; + public const string CsrfValidationCookieName = "UMB-XSRF-V"; /// - /// The cookie name that is set for angular to use to pass in to the header value for "X-XSRF-TOKEN" + /// The cookie name that is set for angular to use to pass in to the header value for "X-UMB-XSRF-TOKEN" /// - public const string AngularCookieName = "XSRF-TOKEN"; + public const string AngularCookieName = "UMB-XSRF-TOKEN"; /// /// The header name that angular uses to pass in the token to validate the cookie /// - public const string AngularHeadername = "X-XSRF-TOKEN"; + public const string AngularHeadername = "X-UMB-XSRF-TOKEN"; /// /// Returns 2 tokens - one for the cookie value and one that angular should set as the header value From 8c998fd4e663a4ff6066d6a55207862e3c83aa4a Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 30 Aug 2017 11:02:58 +1000 Subject: [PATCH 6/6] Creates a virtual ConfigureUmbracoUserManager in UmbracoDefaultOwinStartup so inheritors can just override that if they want to simplify the startup owin code process --- src/Umbraco.Web/UmbracoDefaultOwinStartup.cs | 21 +++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs index c11252d2ca..5773d88f73 100644 --- a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs +++ b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs @@ -37,12 +37,7 @@ namespace Umbraco.Web protected virtual void ConfigureServices(IAppBuilder app) { app.SetUmbracoLoggerFactory(); - - //Configure the Identity user manager for use with Umbraco Back office - // (EXPERT: an overload accepts a custom BackOfficeUserStore implementation) - app.ConfigureUserManagerForUmbracoBackOffice( - ApplicationContext, - Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider().AsUmbracoMembershipProvider()); + ConfigureUmbracoUserManager(app); } /// @@ -62,7 +57,19 @@ namespace Umbraco.Web } /// - /// Raised when the middelware has been configured + /// Configure the Identity user manager for use with Umbraco Back office + /// + /// + protected virtual void ConfigureUmbracoUserManager(IAppBuilder app) + { + // (EXPERT: an overload accepts a custom BackOfficeUserStore implementation) + app.ConfigureUserManagerForUmbracoBackOffice( + ApplicationContext, + Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider().AsUmbracoMembershipProvider()); + } + + /// + /// Raised when the middleware has been configured /// public static event EventHandler MiddlewareConfigured;