diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 4874f8a74a..6e638eef20 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -250,6 +250,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.UI.Client/src/common/security/securityinterceptor.js b/src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js index 6f5712a78d..b283a1fec8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js +++ b/src/Umbraco.Web.UI.Client/src/common/security/securityinterceptor.js @@ -120,6 +120,8 @@ 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'); $httpProvider.interceptors.push('umbracoRequestInterceptor'); }]); diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js index 5c70ca6a90..631e38657b 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', 'queryStrings', 'navi // 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"]); if (queryStrings.getParams().umbDebug === "true" || queryStrings.getParams().umbdebug === "true") { xhr.setRequestHeader("X-UMB-DEBUG", "true"); } 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 05d1e5fbfa..60d762738f 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 && $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 c0f084f0c3..3bb909c717 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 && $scope.model.config.maxChars) { $scope.model.maxlength = true; diff --git a/src/Umbraco.Web.UI/umbraco_client/Application/Extensions.js b/src/Umbraco.Web.UI/umbraco_client/Application/Extensions.js index 9884df1c80..e7af064229 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Application/Extensions.js +++ b/src/Umbraco.Web.UI/umbraco_client/Application/Extensions.js @@ -26,7 +26,7 @@ } }; } - + if (!window.location.getParams) { var pl = /\+/g; // Regex for replacing addition symbol with a space var search = /([^&=]+)=?([^&]*)/g; @@ -38,11 +38,11 @@ var urlParams = {}; while (match = search.exec(query)) - urlParams[decode(match[1])] = decode(match[2]); - + urlParams[decode(match[1])] = decode(match[2]); + return urlParams; } - } + } if (!String.prototype.startsWith) { String.prototype.startsWith = function (str) { @@ -388,12 +388,12 @@ return null; } - var cookieVal = getCookie("XSRF-TOKEN"); + var cookieVal = getCookie("UMB-XSRF-TOKEN"); if (cookieVal) { - xhr.setRequestHeader("X-XSRF-TOKEN", cookieVal); + xhr.setRequestHeader("X-UMB-XSRF-TOKEN", cookieVal); } - var queryString = window.location.getParams(); + var queryString = window.location.getParams(); if (queryString.umbDebug === "true") { xhr.setRequestHeader("X-UMB-DEBUG", cookieVal); } diff --git a/src/Umbraco.Web/HttpCookieExtensions.cs b/src/Umbraco.Web/HttpCookieExtensions.cs index 7aec1466da..d2133927a0 100644 --- a/src/Umbraco.Web/HttpCookieExtensions.cs +++ b/src/Umbraco.Web/HttpCookieExtensions.cs @@ -16,6 +16,40 @@ 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) + { + foreach (var header in requestHeaders) + { + 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; + } + /// /// Removes the cookie from the request and the response if it exists /// 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; diff --git a/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs b/src/Umbraco.Web/WebApi/Filters/AngularAntiForgeryHelper.cs index 962183f7ef..dfeeee536d 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; @@ -18,19 +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 @@ -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,13 @@ 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 = requestHeaders.GetCookieValue(CsrfValidationCookieName); return ValidateHeaders( requestHeaders.ToDictionary(x => x.Key, x => x.Value).ToArray(), - cookieToken == null ? null : cookieToken.Value, + cookieToken == null ? null : cookieToken, out failedReason); } + } } \ No newline at end of file