From 0ce90cf359a2093ab7a81407bb26569c3ca239c9 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Jan 2021 17:21:35 +1100 Subject: [PATCH] Moves UrlExtensions methods to new service and reduce the huge amount of allocated strings during routing --- .../DependencyInjection/UmbracoBuilder.cs | 2 + .../Routing/UmbracoRequestPaths.cs | 151 ++++++++++++++++++ src/Umbraco.Core/UriExtensions.cs | 145 ----------------- .../LiveModelsProvider.cs | 15 +- .../Objects/TestUmbracoContextFactory.cs | 3 +- .../Extensions/UriExtensionsTests.cs | 45 +----- .../Routing/UmbracoRequestPathsTests.cs | 105 ++++++++++++ .../Security/BackOfficeCookieManagerTests.cs | 34 ++-- .../EndpointRouteBuilderExtensionsTests.cs | 2 +- .../Routing/RoutableDocumentFilterTests.cs | 32 +++- .../Controllers/SurfaceControllerTests.cs | 9 +- .../Routing/UmbracoModuleTests.cs | 23 --- .../UnhandledExceptionLoggerMiddleware.cs | 6 +- .../PreviewAuthenticationMiddleware.cs | 26 +-- .../Routing/BackOfficeAreaRoutes.cs | 9 +- .../Routing/PreviewRoutes.cs | 1 + .../Security/BackOfficeCookieManager.cs | 43 +++-- .../Security/BackOfficeSessionIdValidator.cs | 26 ++- .../ConfigureBackOfficeCookieOptions.cs | 9 +- .../EndpointRouteBuilderExtensions.cs | 70 ++++---- .../Extensions/HttpRequestExtensions.cs | 34 ++-- .../Install/InstallAreaRoutes.cs | 1 + .../Middleware/UmbracoRequestMiddleware.cs | 19 ++- .../Routing/RoutableDocumentFilter.cs | 7 +- .../UmbracoContext/UmbracoContext.cs | 9 +- .../UmbracoContext/UmbracoContextFactory.cs | 9 +- .../Routing/UmbracoRouteValueTransformer.cs | 8 - src/Umbraco.Web/UmbracoContext.cs | 94 ++--------- src/Umbraco.Web/UmbracoInjectedModule.cs | 57 ------- 29 files changed, 473 insertions(+), 521 deletions(-) create mode 100644 src/Umbraco.Core/Routing/UmbracoRequestPaths.cs create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs rename src/Umbraco.Web.Common/{Routing => Extensions}/EndpointRouteBuilderExtensions.cs (64%) diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 96f01d111a..260ec5487f 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -24,6 +24,7 @@ using Umbraco.Core.Mail; using Umbraco.Core.Manifest; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Routing; using Umbraco.Core.Runtime; using Umbraco.Core.Security; using Umbraco.Core.Services; @@ -146,6 +147,7 @@ namespace Umbraco.Core.DependencyInjection this.AddNotificationHandler(); Services.AddSingleton(); + Services.AddSingleton(); this.AddNotificationHandler(); Services.AddUnique(); diff --git a/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs new file mode 100644 index 0000000000..5ec8be071f --- /dev/null +++ b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.Extensions.Options; +using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Hosting; + +namespace Umbraco.Core.Routing +{ + /// + /// Utility for checking paths + /// + public class UmbracoRequestPaths + { + private readonly string _backOfficePath; + private readonly string _mvcArea; + private readonly string _backOfficeMvcPath; + private readonly string _previewMvcPath; + private readonly string _installPath; + private readonly string _appPath; + private readonly List _aspLegacyJsExt = new List { ".asmx/", ".aspx/", ".ashx/", ".axd/", ".svc/" }; + private readonly List _aspLegacyExt = new List { ".asmx", ".aspx", ".ashx", ".axd", ".svc" }; + + + /// + /// Initializes a new instance of the class. + /// + public UmbracoRequestPaths(IOptions globalSettings, IHostingEnvironment hostingEnvironment) + { + var applicationPath = hostingEnvironment.ApplicationVirtualPath; + _appPath = applicationPath.TrimStart('/'); + + _backOfficePath = globalSettings.Value.GetBackOfficePath(hostingEnvironment) + .EnsureStartsWith('/').TrimStart(_appPath.EnsureStartsWith('/')).EnsureStartsWith('/'); + + _mvcArea = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment); + + _backOfficeMvcPath = "/" + _mvcArea + "/BackOffice/"; + _previewMvcPath = "/" + _mvcArea + "/Preview/"; + _installPath = hostingEnvironment.ToAbsolute(Constants.SystemDirectories.Install); + } + + /// + /// Checks if the current uri is a back office request + /// + /// + /// There are some special routes we need to check to properly determine this: + /// + /// If any route has an extension in the path like .aspx = back office + /// + /// These are def back office: + /// /Umbraco/BackOffice = back office + /// /Umbraco/Preview = back office + /// If it's not any of the above, and there's no extension then we cannot determine if it's back office or front-end + /// so we can only assume that it is not back office. This will occur if people use an UmbracoApiController for the backoffice + /// but do not inherit from UmbracoAuthorizedApiController and do not use [IsBackOffice] attribute. + /// + /// These are def front-end: + /// /Umbraco/Surface = front-end + /// /Umbraco/Api = front-end + /// But if we've got this far we'll just have to assume it's front-end anyways. + /// + /// + public bool IsBackOfficeRequest(string absPath) + { + var fullUrlPath = absPath.TrimStart('/'); + var urlPath = fullUrlPath.TrimStart(_appPath).EnsureStartsWith('/'); + + // check if this is in the umbraco back office + var isUmbracoPath = urlPath.InvariantStartsWith(_backOfficePath); + + // if not, then def not back office + if (isUmbracoPath == false) + { + return false; + } + + // if its the normal /umbraco path + if (urlPath.InvariantEquals("/" + _mvcArea) + || urlPath.InvariantEquals("/" + _mvcArea + "/")) + { + return true; + } + + // check for a file extension + var extension = Path.GetExtension(absPath); + + // has an extension, def back office + if (extension.IsNullOrWhiteSpace() == false) + { + return true; + } + + // check for special case asp.net calls like: + // /umbraco/webservices/legacyAjaxCalls.asmx/js which will return a null file extension but are still considered requests with an extension + if (_aspLegacyJsExt.Any(x => urlPath.InvariantContains(x))) + { + return true; + } + + // check for special back office paths + if (urlPath.InvariantStartsWith(_backOfficeMvcPath) + || urlPath.InvariantStartsWith(_previewMvcPath)) + { + return true; + } + + // check for special front-end paths + // TODO: These should be constants - will need to update when we do front-end routing + if (urlPath.InvariantStartsWith("/" + _mvcArea + "/Surface/") + || urlPath.InvariantStartsWith("/" + _mvcArea + "/Api/")) + { + return false; + } + + // if its none of the above, we will have to try to detect if it's a PluginController route, we can detect this by + // checking how many parts the route has, for example, all PluginController routes will be routed like + // Umbraco/MYPLUGINAREA/MYCONTROLLERNAME/{action}/{id} + // so if the path contains at a minimum 3 parts: Umbraco + MYPLUGINAREA + MYCONTROLLERNAME then we will have to assume it is a + // plugin controller for the front-end. + if (urlPath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).Length >= 3) + { + return false; + } + + // if its anything else we can assume it's back office + return true; + } + + /// + /// Checks if the current uri is an install request + /// + public bool IsInstallerRequest(string absPath) => absPath.InvariantStartsWith(_installPath); + + /// + /// Rudimentary check to see if it's not a server side request + /// + public bool IsClientSideRequest(string absPath) + { + var ext = Path.GetExtension(absPath); + if (ext.IsNullOrWhiteSpace()) + { + return false; + } + + return _aspLegacyExt.Any(ext.InvariantEquals) == false; + } + } +} diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index ea846f7f7a..497159309a 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -13,151 +13,6 @@ namespace Umbraco.Core /// public static class UriExtensions { - /// - /// Checks if the current uri is a back office request - /// - /// - /// - /// - /// - /// - /// There are some special routes we need to check to properly determine this: - /// - /// If any route has an extension in the path like .aspx = back office - /// - /// These are def back office: - /// /Umbraco/BackOffice = back office - /// /Umbraco/Preview = back office - /// If it's not any of the above, and there's no extension then we cannot determine if it's back office or front-end - /// so we can only assume that it is not back office. This will occur if people use an UmbracoApiController for the backoffice - /// but do not inherit from UmbracoAuthorizedApiController and do not use [IsBackOffice] attribute. - /// - /// These are def front-end: - /// /Umbraco/Surface = front-end - /// /Umbraco/Api = front-end - /// But if we've got this far we'll just have to assume it's front-end anyways. - /// - /// - public static bool IsBackOfficeRequest(this Uri url, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) - { - var applicationPath = hostingEnvironment.ApplicationVirtualPath; - - var fullUrlPath = url.AbsolutePath.TrimStart(new[] {'/'}); - var appPath = applicationPath.TrimStart(new[] {'/'}); - var urlPath = fullUrlPath.TrimStart(appPath).EnsureStartsWith('/'); - - //check if this is in the umbraco back office - var backOfficePath = globalSettings.GetBackOfficePath(hostingEnvironment); - var isUmbracoPath = urlPath.InvariantStartsWith(backOfficePath.EnsureStartsWith('/').TrimStart(appPath.EnsureStartsWith('/')).EnsureStartsWith('/')); - //if not, then def not back office - if (isUmbracoPath == false) return false; - - var mvcArea = globalSettings.GetUmbracoMvcArea(hostingEnvironment); - //if its the normal /umbraco path - if (urlPath.InvariantEquals("/" + mvcArea) - || urlPath.InvariantEquals("/" + mvcArea + "/")) - { - return true; - } - - //check for a file extension - var extension = Path.GetExtension(url.LocalPath); - //has an extension, def back office - if (extension.IsNullOrWhiteSpace() == false) return true; - //check for special case asp.net calls like: - // /umbraco/webservices/legacyAjaxCalls.asmx/js which will return a null file extension but are still considered requests with an extension - if (urlPath.InvariantContains(".asmx/") - || urlPath.InvariantContains(".aspx/") - || urlPath.InvariantContains(".ashx/") - || urlPath.InvariantContains(".axd/") - || urlPath.InvariantContains(".svc/")) - { - return true; - } - - //check for special back office paths - if (urlPath.InvariantStartsWith("/" + mvcArea + "/BackOffice/") - || urlPath.InvariantStartsWith("/" + mvcArea + "/Preview/")) - { - return true; - } - - //check for special front-end paths - // TODO: These should be constants - will need to update when we do front-end routing - if (urlPath.InvariantStartsWith("/" + mvcArea + "/Surface/") - || urlPath.InvariantStartsWith("/" + mvcArea + "/Api/")) - { - return false; - } - - //if its none of the above, we will have to try to detect if it's a PluginController route, we can detect this by - // checking how many parts the route has, for example, all PluginController routes will be routed like - // Umbraco/MYPLUGINAREA/MYCONTROLLERNAME/{action}/{id} - // so if the path contains at a minimum 3 parts: Umbraco + MYPLUGINAREA + MYCONTROLLERNAME then we will have to assume it is a - // plugin controller for the front-end. - if (urlPath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries).Length >= 3) - { - return false; - } - - // if its anything else we can assume it's back office - return true; - } - - /// - /// Checks if the current uri is an install request - /// - public static bool IsInstallerRequest(this Uri url, IHostingEnvironment hostingEnvironment) - { - var authority = url.GetLeftPart(UriPartial.Authority); - var afterAuthority = url.GetLeftPart(UriPartial.Query) - .TrimStart(authority) - .TrimStart("/"); - - // check if this is in the umbraco back office - return afterAuthority.InvariantStartsWith(hostingEnvironment.ToAbsolute(Constants.SystemDirectories.Install).TrimStart("/")); - } - - /// - /// Checks if the uri is a request for the default back office page - /// - public static bool IsDefaultBackOfficeRequest(this Uri url, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) - { - var backOfficePath = globalSettings.GetBackOfficePath(hostingEnvironment); - if (url.AbsolutePath.InvariantEquals(backOfficePath.TrimEnd("/")) - || url.AbsolutePath.InvariantEquals(backOfficePath.EnsureEndsWith('/')) - || url.AbsolutePath.InvariantEquals(backOfficePath.EnsureEndsWith('/') + "Default") - || url.AbsolutePath.InvariantEquals(backOfficePath.EnsureEndsWith('/') + "Default/")) - { - return true; - } - - return false; - } - - /// - /// This is a performance tweak to check if this not an ASP.Net server file - /// .Net will pass these requests through to the module when in integrated mode. - /// We want to ignore all of these requests immediately. - /// - /// - /// - public static bool IsClientSideRequest(this Uri url) - { - try - { - var ext = Path.GetExtension(url.LocalPath); - if (ext.IsNullOrWhiteSpace()) return false; - var toInclude = new[] {".aspx", ".ashx", ".asmx", ".axd", ".svc"}; - return toInclude.Any(ext.InvariantEquals) == false; - } - catch (ArgumentException) - { - StaticApplicationLogging.Logger.LogDebug("Failed to determine if request was client side (invalid chars in path \"{Path}\"?)", url.LocalPath); - return false; - } - } - /// /// Rewrites the path of uri. /// diff --git a/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs index b8488e0852..d7fc051500 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/LiveModelsProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; @@ -11,6 +11,7 @@ using Umbraco.ModelsBuilder.Embedded.Building; using Umbraco.Web.Cache; using Umbraco.Core.Configuration.Models; using Microsoft.Extensions.Options; +using Umbraco.Extensions; namespace Umbraco.ModelsBuilder.Embedded { @@ -115,12 +116,16 @@ namespace Umbraco.ModelsBuilder.Embedded public void AppEndRequest(HttpContext context) { - var requestUri = new Uri(context.Request.GetEncodedUrl(), UriKind.RelativeOrAbsolute); - - if (requestUri.IsClientSideRequest()) + if (context.Request.IsClientSideRequest()) + { return; + } + + if (!IsEnabled) + { + return; + } - if (!IsEnabled) return; GenerateModelsIfRequested(); } } diff --git a/src/Umbraco.Tests.UnitTests/TestHelpers/Objects/TestUmbracoContextFactory.cs b/src/Umbraco.Tests.UnitTests/TestHelpers/Objects/TestUmbracoContextFactory.cs index 91d05ba0df..37b96b9947 100644 --- a/src/Umbraco.Tests.UnitTests/TestHelpers/Objects/TestUmbracoContextFactory.cs +++ b/src/Umbraco.Tests.UnitTests/TestHelpers/Objects/TestUmbracoContextFactory.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Options; using Moq; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; +using Umbraco.Core.Routing; using Umbraco.Core.Security; using Umbraco.Tests.Common; using Umbraco.Web; @@ -63,7 +64,7 @@ namespace Umbraco.Tests.UnitTests.TestHelpers.Objects snapshotService.Object, new TestVariationContextAccessor(), new TestDefaultCultureAccessor(), - Options.Create(globalSettings), + new UmbracoRequestPaths(Options.Create(globalSettings), hostingEnvironment), hostingEnvironment, new UriUtility(hostingEnvironment), new AspNetCoreCookieManager(httpContextAccessor), diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UriExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UriExtensionsTests.cs index a072a1a189..fc8ecd0474 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UriExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UriExtensionsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System; @@ -25,49 +25,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.Extensions private IWebHostEnvironment _hostEnvironment; private GlobalSettings _globalSettings; - [TestCase("http://www.domain.com/umbraco/preview/frame?id=1234", "", true)] - [TestCase("http://www.domain.com/umbraco", "", true)] - [TestCase("http://www.domain.com/Umbraco/", "", true)] - [TestCase("http://www.domain.com/umbraco/default.aspx", "", true)] - [TestCase("http://www.domain.com/umbraco/test/test", "", false)] - [TestCase("http://www.domain.com/umbraco/test/test/test", "", false)] - [TestCase("http://www.domain.com/Umbraco/test/test.aspx", "", true)] - [TestCase("http://www.domain.com/umbraco/test/test.js", "", true)] - [TestCase("http://www.domain.com/umbrac", "", false)] - [TestCase("http://www.domain.com/test", "", false)] - [TestCase("http://www.domain.com/test/umbraco", "", false)] - [TestCase("http://www.domain.com/Umbraco/Backoffice/blah", "", true)] - [TestCase("http://www.domain.com/Umbraco/anything", "", true)] - [TestCase("http://www.domain.com/Umbraco/anything/", "", true)] - [TestCase("http://www.domain.com/Umbraco/surface/blah", "", false)] - [TestCase("http://www.domain.com/umbraco/api/blah", "", false)] - [TestCase("http://www.domain.com/myvdir/umbraco/api/blah", "myvdir", false)] - [TestCase("http://www.domain.com/MyVdir/umbraco/api/blah", "/myvdir", false)] - [TestCase("http://www.domain.com/MyVdir/Umbraco/", "myvdir", true)] - [TestCase("http://www.domain.com/umbraco/test/legacyAjaxCalls.ashx?some=query&blah=js", "", true)] - public void Is_Back_Office_Request(string input, string virtualPath, bool expected) - { - var source = new Uri(input); - var hostingEnvironment = CreateHostingEnvironment(virtualPath); - Assert.AreEqual(expected, source.IsBackOfficeRequest(_globalSettings, hostingEnvironment)); - } - - [TestCase("http://www.domain.com/install", true)] - [TestCase("http://www.domain.com/Install/", true)] - [TestCase("http://www.domain.com/install/default.aspx", true)] - [TestCase("http://www.domain.com/install/test/test", true)] - [TestCase("http://www.domain.com/Install/test/test.aspx", true)] - [TestCase("http://www.domain.com/install/test/test.js", true)] - [TestCase("http://www.domain.com/instal", false)] - [TestCase("http://www.domain.com/umbraco", false)] - [TestCase("http://www.domain.com/umbraco/umbraco", false)] - public void Is_Installer_Request(string input, bool expected) - { - var source = new Uri(input); - var hostingEnvironment = CreateHostingEnvironment(); - Assert.AreEqual(expected, source.IsInstallerRequest(hostingEnvironment)); - } - private AspNetCoreHostingEnvironment CreateHostingEnvironment(string virtualPath = "") { var hostingSettings = new HostingSettings { ApplicationVirtualPath = virtualPath }; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs new file mode 100644 index 0000000000..da2ea985d0 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Options; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Configuration.Models; +using Umbraco.Core.Routing; +using Umbraco.Web.Common.AspNetCore; + +namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing +{ + [TestFixture] + public class UmbracoRequestPathsTests + { + private IWebHostEnvironment _hostEnvironment; + private GlobalSettings _globalSettings; + + [OneTimeSetUp] + public void Setup() + { + _hostEnvironment = Mock.Of(); + _globalSettings = new GlobalSettings(); + } + + private AspNetCoreHostingEnvironment CreateHostingEnvironment(string virtualPath = "") + { + var hostingSettings = new HostingSettings { ApplicationVirtualPath = virtualPath }; + var mockedOptionsMonitorOfHostingSettings = Mock.Of>(x => x.CurrentValue == hostingSettings); + return new AspNetCoreHostingEnvironment(mockedOptionsMonitorOfHostingSettings, _hostEnvironment); + } + + [TestCase("/favicon.ico", true)] + [TestCase("/umbraco_client/Tree/treeIcons.css", true)] + [TestCase("/umbraco_client/Tree/Themes/umbraco/style.css?cdv=37", true)] + [TestCase("/base/somebasehandler", false)] + [TestCase("/", false)] + [TestCase("/home.aspx", false)] + public void Is_Client_Side_Request(string url, bool assert) + { + var umbracoRequestPaths = new UmbracoRequestPaths(null, null); + + var uri = new Uri("http://test.com" + url); + var result = umbracoRequestPaths.IsClientSideRequest(uri.AbsolutePath); + Assert.AreEqual(assert, result); + } + + [Test] + public void Is_Client_Side_Request_InvalidPath_ReturnFalse() + { + var umbracoRequestPaths = new UmbracoRequestPaths(null, null); + + // This URL is invalid. Default to false when the extension cannot be determined + var uri = new Uri("http://test.com/installing-modules+foobar+\"yipee\""); + var result = umbracoRequestPaths.IsClientSideRequest(uri.AbsolutePath); + Assert.AreEqual(false, result); + } + + [TestCase("http://www.domain.com/umbraco/preview/frame?id=1234", "", true)] + [TestCase("http://www.domain.com/umbraco", "", true)] + [TestCase("http://www.domain.com/Umbraco/", "", true)] + [TestCase("http://www.domain.com/umbraco/default.aspx", "", true)] + [TestCase("http://www.domain.com/umbraco/test/test", "", false)] + [TestCase("http://www.domain.com/umbraco/test/test/test", "", false)] + [TestCase("http://www.domain.com/Umbraco/test/test.aspx", "", true)] + [TestCase("http://www.domain.com/umbraco/test/test.js", "", true)] + [TestCase("http://www.domain.com/umbrac", "", false)] + [TestCase("http://www.domain.com/test", "", false)] + [TestCase("http://www.domain.com/test/umbraco", "", false)] + [TestCase("http://www.domain.com/Umbraco/Backoffice/blah", "", true)] + [TestCase("http://www.domain.com/Umbraco/anything", "", true)] + [TestCase("http://www.domain.com/Umbraco/anything/", "", true)] + [TestCase("http://www.domain.com/Umbraco/surface/blah", "", false)] + [TestCase("http://www.domain.com/umbraco/api/blah", "", false)] + [TestCase("http://www.domain.com/myvdir/umbraco/api/blah", "myvdir", false)] + [TestCase("http://www.domain.com/MyVdir/umbraco/api/blah", "/myvdir", false)] + [TestCase("http://www.domain.com/MyVdir/Umbraco/", "myvdir", true)] + [TestCase("http://www.domain.com/umbraco/test/legacyAjaxCalls.ashx?some=query&blah=js", "", true)] + public void Is_Back_Office_Request(string input, string virtualPath, bool expected) + { + var source = new Uri(input); + var hostingEnvironment = CreateHostingEnvironment(virtualPath); + var umbracoRequestPaths = new UmbracoRequestPaths(Options.Create(_globalSettings), hostingEnvironment); + Assert.AreEqual(expected, umbracoRequestPaths.IsBackOfficeRequest(source.AbsolutePath)); + } + + [TestCase("http://www.domain.com/install", true)] + [TestCase("http://www.domain.com/Install/", true)] + [TestCase("http://www.domain.com/install/default.aspx", true)] + [TestCase("http://www.domain.com/install/test/test", true)] + [TestCase("http://www.domain.com/Install/test/test.aspx", true)] + [TestCase("http://www.domain.com/install/test/test.js", true)] + [TestCase("http://www.domain.com/instal", false)] + [TestCase("http://www.domain.com/umbraco", false)] + [TestCase("http://www.domain.com/umbraco/umbraco", false)] + public void Is_Installer_Request(string input, bool expected) + { + var source = new Uri(input); + var hostingEnvironment = CreateHostingEnvironment(); + var umbracoRequestPaths = new UmbracoRequestPaths(Options.Create(_globalSettings), hostingEnvironment); + Assert.AreEqual(expected, umbracoRequestPaths.IsInstallerRequest(source.AbsolutePath)); + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs index b677f11f2c..569c79faef 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Security/BackOfficeCookieManagerTests.cs @@ -2,11 +2,13 @@ // See LICENSE for more details. using System; +using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; +using Umbraco.Core.Routing; using Umbraco.Extensions; using Umbraco.Web; using Umbraco.Web.BackOffice.Controllers; @@ -26,10 +28,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security var mgr = new BackOfficeCookieManager( Mock.Of(), runtime, - Mock.Of(), - globalSettings); + new UmbracoRequestPaths(Options.Create(globalSettings), Mock.Of())); - var result = mgr.ShouldAuthenticateRequest(new Uri("http://localhost/umbraco")); + var result = mgr.ShouldAuthenticateRequest("/umbraco"); Assert.IsFalse(result); } @@ -43,10 +44,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security var mgr = new BackOfficeCookieManager( Mock.Of(), runtime, - Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco"), - globalSettings); + new UmbracoRequestPaths( + Options.Create(globalSettings), + Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco"))); - var result = mgr.ShouldAuthenticateRequest(new Uri("http://localhost/umbraco")); + var result = mgr.ShouldAuthenticateRequest("/umbraco"); Assert.IsTrue(result); } @@ -63,13 +65,14 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security var mgr = new BackOfficeCookieManager( Mock.Of(), runtime, - Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install"), - globalSettings); + new UmbracoRequestPaths( + Options.Create(globalSettings), + Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install"))); - var result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost{remainingTimeoutSecondsPath}")); + var result = mgr.ShouldAuthenticateRequest(remainingTimeoutSecondsPath); Assert.IsTrue(result); - result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost{isAuthPath}")); + result = mgr.ShouldAuthenticateRequest(isAuthPath); Assert.IsTrue(result); } @@ -83,14 +86,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security var mgr = new BackOfficeCookieManager( Mock.Of(), runtime, - Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install"), - globalSettings); + new UmbracoRequestPaths( + Options.Create(globalSettings), + Mock.Of(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install"))); - var result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost/notbackoffice")); + var result = mgr.ShouldAuthenticateRequest("/notbackoffice"); Assert.IsFalse(result); - result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost/umbraco/api/notbackoffice")); + result = mgr.ShouldAuthenticateRequest("/umbraco/api/notbackoffice"); Assert.IsFalse(result); - result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost/umbraco/surface/notbackoffice")); + result = mgr.ShouldAuthenticateRequest("/umbraco/surface/notbackoffice"); Assert.IsFalse(result); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/EndpointRouteBuilderExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/EndpointRouteBuilderExtensionsTests.cs index f5b491a8af..0990cb9d9a 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/EndpointRouteBuilderExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/EndpointRouteBuilderExtensionsTests.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Routing; using NUnit.Framework; using Umbraco.Core; using Umbraco.Extensions; -using Umbraco.Web.Common.Routing; +using Umbraco.Web.Common.Extensions; using Constants = Umbraco.Core.Constants; namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs index 87ee6d0ea0..8c42779372 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Routing/RoutableDocumentFilterTests.cs @@ -76,13 +76,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing RouteEndpoint endpoint1 = CreateEndpoint( "Umbraco/RenderMvc/{action?}/{id?}", new { controller = "RenderMvc" }, - "Umbraco_default", 0); RouteEndpoint endpoint2 = CreateEndpoint( "api/{controller?}/{id?}", new { action = "Index" }, - "WebAPI", 1); var endpointDataSource = new DefaultEndpointDataSource(endpoint1, endpoint2); @@ -97,16 +95,42 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing routableDocFilter.IsDocumentRequest(url)); } + [TestCase("/umbraco", true)] + [TestCase("/umbraco/", true)] + [TestCase("/umbraco/Default", true)] + [TestCase("/umbraco/default/", true)] + [TestCase("/umbraco/default/123", true)] + [TestCase("/umbraco/default/blah/123", false)] + public void Is_Reserved_By_Default_Back_Office_Route(string url, bool isReserved) + { + var globalSettings = new GlobalSettings { ReservedPaths = string.Empty, ReservedUrls = string.Empty }; + + RouteEndpoint endpoint1 = CreateEndpoint( + "umbraco/{action}/{id?}", + new { controller = "BackOffice", action = "Default" }, + 0); + + var endpointDataSource = new DefaultEndpointDataSource(endpoint1); + + var routableDocFilter = new RoutableDocumentFilter( + globalSettings, + GetHostingEnvironment(), + endpointDataSource); + + Assert.AreEqual( + !isReserved, // not reserved if it's a document request + routableDocFilter.IsDocumentRequest(url)); + } + // borrowed from https://github.com/dotnet/aspnetcore/blob/19559e73da2b6d335b864ed2855dd8a0c7a207a0/src/Mvc/Mvc.Core/test/Routing/ControllerLinkGeneratorExtensionsTest.cs#L171 private RouteEndpoint CreateEndpoint( string template, object defaults = null, - string name = null, int order = 0) => new RouteEndpoint( (httpContext) => Task.CompletedTask, RoutePatternFactory.Parse(template, defaults, null), order, new EndpointMetadataCollection(Array.Empty()), - name); + null); } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs index 07f8118ad0..1ea3e99b54 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs @@ -12,6 +12,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Routing; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Tests.Common; @@ -49,7 +50,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers Mock.Of(), new TestVariationContextAccessor(), new TestDefaultCultureAccessor(), - Options.Create(globalSettings), + new UmbracoRequestPaths(Options.Create(globalSettings), hostingEnvironment), hostingEnvironment, new UriUtility(hostingEnvironment), Mock.Of(), @@ -80,7 +81,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers Mock.Of(), new TestVariationContextAccessor(), new TestDefaultCultureAccessor(), - Options.Create(globalSettings), + new UmbracoRequestPaths(Options.Create(globalSettings), hostingEnvironment), hostingEnvironment, new UriUtility(hostingEnvironment), Mock.Of(), @@ -115,7 +116,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers publishedSnapshotService.Object, new TestVariationContextAccessor(), new TestDefaultCultureAccessor(), - Options.Create(globalSettings), + new UmbracoRequestPaths(Options.Create(globalSettings), hostingEnvironment), hostingEnvironment, new UriUtility(hostingEnvironment), Mock.Of(), @@ -149,7 +150,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers Mock.Of(), new TestVariationContextAccessor(), new TestDefaultCultureAccessor(), - Options.Create(globalSettings), + new UmbracoRequestPaths(Options.Create(globalSettings), hostingEnvironment), hostingEnvironment, new UriUtility(hostingEnvironment), Mock.Of(), diff --git a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs index 166dea39f8..2ec0113c2f 100644 --- a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs +++ b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs @@ -32,7 +32,6 @@ namespace Umbraco.Tests.Routing ( runtime, logger, - null, // FIXME: PublishedRouter complexities... Mock.Of(), globalSettings, HostingEnvironment @@ -77,28 +76,6 @@ namespace Umbraco.Tests.Routing Assert.AreEqual(assert, result.Success); } - [TestCase("/favicon.ico", true)] - [TestCase("/umbraco_client/Tree/treeIcons.css", true)] - [TestCase("/umbraco_client/Tree/Themes/umbraco/style.css?cdv=37", true)] - [TestCase("/base/somebasehandler", false)] - [TestCase("/", false)] - [TestCase("/home.aspx", false)] - public void Is_Client_Side_Request(string url, bool assert) - { - var uri = new Uri("http://test.com" + url); - var result = uri.IsClientSideRequest(); - Assert.AreEqual(assert, result); - } - - [Test] - public void Is_Client_Side_Request_InvalidPath_ReturnFalse() - { - //This URL is invalid. Default to false when the extension cannot be determined - var uri = new Uri("http://test.com/installing-modules+foobar+\"yipee\""); - var result = uri.IsClientSideRequest(); - Assert.AreEqual(false, result); - } - //NOTE: This test shows how we can test most of the HttpModule, it however is testing a method that no longer exists and is testing too much, // we need to write unit tests for each of the components: NiceUrlProvider, all of the Lookup classes, etc... // to ensure that each one is individually tested. diff --git a/src/Umbraco.Web.BackOffice/Filters/UnhandledExceptionLoggerMiddleware.cs b/src/Umbraco.Web.BackOffice/Filters/UnhandledExceptionLoggerMiddleware.cs index db6162afa8..e2192f694b 100644 --- a/src/Umbraco.Web.BackOffice/Filters/UnhandledExceptionLoggerMiddleware.cs +++ b/src/Umbraco.Web.BackOffice/Filters/UnhandledExceptionLoggerMiddleware.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; using Umbraco.Core; +using Umbraco.Extensions; namespace Umbraco.Web.BackOffice.Filters { @@ -21,9 +22,8 @@ namespace Umbraco.Web.BackOffice.Filters public async Task InvokeAsync(HttpContext context, RequestDelegate next) { - var requestUri = new Uri(context.Request.GetEncodedUrl(), UriKind.RelativeOrAbsolute); // If it's a client side request just call next and don't try to log anything - if (requestUri.IsClientSideRequest()) + if (context.Request.IsClientSideRequest()) { await next(context); } @@ -36,7 +36,7 @@ namespace Umbraco.Web.BackOffice.Filters } catch (Exception e) { - _logger.LogError(e, "Unhandled controller exception occurred for request '{RequestUrl}'", requestUri.AbsoluteUri); + _logger.LogError(e, "Unhandled controller exception occurred for request '{RequestUrl}'", context.Request.GetEncodedPathAndQuery()); // Throw the error again, just in case it gets handled throw; } diff --git a/src/Umbraco.Web.BackOffice/Middleware/PreviewAuthenticationMiddleware.cs b/src/Umbraco.Web.BackOffice/Middleware/PreviewAuthenticationMiddleware.cs index 06715b4ad1..85bc7c9ef7 100644 --- a/src/Umbraco.Web.BackOffice/Middleware/PreviewAuthenticationMiddleware.cs +++ b/src/Umbraco.Web.BackOffice/Middleware/PreviewAuthenticationMiddleware.cs @@ -1,13 +1,10 @@ +using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using System; -using System.Threading.Tasks; using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; using Umbraco.Extensions; namespace Umbraco.Web.BackOffice.Middleware @@ -17,17 +14,7 @@ namespace Umbraco.Web.BackOffice.Middleware /// public class PreviewAuthenticationMiddleware : IMiddleware { - private readonly GlobalSettings _globalSettings; - private readonly IHostingEnvironment _hostingEnvironment; - - public PreviewAuthenticationMiddleware( - IOptions globalSettings, - IHostingEnvironment hostingEnvironment) - { - _globalSettings = globalSettings.Value; - _hostingEnvironment = hostingEnvironment; - } - + /// public async Task InvokeAsync(HttpContext context, RequestDelegate next) { var request = context.Request; @@ -35,7 +22,7 @@ namespace Umbraco.Web.BackOffice.Middleware { var isPreview = request.HasPreviewCookie() && context.User != null - && !request.IsBackOfficeRequest(_globalSettings, _hostingEnvironment); + && !request.IsBackOfficeRequest(); if (isPreview) { @@ -43,7 +30,9 @@ namespace Umbraco.Web.BackOffice.Middleware .Get(Constants.Security.BackOfficeAuthenticationType); if (cookieOptions == null) + { throw new InvalidOperationException("No cookie options found with name " + Constants.Security.BackOfficeAuthenticationType); + } // If we've gotten this far it means a preview cookie has been set and a front-end umbraco document request is executing. // In this case, authentication will not have occurred for an Umbraco back office User, however we need to perform the authentication @@ -55,11 +44,12 @@ namespace Umbraco.Web.BackOffice.Middleware { var backOfficeIdentity = unprotected.Principal.GetUmbracoIdentity(); if (backOfficeIdentity != null) + { // Ok, we've got a real ticket, now we can add this ticket's identity to the current // Principal, this means we'll have 2 identities assigned to the principal which we can // use to authorize the preview and allow for a back office User. - context.User.AddIdentity(backOfficeIdentity); + } } } diff --git a/src/Umbraco.Web.BackOffice/Routing/BackOfficeAreaRoutes.cs b/src/Umbraco.Web.BackOffice/Routing/BackOfficeAreaRoutes.cs index c3fbc9c556..39cfe0002b 100644 --- a/src/Umbraco.Web.BackOffice/Routing/BackOfficeAreaRoutes.cs +++ b/src/Umbraco.Web.BackOffice/Routing/BackOfficeAreaRoutes.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -6,6 +6,7 @@ using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.Common.Controllers; +using Umbraco.Web.Common.Extensions; using Umbraco.Web.Common.Routing; using Umbraco.Web.WebApi; @@ -61,14 +62,16 @@ namespace Umbraco.Web.BackOffice.Routing /// /// Map the minimal routes required to load the back office login and auth /// - /// private void MapMinimalBackOffice(IEndpointRouteBuilder endpoints) { - endpoints.MapUmbracoRoute(_umbracoPathSegment, Constants.Web.Mvc.BackOfficeArea, + endpoints.MapUmbracoRoute( + _umbracoPathSegment, + Constants.Web.Mvc.BackOfficeArea, string.Empty, "Default", includeControllerNameInRoute: false, constraints: + // Limit the action/id to only allow characters - this is so this route doesn't hog all other // routes like: /umbraco/channels/word.aspx, etc... // (Not that we have to worry about too many of those these days, there still might be a need for these constraints). diff --git a/src/Umbraco.Web.BackOffice/Routing/PreviewRoutes.cs b/src/Umbraco.Web.BackOffice/Routing/PreviewRoutes.cs index d6c961ed54..947e7ac468 100644 --- a/src/Umbraco.Web.BackOffice/Routing/PreviewRoutes.cs +++ b/src/Umbraco.Web.BackOffice/Routing/PreviewRoutes.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Web.BackOffice.Controllers; using Umbraco.Web.BackOffice.SignalR; +using Umbraco.Web.Common.Extensions; using Umbraco.Web.Common.Routing; namespace Umbraco.Web.BackOffice.Routing diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs index 513bbd255c..7d3d392712 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs @@ -1,14 +1,9 @@ -using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; -using Umbraco.Extensions; +using Umbraco.Core.Routing; namespace Umbraco.Web.BackOffice.Security { @@ -23,9 +18,8 @@ namespace Umbraco.Web.BackOffice.Security { private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IRuntimeState _runtime; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly GlobalSettings _globalSettings; private readonly string[] _explicitPaths; + private readonly UmbracoRequestPaths _umbracoRequestPaths; /// /// Initializes a new instance of the class. @@ -33,10 +27,10 @@ namespace Umbraco.Web.BackOffice.Security public BackOfficeCookieManager( IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, - IHostingEnvironment hostingEnvironment, - GlobalSettings globalSettings) - : this(umbracoContextAccessor, runtime, hostingEnvironment, globalSettings, null) - { } + UmbracoRequestPaths umbracoRequestPaths) + : this(umbracoContextAccessor, runtime, null, umbracoRequestPaths) + { + } /// /// Initializes a new instance of the class. @@ -44,21 +38,18 @@ namespace Umbraco.Web.BackOffice.Security public BackOfficeCookieManager( IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, - IHostingEnvironment hostingEnvironment, - GlobalSettings globalSettings, - IEnumerable explicitPaths) + IEnumerable explicitPaths, + UmbracoRequestPaths umbracoRequestPaths) { _umbracoContextAccessor = umbracoContextAccessor; _runtime = runtime; - _hostingEnvironment = hostingEnvironment; - _globalSettings = globalSettings; _explicitPaths = explicitPaths?.ToArray(); + _umbracoRequestPaths = umbracoRequestPaths; } /// /// Determines if we should authenticate the request /// - /// The to check /// true if the request should be authenticated /// /// We auth the request when: @@ -66,7 +57,7 @@ namespace Umbraco.Web.BackOffice.Security /// * it is an installer request /// * it is a preview request /// - public bool ShouldAuthenticateRequest(Uri requestUri) + public bool ShouldAuthenticateRequest(string absPath) { // Do not authenticate the request if we are not running (don't have a db, are not configured) - since we will never need // to know a current user in this scenario - we treat it as a new install. Without this we can have some issues @@ -82,14 +73,14 @@ namespace Umbraco.Web.BackOffice.Security // check the explicit paths if (_explicitPaths != null) { - return _explicitPaths.Any(x => x.InvariantEquals(requestUri.AbsolutePath)); + return _explicitPaths.Any(x => x.InvariantEquals(absPath)); } if (// check back office - requestUri.IsBackOfficeRequest(_globalSettings, _hostingEnvironment) + _umbracoRequestPaths.IsBackOfficeRequest(absPath) // check installer - || requestUri.IsInstallerRequest(_hostingEnvironment)) + || _umbracoRequestPaths.IsInstallerRequest(absPath)) { return true; } @@ -103,16 +94,18 @@ namespace Umbraco.Web.BackOffice.Security /// string Microsoft.AspNetCore.Authentication.Cookies.ICookieManager.GetRequestCookie(HttpContext context, string key) { - var requestUri = new Uri(context.Request.GetEncodedUrl(), UriKind.RelativeOrAbsolute); + var absPath = context.Request.Path; - if (_umbracoContextAccessor.UmbracoContext == null || requestUri.IsClientSideRequest()) + if (_umbracoContextAccessor.UmbracoContext == null || _umbracoRequestPaths.IsClientSideRequest(absPath)) { return null; } - return ShouldAuthenticateRequest(requestUri) == false + return ShouldAuthenticateRequest(absPath) == false + // Don't auth request, don't return a cookie ? null + // Return the default implementation : GetRequestCookie(context, key); } diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeSessionIdValidator.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeSessionIdValidator.cs index 1ccb94e988..5efbf65b78 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeSessionIdValidator.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeSessionIdValidator.cs @@ -8,13 +8,14 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; using Umbraco.Core.Security; using Umbraco.Extensions; namespace Umbraco.Web.BackOffice.Security { +#pragma warning disable IDE0065 // Misplaced using directive using ICookieManager = Microsoft.AspNetCore.Authentication.Cookies.ICookieManager; +#pragma warning restore IDE0065 // Misplaced using directive /// /// Used to validate a cookie against a user's session id @@ -36,21 +37,24 @@ namespace Umbraco.Web.BackOffice.Security public const string CookieName = "UMB_UCONTEXT_C"; private readonly ISystemClock _systemClock; private readonly GlobalSettings _globalSettings; - private readonly IHostingEnvironment _hostingEnvironment; private readonly IBackOfficeUserManager _userManager; - public BackOfficeSessionIdValidator(ISystemClock systemClock, IOptions globalSettings, IHostingEnvironment hostingEnvironment, IBackOfficeUserManager userManager) + /// + /// Initializes a new instance of the class. + /// + public BackOfficeSessionIdValidator(ISystemClock systemClock, IOptions globalSettings, IBackOfficeUserManager userManager) { _systemClock = systemClock; _globalSettings = globalSettings.Value; - _hostingEnvironment = hostingEnvironment; _userManager = userManager; } public async Task ValidateSessionAsync(TimeSpan validateInterval, CookieValidatePrincipalContext context) { - if (!context.Request.IsBackOfficeRequest(_globalSettings, _hostingEnvironment)) + if (!context.Request.IsBackOfficeRequest()) + { return; + } var valid = await ValidateSessionAsync(validateInterval, context.HttpContext, context.Options.CookieManager, _systemClock, context.Properties.IssuedUtc, context.Principal.Identity as ClaimsIdentity); @@ -81,7 +85,7 @@ namespace Umbraco.Web.BackOffice.Security DateTimeOffset? issuedUtc = null; var currentUtc = systemClock.UtcNow; - //read the last checked time from a custom cookie + // read the last checked time from a custom cookie var lastCheckedCookie = cookieManager.GetRequestCookie(httpContext, CookieName); if (lastCheckedCookie.IsNullOrWhiteSpace() == false) @@ -92,7 +96,7 @@ namespace Umbraco.Web.BackOffice.Security } } - //no cookie, use the issue time of the auth ticket + // no cookie, use the issue time of the auth ticket if (issuedUtc.HasValue == false) { issuedUtc = authTicketIssueDate; @@ -107,18 +111,24 @@ namespace Umbraco.Web.BackOffice.Security } if (validate == false) + { return true; + } var userId = currentIdentity.GetUserId(); var user = await _userManager.FindByIdAsync(userId); if (user == null) + { return false; + } var sessionId = currentIdentity.FindFirstValue(Constants.Security.SessionIdClaimType); if (await _userManager.ValidateSessionIdAsync(userId, sessionId) == false) + { return false; + } - //we will re-issue the cookie last checked cookie + // we will re-issue the cookie last checked cookie cookieManager.AppendResponseCookie( httpContext, CookieName, diff --git a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs index b8568a2f03..c267cb7489 100644 --- a/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs +++ b/src/Umbraco.Web.BackOffice/Security/ConfigureBackOfficeCookieOptions.cs @@ -13,6 +13,7 @@ using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; +using Umbraco.Core.Routing; using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Extensions; @@ -36,6 +37,7 @@ namespace Umbraco.Web.BackOffice.Security private readonly IUserService _userService; private readonly IIpResolver _ipResolver; private readonly ISystemClock _systemClock; + private readonly UmbracoRequestPaths _umbracoRequestPaths; /// /// Initializes a new instance of the class. @@ -60,7 +62,8 @@ namespace Umbraco.Web.BackOffice.Security IDataProtectionProvider dataProtection, IUserService userService, IIpResolver ipResolver, - ISystemClock systemClock) + ISystemClock systemClock, + UmbracoRequestPaths umbracoRequestPaths) { _serviceProvider = serviceProvider; _umbracoContextAccessor = umbracoContextAccessor; @@ -72,6 +75,7 @@ namespace Umbraco.Web.BackOffice.Security _userService = userService; _ipResolver = ipResolver; _systemClock = systemClock; + _umbracoRequestPaths = umbracoRequestPaths; } /// @@ -115,8 +119,7 @@ namespace Umbraco.Web.BackOffice.Security options.CookieManager = new BackOfficeCookieManager( _umbracoContextAccessor, _runtimeState, - _hostingEnvironment, - _globalSettings); // _explicitPaths); TODO: Implement this once we do OAuth somehow + _umbracoRequestPaths); options.Events = new CookieAuthenticationEvents { diff --git a/src/Umbraco.Web.Common/Routing/EndpointRouteBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/EndpointRouteBuilderExtensions.cs similarity index 64% rename from src/Umbraco.Web.Common/Routing/EndpointRouteBuilderExtensions.cs rename to src/Umbraco.Web.Common/Extensions/EndpointRouteBuilderExtensions.cs index 1349145357..ccaa29544b 100644 --- a/src/Umbraco.Web.Common/Routing/EndpointRouteBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/EndpointRouteBuilderExtensions.cs @@ -1,26 +1,18 @@ -using Microsoft.AspNetCore.Builder; +using System; +using System.Text; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using NUglify.Helpers; -using System; -using System.Text; using Umbraco.Extensions; -namespace Umbraco.Web.Common.Routing +namespace Umbraco.Web.Common.Extensions { public static class EndpointRouteBuilderExtensions { /// /// Used to map Umbraco controllers consistently /// - /// - /// - /// - /// - /// - /// - /// - /// public static void MapUmbracoRoute( this IEndpointRouteBuilder endpoints, Type controllerType, @@ -36,19 +28,26 @@ namespace Umbraco.Web.Common.Routing // build the route pattern var pattern = new StringBuilder(rootSegment); if (!prefixPathSegment.IsNullOrWhiteSpace()) + { pattern.Append("/").Append(prefixPathSegment); + } + if (includeControllerNameInRoute) + { pattern.Append("/").Append(controllerName); + } + pattern.Append("/").Append("{action}/{id?}"); var defaults = defaultAction.IsNullOrWhiteSpace() - ? (object) new { controller = controllerName } + ? (object)new { controller = controllerName } : new { controller = controllerName, action = defaultAction }; if (areaName.IsNullOrWhiteSpace()) { endpoints.MapControllerRoute( + // named consistently $"umbraco-{areaName}-{controllerName}".ToLowerInvariant(), pattern.ToString().ToLowerInvariant(), @@ -58,6 +57,7 @@ namespace Umbraco.Web.Common.Routing else { endpoints.MapAreaControllerRoute( + // named consistently $"umbraco-{areaName}-{controllerName}".ToLowerInvariant(), areaName, @@ -65,19 +65,11 @@ namespace Umbraco.Web.Common.Routing defaults, constraints); } - } /// /// Used to map Umbraco controllers consistently /// - /// - /// - /// - /// - /// - /// - /// public static void MapUmbracoRoute( this IEndpointRouteBuilder endpoints, string rootSegment, @@ -92,12 +84,6 @@ namespace Umbraco.Web.Common.Routing /// /// Used to map Umbraco api controllers consistently /// - /// - /// - /// - /// - /// If the route is a back office route - /// public static void MapUmbracoApiRoute( this IEndpointRouteBuilder endpoints, string rootSegment, @@ -111,13 +97,6 @@ namespace Umbraco.Web.Common.Routing /// /// Used to map Umbraco api controllers consistently /// - /// - /// - /// - /// - /// If the route is a back office route - /// - /// public static void MapUmbracoApiRoute( this IEndpointRouteBuilder endpoints, Type controllerType, @@ -126,10 +105,23 @@ namespace Umbraco.Web.Common.Routing bool isBackOffice, string defaultAction = "Index", object constraints = null) - => endpoints.MapUmbracoRoute(controllerType, rootSegment, areaName, - isBackOffice - ? (areaName.IsNullOrWhiteSpace() ? $"{Core.Constants.Web.Mvc.BackOfficePathSegment}/Api" : $"{Core.Constants.Web.Mvc.BackOfficePathSegment}/{areaName}") - : (areaName.IsNullOrWhiteSpace() ? "Api" : areaName), - defaultAction, true, constraints); + { + string prefixPathSegment = isBackOffice + ? areaName.IsNullOrWhiteSpace() + ? $"{Core.Constants.Web.Mvc.BackOfficePathSegment}/Api" + : $"{Core.Constants.Web.Mvc.BackOfficePathSegment}/{areaName}" + : areaName.IsNullOrWhiteSpace() + ? "Api" + : areaName; + + endpoints.MapUmbracoRoute( + controllerType, + rootSegment, + areaName, + prefixPathSegment, + defaultAction, + true, + constraints); + } } } diff --git a/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs b/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs index fe61941e5c..31e65edf65 100644 --- a/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs @@ -1,34 +1,44 @@ -using System; using System.IO; using System.Net; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.Extensions.DependencyInjection; using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Hosting; +using Umbraco.Core.Routing; namespace Umbraco.Extensions { + /// + /// Extension methods for + /// public static class HttpRequestExtensions { - /// /// Check if a preview cookie exist /// public static bool HasPreviewCookie(this HttpRequest request) => request.Cookies.TryGetValue(Constants.Web.PreviewCookieName, out var cookieVal) && !cookieVal.IsNullOrWhiteSpace(); - public static bool IsBackOfficeRequest(this HttpRequest request, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) - => new Uri(request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsBackOfficeRequest(globalSettings, hostingEnvironment); + /// + /// Returns true if the request is a back office request + /// + public static bool IsBackOfficeRequest(this HttpRequest request) + { + PathString absPath = request.Path; + UmbracoRequestPaths umbReqPaths = request.HttpContext.RequestServices.GetService(); + return umbReqPaths.IsBackOfficeRequest(absPath); + } + /// + /// Returns true if the request is for a client side extension + /// public static bool IsClientSideRequest(this HttpRequest request) - => new Uri(request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsClientSideRequest(); - - public static bool IsDefaultBackOfficeRequest(this HttpRequest request, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) - => new Uri(request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsDefaultBackOfficeRequest(globalSettings, hostingEnvironment); + { + PathString absPath = request.Path; + UmbracoRequestPaths umbReqPaths = request.HttpContext.RequestServices.GetService(); + return umbReqPaths.IsClientSideRequest(absPath); + } public static string ClientCulture(this HttpRequest request) => request.Headers.TryGetValue("X-UMB-CULTURE", out var values) ? values[0] : null; diff --git a/src/Umbraco.Web.Common/Install/InstallAreaRoutes.cs b/src/Umbraco.Web.Common/Install/InstallAreaRoutes.cs index b21495e27d..f1fb7220bd 100644 --- a/src/Umbraco.Web.Common/Install/InstallAreaRoutes.cs +++ b/src/Umbraco.Web.Common/Install/InstallAreaRoutes.cs @@ -7,6 +7,7 @@ using Umbraco.Core; using Umbraco.Core.Hosting; using Umbraco.Core.Logging; using Umbraco.Extensions; +using Umbraco.Web.Common.Extensions; using Umbraco.Web.Common.Routing; namespace Umbraco.Web.Common.Install diff --git a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs index 0474f2445c..30381dcb6a 100644 --- a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs +++ b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Logging; +using Umbraco.Extensions; using Umbraco.Web.Common.Lifetime; using Umbraco.Web.PublishedCache.NuCache; @@ -59,10 +60,8 @@ namespace Umbraco.Web.Common.Middleware /// public async Task InvokeAsync(HttpContext context, RequestDelegate next) { - var requestUri = new Uri(context.Request.GetEncodedUrl(), UriKind.RelativeOrAbsolute); - // do not process if client-side request - if (requestUri.IsClientSideRequest()) + if (context.Request.IsClientSideRequest()) { await next(context); return; @@ -75,12 +74,14 @@ namespace Umbraco.Web.Common.Middleware bool isFrontEndRequest = umbracoContextReference.UmbracoContext.IsFrontEndUmbracoRequest(); + var pathAndQuery = context.Request.GetEncodedPathAndQuery(); + try { if (isFrontEndRequest) { LogHttpRequest.TryGetCurrentHttpRequestId(out Guid httpRequestId, _requestCache); - _logger.LogTrace("Begin request [{HttpRequestId}]: {RequestUrl}", httpRequestId, requestUri); + _logger.LogTrace("Begin request [{HttpRequestId}]: {RequestUrl}", httpRequestId, pathAndQuery); } try @@ -109,12 +110,12 @@ namespace Umbraco.Web.Common.Middleware if (isFrontEndRequest) { LogHttpRequest.TryGetCurrentHttpRequestId(out var httpRequestId, _requestCache); - _logger.LogTrace("End Request [{HttpRequestId}]: {RequestUrl} ({RequestDuration}ms)", httpRequestId, requestUri, DateTime.Now.Subtract(umbracoContextReference.UmbracoContext.ObjectCreated).TotalMilliseconds); + _logger.LogTrace("End Request [{HttpRequestId}]: {RequestUrl} ({RequestDuration}ms)", httpRequestId, pathAndQuery, DateTime.Now.Subtract(umbracoContextReference.UmbracoContext.ObjectCreated).TotalMilliseconds); } try { - DisposeRequestCacheItems(_logger, _requestCache, requestUri); + DisposeRequestCacheItems(_logger, _requestCache, context.Request); } finally { @@ -126,10 +127,10 @@ namespace Umbraco.Web.Common.Middleware /// /// Any object that is in the HttpContext.Items collection that is IDisposable will get disposed on the end of the request /// - private static void DisposeRequestCacheItems(ILogger logger, IRequestCache requestCache, Uri requestUri) + private static void DisposeRequestCacheItems(ILogger logger, IRequestCache requestCache, HttpRequest request) { // do not process if client-side request - if (requestUri.IsClientSideRequest()) + if (request.IsClientSideRequest()) { return; } @@ -143,6 +144,7 @@ namespace Umbraco.Web.Common.Middleware keys.Add(i.Key); } } + // dispose each item and key that was found as disposable. foreach (var k in keys) { @@ -154,6 +156,7 @@ namespace Umbraco.Web.Common.Middleware { logger.LogError("Could not dispose item with key " + k, ex); } + try { k.DisposeIfDisposable(); diff --git a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs index b00dede27a..1dc50a3fc2 100644 --- a/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs +++ b/src/Umbraco.Web.Common/Routing/RoutableDocumentFilter.cs @@ -7,10 +7,9 @@ using System.Threading; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Template; using Umbraco.Core; -using Umbraco.Core.Collections; +using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; -using Umbraco.Core.IO; namespace Umbraco.Web.Common.Routing { @@ -27,12 +26,10 @@ namespace Umbraco.Web.Common.Routing private readonly IHostingEnvironment _hostingEnvironment; private readonly EndpointDataSource _endpointDataSource; private readonly object _routeLocker = new object(); - -#pragma warning disable IDE0044 // Add readonly modifier + private readonly List _backOfficePaths; private object _initLocker = new object(); private bool _isInit = false; private HashSet _reservedList; -#pragma warning restore IDE0044 // Add readonly modifier /// /// Initializes a new instance of the class. diff --git a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs index 76a5823801..f7d3e61664 100644 --- a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs +++ b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContext.cs @@ -3,6 +3,7 @@ using Umbraco.Core; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Routing; using Umbraco.Core.Security; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; @@ -14,7 +15,6 @@ namespace Umbraco.Web /// public class UmbracoContext : DisposableObjectSlim, IDisposeOnRequestEnd, IUmbracoContext { - private readonly GlobalSettings _globalSettings; private readonly IHostingEnvironment _hostingEnvironment; private readonly ICookieManager _cookieManager; private readonly IRequestAccessor _requestAccessor; @@ -22,6 +22,7 @@ namespace Umbraco.Web private string _previewToken; private bool? _previewing; private readonly IBackOfficeSecurity _backofficeSecurity; + private readonly UmbracoRequestPaths _umbracoRequestPaths; // initializes a new instance of the UmbracoContext class // internal for unit tests @@ -30,7 +31,7 @@ namespace Umbraco.Web internal UmbracoContext( IPublishedSnapshotService publishedSnapshotService, IBackOfficeSecurity backofficeSecurity, - GlobalSettings globalSettings, + UmbracoRequestPaths umbracoRequestPaths, IHostingEnvironment hostingEnvironment, IVariationContextAccessor variationContextAccessor, UriUtility uriUtility, @@ -43,7 +44,6 @@ namespace Umbraco.Web } VariationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); - _globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings)); _hostingEnvironment = hostingEnvironment; _cookieManager = cookieManager; @@ -52,6 +52,7 @@ namespace Umbraco.Web ObjectCreated = DateTime.Now; UmbracoRequestId = Guid.NewGuid(); _backofficeSecurity = backofficeSecurity ?? throw new ArgumentNullException(nameof(backofficeSecurity)); + _umbracoRequestPaths = umbracoRequestPaths; // beware - we cannot expect a current user here, so detecting preview mode must be a lazy thing _publishedSnapshot = new Lazy(() => publishedSnapshotService.CreatePublishedSnapshot(PreviewToken)); @@ -140,7 +141,7 @@ namespace Umbraco.Web { Uri requestUrl = _requestAccessor.GetRequestUrl(); if (requestUrl != null - && requestUrl.IsBackOfficeRequest(_globalSettings, _hostingEnvironment) == false + && _umbracoRequestPaths.IsBackOfficeRequest(requestUrl.AbsolutePath) == false && _backofficeSecurity.CurrentUser != null) { var previewToken = _cookieManager.GetCookieValue(Constants.Web.PreviewCookieName); // may be null or empty diff --git a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContextFactory.cs b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContextFactory.cs index ac0d776e71..67dfd72bad 100644 --- a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContextFactory.cs +++ b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContextFactory.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Options; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Routing; using Umbraco.Core.Security; using Umbraco.Web.PublishedCache; @@ -18,7 +19,7 @@ namespace Umbraco.Web private readonly IVariationContextAccessor _variationContextAccessor; private readonly IDefaultCultureAccessor _defaultCultureAccessor; - private readonly GlobalSettings _globalSettings; + private readonly UmbracoRequestPaths _umbracoRequestPaths; private readonly IHostingEnvironment _hostingEnvironment; private readonly ICookieManager _cookieManager; private readonly IRequestAccessor _requestAccessor; @@ -33,7 +34,7 @@ namespace Umbraco.Web IPublishedSnapshotService publishedSnapshotService, IVariationContextAccessor variationContextAccessor, IDefaultCultureAccessor defaultCultureAccessor, - IOptions globalSettings, + UmbracoRequestPaths umbracoRequestPaths, IHostingEnvironment hostingEnvironment, UriUtility uriUtility, ICookieManager cookieManager, @@ -44,7 +45,7 @@ namespace Umbraco.Web _publishedSnapshotService = publishedSnapshotService ?? throw new ArgumentNullException(nameof(publishedSnapshotService)); _variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); _defaultCultureAccessor = defaultCultureAccessor ?? throw new ArgumentNullException(nameof(defaultCultureAccessor)); - _globalSettings = globalSettings.Value ?? throw new ArgumentNullException(nameof(globalSettings)); + _umbracoRequestPaths = umbracoRequestPaths ?? throw new ArgumentNullException(nameof(umbracoRequestPaths)); _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); _uriUtility = uriUtility ?? throw new ArgumentNullException(nameof(uriUtility)); _cookieManager = cookieManager ?? throw new ArgumentNullException(nameof(cookieManager)); @@ -75,7 +76,7 @@ namespace Umbraco.Web return new UmbracoContext( _publishedSnapshotService, _backofficeSecurityAccessor.BackOfficeSecurity, - _globalSettings, + _umbracoRequestPaths, _hostingEnvironment, _variationContextAccessor, _uriUtility, diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs index de6cb72edb..32b11bf876 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValueTransformer.cs @@ -71,14 +71,6 @@ namespace Umbraco.Web.Website.Routing return values; } - // Check for back office request - // TODO: This is how the module was doing it before but could just as easily be part of the RoutableDocumentFilter - // which still needs to be migrated. - if (httpContext.Request.IsDefaultBackOfficeRequest(_globalSettings, _hostingEnvironment)) - { - return values; - } - // Check if there is no existing content and return the no content controller if (!_umbracoContextAccessor.UmbracoContext.Content.HasContent()) { diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 8707bea26b..7461364d3f 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -12,18 +12,12 @@ using Umbraco.Web.Security; namespace Umbraco.Web { - /// - /// Class that encapsulates Umbraco information of a specific HTTP request - /// + // NOTE: has all been ported to netcore but exists here just to keep the build working for tests + public class UmbracoContext : DisposableObjectSlim, IDisposeOnRequestEnd, IUmbracoContext { private readonly IHttpContextAccessor _httpContextAccessor; - private readonly GlobalSettings _globalSettings; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly ICookieManager _cookieManager; private readonly Lazy _publishedSnapshot; - private string _previewToken; - private bool? _previewing; // initializes a new instance of the UmbracoContext class // internal for unit tests @@ -44,9 +38,6 @@ namespace Umbraco.Web if (backofficeSecurity == null) throw new ArgumentNullException(nameof(backofficeSecurity)); VariationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor)); _httpContextAccessor = httpContextAccessor; - _globalSettings = globalSettings ?? throw new ArgumentNullException(nameof(globalSettings)); - _hostingEnvironment = hostingEnvironment; - _cookieManager = cookieManager; // ensure that this instance is disposed when the request terminates, though we *also* ensure // this happens in the Umbraco module since the UmbracoCOntext is added to the HttpContext items. @@ -134,68 +125,17 @@ namespace Umbraco.Web /// public IVariationContextAccessor VariationContextAccessor { get; } - /// - /// Gets a value indicating whether the request has debugging enabled - /// - /// true if this instance is debug; otherwise, false. - public bool IsDebug - { - get - { - var request = GetRequestFromContext(); - //NOTE: the request can be null during app startup! - return Current.HostingEnvironment.IsDebugMode - && request != null - && (string.IsNullOrEmpty(request["umbdebugshowtrace"]) == false - || string.IsNullOrEmpty(request["umbdebug"]) == false - || string.IsNullOrEmpty(request.Cookies["UMB-DEBUG"]?.Value) == false); - } - } + // NOTE: has been ported to netcore + public bool IsDebug => false; - /// - /// Determines whether the current user is in a preview mode and browsing the site (ie. not in the admin UI) - /// - public bool InPreviewMode - { - get - { - if (_previewing.HasValue == false) DetectPreviewMode(); - return _previewing ?? false; - } - private set => _previewing = value; - } + // NOTE: has been ported to netcore + public bool InPreviewMode => false; - public string PreviewToken - { - get - { - if (_previewing.HasValue == false) DetectPreviewMode(); - return _previewToken; - } - } + // NOTE: has been ported to netcore + public string PreviewToken => null; - private void DetectPreviewMode() - { - var request = GetRequestFromContext(); - if (request?.Url != null - && request.Url.IsBackOfficeRequest(_globalSettings, _hostingEnvironment) == false - && Security.CurrentUser != null) - { - var previewToken = _cookieManager.GetPreviewCookieValue(); // may be null or empty - _previewToken = previewToken.IsNullOrWhiteSpace() ? null : previewToken; - } - - _previewing = _previewToken.IsNullOrWhiteSpace() == false; - } - - // say we render a macro or RTE in a give 'preview' mode that might not be the 'current' one, - // then due to the way it all works at the moment, the 'current' published snapshot need to be in the proper - // default 'preview' mode - somehow we have to force it. and that could be recursive. - public IDisposable ForcedPreview(bool preview) - { - InPreviewMode = preview; - return PublishedSnapshot.ForcedPreview(preview, orig => InPreviewMode = orig); - } + // NOTE: has been ported to netcore + public IDisposable ForcedPreview(bool preview) => null; private HttpRequestBase GetRequestFromContext() { @@ -209,17 +149,7 @@ namespace Umbraco.Web } } - protected override void DisposeResources() - { - // DisposableObject ensures that this runs only once - - Security.DisposeIfDisposable(); - - // help caches release resources - // (but don't create caches just to dispose them) - // context is not multi-threaded - if (_publishedSnapshot.IsValueCreated) - _publishedSnapshot.Value.Dispose(); - } + // NOTE: has been ported to netcore + protected override void DisposeResources() { } } } diff --git a/src/Umbraco.Web/UmbracoInjectedModule.cs b/src/Umbraco.Web/UmbracoInjectedModule.cs index 50d843932b..b50f4ce23e 100644 --- a/src/Umbraco.Web/UmbracoInjectedModule.cs +++ b/src/Umbraco.Web/UmbracoInjectedModule.cs @@ -3,7 +3,6 @@ using System.Web; using System.Web.Routing; using Microsoft.Extensions.Logging; using Umbraco.Core; -using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Exceptions; @@ -11,7 +10,6 @@ using Umbraco.Core.Hosting; using Umbraco.Core.Security; using Umbraco.Web.Composing; using Umbraco.Web.Routing; -using RouteDirection = Umbraco.Web.Routing.RouteDirection; namespace Umbraco.Web { @@ -34,7 +32,6 @@ namespace Umbraco.Web { private readonly IRuntimeState _runtime; private readonly ILogger _logger; - private readonly IPublishedRouter _publishedRouter; private readonly IUmbracoContextFactory _umbracoContextFactory; private readonly GlobalSettings _globalSettings; private readonly IHostingEnvironment _hostingEnvironment; @@ -42,31 +39,22 @@ namespace Umbraco.Web public UmbracoInjectedModule( IRuntimeState runtime, ILogger logger, - IPublishedRouter publishedRouter, IUmbracoContextFactory umbracoContextFactory, GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) { _runtime = runtime; _logger = logger; - _publishedRouter = publishedRouter; _umbracoContextFactory = umbracoContextFactory; _globalSettings = globalSettings; _hostingEnvironment = hostingEnvironment; } - #region HttpModule event handlers - /// /// Begins to process a request. /// - /// private void BeginRequest(HttpContextBase httpContext) { - // do not process if client-side request - if (httpContext.Request.Url.IsClientSideRequest()) - return; - // write the trace output for diagnostics at the end of the request httpContext.Trace.Write("UmbracoModule", "Umbraco request begins"); @@ -82,69 +70,25 @@ namespace Umbraco.Web /// /// Processes the Umbraco Request /// - /// /// - /// /// This will check if we are trying to route to the default back office page (i.e. ~/Umbraco/ or ~/Umbraco or ~/Umbraco/Default ) /// and ensure that the MVC handler executes for that. This is required because the route for /Umbraco will never execute because /// files/folders exist there and we cannot set the RouteCollection.RouteExistingFiles = true since that will muck a lot of other things up. /// So we handle it here and explicitly execute the MVC controller. - /// /// void ProcessRequest(HttpContextBase httpContext) { - // do not process if client-side request - if (httpContext.Request.Url.IsClientSideRequest()) - return; - - if (Current.UmbracoContext == null) - throw new InvalidOperationException("The Current.UmbracoContext is null, ProcessRequest cannot proceed unless there is a current UmbracoContext"); var umbracoContext = Current.UmbracoContext; - // re-write for the default back office path - if (httpContext.Request.Url.IsDefaultBackOfficeRequest(_globalSettings, _hostingEnvironment)) - { - if (EnsureRuntime(httpContext, umbracoContext.OriginalRequestUrl)) - RewriteToBackOfficeHandler(httpContext); - return; - } - // do not process if this request is not a front-end routable page var isRoutableAttempt = EnsureUmbracoRoutablePage(umbracoContext, httpContext); // raise event here UmbracoModule.OnRouteAttempt(this, new RoutableAttemptEventArgs(isRoutableAttempt.Result, umbracoContext)); if (isRoutableAttempt.Success == false) return; - - httpContext.Trace.Write("UmbracoModule", "Umbraco request confirmed"); - - // ok, process - - // note: requestModule.UmbracoRewrite also did some stripping of &umbPage - // from the querystring... that was in v3.x to fix some issues with pre-forms - // auth. Paul Sterling confirmed in Jan. 2013 that we can get rid of it. - - // instantiate, prepare and process the published content request - // important to use CleanedUmbracoUrl - lowercase path-only version of the current URL - var requestBuilder = _publishedRouter.CreateRequestAsync(umbracoContext.CleanedUmbracoUrl).Result; - var request = umbracoContext.PublishedRequest = _publishedRouter.RouteRequestAsync(requestBuilder, new RouteRequestOptions(RouteDirection.Inbound)).Result; - - // NOTE: This has been ported to netcore - // HandleHttpResponseStatus returns a value indicating that the request should - // not be processed any further, eg because it has been redirect. then, exit. - //if (UmbracoModule.HandleHttpResponseStatus(httpContext, request, _logger)) - // return; - //if (request.HasPublishedContent() == false) - // httpContext.RemapHandler(new PublishedContentNotFoundHandler()); - //else - // RewriteToUmbracoHandler(httpContext, request); } - #endregion - - #region Methods - /// /// Checks the current request and ensures that it is routable based on the structure of the request and URI /// @@ -251,7 +195,6 @@ namespace Umbraco.Web } - #endregion #region IHttpModule