Merge branch 'netcore/netcore' into netcore/members-userstore

This commit is contained in:
Emma Garland
2021-01-12 14:28:47 +00:00
36 changed files with 797 additions and 860 deletions

View File

@@ -10,6 +10,17 @@ namespace Umbraco.Core.Configuration.Models
/// </summary>
public class WebRoutingSettings
{
/// <summary>
/// Gets or sets a value indicating whether to check if any routed endpoints match a front-end request before
/// the Umbraco dynamic router tries to map the request to an Umbraco content item.
/// </summary>
/// <remarks>
/// This should not be necessary if the Umbraco catch-all/dynamic route is registered last like it's supposed to be. In that case
/// ASP.NET Core will automatically handle this in all cases. This is more of a backward compatible option since this is what v7/v8 used
/// to do.
/// </remarks>
public bool TryMatchingEndpointsForAllPages { get; set; } = false;
/// <summary>
/// Gets or sets a value indicating whether IIS custom errors should be skipped.
/// </summary>

View File

@@ -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<UmbracoApplicationStarting, EssentialDirectoryCreator>();
Services.AddSingleton<ManifestWatcher>();
Services.AddSingleton<UmbracoRequestPaths>();
this.AddNotificationHandler<UmbracoApplicationStarting, AppPluginsManifestWatcherNotificationHandler>();
Services.AddUnique<InstallStatusTracker>();

View File

@@ -0,0 +1,133 @@
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
{
/// <summary>
/// Utility for checking paths
/// </summary>
public class UmbracoRequestPaths
{
private readonly string _backOfficePath;
private readonly string _mvcArea;
private readonly string _backOfficeMvcPath;
private readonly string _previewMvcPath;
private readonly string _surfaceMvcPath;
private readonly string _apiMvcPath;
private readonly string _installPath;
private readonly string _appPath;
private readonly List<string> _defaultUmbPaths;
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoRequestPaths"/> class.
/// </summary>
public UmbracoRequestPaths(IOptions<GlobalSettings> 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);
_defaultUmbPaths = new List<string> { "/" + _mvcArea, "/" + _mvcArea + "/" };
_backOfficeMvcPath = "/" + _mvcArea + "/BackOffice/";
_previewMvcPath = "/" + _mvcArea + "/Preview/";
_surfaceMvcPath = "/" + _mvcArea + "/Surface/";
_apiMvcPath = "/" + _mvcArea + "/Api/";
_installPath = hostingEnvironment.ToAbsolute(Constants.SystemDirectories.Install);
}
/// <summary>
/// Checks if the current uri is a back office request
/// </summary>
/// <remarks>
/// <para>
/// There are some special routes we need to check to properly determine this:
/// </para>
/// <para>
/// These are def back office:
/// /Umbraco/BackOffice = back office
/// /Umbraco/Preview = back office
/// </para>
/// <para>
/// If it's not any of the above 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.
/// </para>
/// <para>
/// 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.
/// </para>
/// </remarks>
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 (_defaultUmbPaths.Any(x => urlPath.InvariantEquals(x)))
{
return true;
}
// check for special back office paths
if (urlPath.InvariantStartsWith(_backOfficeMvcPath)
|| urlPath.InvariantStartsWith(_previewMvcPath))
{
return true;
}
// check for special front-end paths
if (urlPath.InvariantStartsWith(_surfaceMvcPath)
|| urlPath.InvariantStartsWith(_apiMvcPath))
{
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;
}
/// <summary>
/// Checks if the current uri is an install request
/// </summary>
public bool IsInstallerRequest(string absPath) => absPath.InvariantStartsWith(_installPath);
/// <summary>
/// Rudimentary check to see if it's not a server side request
/// </summary>
public bool IsClientSideRequest(string absPath)
{
var ext = Path.GetExtension(absPath);
return !ext.IsNullOrWhiteSpace();
}
}
}

View File

@@ -13,151 +13,6 @@ namespace Umbraco.Core
/// </summary>
public static class UriExtensions
{
/// <summary>
/// Checks if the current uri is a back office request
/// </summary>
/// <param name="url"></param>
/// <param name="globalSettings"></param>
/// <param name="hostingEnvironment"></param>
/// <returns></returns>
/// <remarks>
/// 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.
///
/// </remarks>
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;
}
/// <summary>
/// Checks if the current uri is an install request
/// </summary>
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("/"));
}
/// <summary>
/// Checks if the uri is a request for the default back office page
/// </summary>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
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;
}
}
/// <summary>
/// Rewrites the path of uri.
/// </summary>

View File

@@ -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();
}
}

View File

@@ -6,8 +6,10 @@ 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.Tests.TestHelpers;
using Umbraco.Web;
using Umbraco.Web.Common.AspNetCore;
using Umbraco.Web.PublishedCache;
@@ -54,7 +56,8 @@ namespace Umbraco.Tests.UnitTests.TestHelpers.Objects
var snapshotService = new Mock<IPublishedSnapshotService>();
snapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny<string>())).Returns(snapshot.Object);
IHostingEnvironment hostingEnvironment = Mock.Of<IHostingEnvironment>();
IHostingEnvironment hostingEnvironment = TestHelper.GetHostingEnvironment();
var backofficeSecurityAccessorMock = new Mock<IBackOfficeSecurityAccessor>();
backofficeSecurityAccessorMock.Setup(x => x.BackOfficeSecurity).Returns(Mock.Of<IBackOfficeSecurity>());
@@ -63,7 +66,7 @@ namespace Umbraco.Tests.UnitTests.TestHelpers.Objects
snapshotService.Object,
new TestVariationContextAccessor(),
new TestDefaultCultureAccessor(),
Options.Create<GlobalSettings>(globalSettings),
new UmbracoRequestPaths(Options.Create(globalSettings), hostingEnvironment),
hostingEnvironment,
new UriUtility(hostingEnvironment),
new AspNetCoreCookieManager(httpContextAccessor),

View File

@@ -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 };

View File

@@ -0,0 +1,105 @@
using System;
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;
using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment;
namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing
{
[TestFixture]
public class UmbracoRequestPathsTests
{
private IWebHostEnvironment _hostEnvironment;
private GlobalSettings _globalSettings;
[OneTimeSetUp]
public void Setup()
{
_hostEnvironment = Mock.Of<IWebHostEnvironment>();
_globalSettings = new GlobalSettings();
}
private AspNetCoreHostingEnvironment CreateHostingEnvironment(string virtualPath = "")
{
var hostingSettings = new HostingSettings { ApplicationVirtualPath = virtualPath };
var mockedOptionsMonitorOfHostingSettings = Mock.Of<IOptionsMonitor<HostingSettings>>(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", true)] // has ext, assume client side
[TestCase("http://www.domain.com/Umbraco/test/test.aspx", true)] // has ext, assume client side
[TestCase("http://www.domain.com/umbraco/test/test.js", true)]
public void Is_Client_Side_Request(string url, bool assert)
{
IHostingEnvironment hostingEnvironment = CreateHostingEnvironment();
var umbracoRequestPaths = new UmbracoRequestPaths(Options.Create(_globalSettings), hostingEnvironment);
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()
{
IHostingEnvironment hostingEnvironment = CreateHostingEnvironment();
var umbracoRequestPaths = new UmbracoRequestPaths(Options.Create(_globalSettings), hostingEnvironment);
// 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/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)]
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));
}
}
}

View File

@@ -1,13 +1,15 @@
// Copyright (c) Umbraco.
// 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.Tests.TestHelpers;
using Umbraco.Web;
using Umbraco.Web.BackOffice.Controllers;
using Umbraco.Web.BackOffice.Security;
@@ -26,10 +28,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security
var mgr = new BackOfficeCookieManager(
Mock.Of<IUmbracoContextAccessor>(),
runtime,
Mock.Of<IHostingEnvironment>(),
globalSettings);
new UmbracoRequestPaths(Options.Create(globalSettings), TestHelper.GetHostingEnvironment()));
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<IUmbracoContextAccessor>(),
runtime,
Mock.Of<IHostingEnvironment>(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco"),
globalSettings);
new UmbracoRequestPaths(
Options.Create(globalSettings),
Mock.Of<IHostingEnvironment>(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<IUmbracoContextAccessor>(),
runtime,
Mock.Of<IHostingEnvironment>(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install"),
globalSettings);
new UmbracoRequestPaths(
Options.Create(globalSettings),
Mock.Of<IHostingEnvironment>(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<IUmbracoContextAccessor>(),
runtime,
Mock.Of<IHostingEnvironment>(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco" && x.ToAbsolute(Constants.SystemDirectories.Install) == "/install"),
globalSettings);
new UmbracoRequestPaths(
Options.Create(globalSettings),
Mock.Of<IHostingEnvironment>(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);
}

View File

@@ -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

View File

@@ -0,0 +1,144 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
using Umbraco.Web.Common.Routing;
namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing
{
[TestFixture]
public class RoutableDocumentFilterTests
{
private IOptions<GlobalSettings> GetGlobalSettings() => Options.Create(new GlobalSettings());
private IOptions<WebRoutingSettings> GetWebRoutingSettings() => Options.Create(new WebRoutingSettings());
private IHostingEnvironment GetHostingEnvironment()
{
var hostingEnv = new Mock<IHostingEnvironment>();
hostingEnv.Setup(x => x.ToAbsolute(It.IsAny<string>())).Returns((string virtualPath) => virtualPath.TrimStart('~', '/'));
return hostingEnv.Object;
}
[TestCase("/umbraco/editContent.aspx")]
[TestCase("/install/default.aspx")]
[TestCase("/install/")]
[TestCase("/install")]
[TestCase("/install/?installStep=asdf")]
[TestCase("/install/test.aspx")]
public void Is_Reserved_Path_Or_Url(string url)
{
var routableDocFilter = new RoutableDocumentFilter(
GetGlobalSettings(),
GetWebRoutingSettings(),
GetHostingEnvironment(),
new DefaultEndpointDataSource());
// Will be false if it is a reserved path
Assert.IsFalse(routableDocFilter.IsDocumentRequest(url));
}
[TestCase("/base/somebasehandler")]
[TestCase("/")]
[TestCase("/home")]
[TestCase("/umbraco-test")]
[TestCase("/install-test")]
public void Is_Not_Reserved_Path_Or_Url(string url)
{
var routableDocFilter = new RoutableDocumentFilter(
GetGlobalSettings(),
GetWebRoutingSettings(),
GetHostingEnvironment(),
new DefaultEndpointDataSource());
// Will be true if it's not reserved
Assert.IsTrue(routableDocFilter.IsDocumentRequest(url));
}
[TestCase("/Do/Not/match", false)]
[TestCase("/Umbraco/RenderMvcs", false)]
[TestCase("/Umbraco/RenderMvc", true)]
[TestCase("/umbraco/RenderMvc/Index", true)]
[TestCase("/Umbraco/RenderMvc/Index/1234", true)]
[TestCase("/Umbraco/RenderMvc/Index/1234/", true)]
[TestCase("/Umbraco/RenderMvc/Index/1234/9876", false)]
[TestCase("/api", true)]
[TestCase("/api/WebApiTest", true)]
[TestCase("/Api/WebApiTest/1234", true)]
[TestCase("/api/WebApiTest/Index/1234", false)]
public void Is_Reserved_By_Route(string url, bool isReserved)
{
var globalSettings = new GlobalSettings { ReservedPaths = string.Empty, ReservedUrls = string.Empty };
var routingSettings = new WebRoutingSettings { TryMatchingEndpointsForAllPages = true };
RouteEndpoint endpoint1 = CreateEndpoint(
"Umbraco/RenderMvc/{action?}/{id?}",
new { controller = "RenderMvc" },
0);
RouteEndpoint endpoint2 = CreateEndpoint(
"api/{controller?}/{id?}",
new { action = "Index" },
1);
var endpointDataSource = new DefaultEndpointDataSource(endpoint1, endpoint2);
var routableDocFilter = new RoutableDocumentFilter(
Options.Create(globalSettings),
Options.Create(routingSettings),
GetHostingEnvironment(),
endpointDataSource);
Assert.AreEqual(
!isReserved, // not reserved if it's a document request
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 };
var routingSettings = new WebRoutingSettings { TryMatchingEndpointsForAllPages = true };
RouteEndpoint endpoint1 = CreateEndpoint(
"umbraco/{action?}/{id?}",
new { controller = "BackOffice" },
0);
var endpointDataSource = new DefaultEndpointDataSource(endpoint1);
var routableDocFilter = new RoutableDocumentFilter(
Options.Create(globalSettings),
Options.Create(routingSettings),
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,
int order = 0) => new RouteEndpoint(
(httpContext) => Task.CompletedTask,
RoutePatternFactory.Parse(template, defaults, null),
order,
new EndpointMetadataCollection(Array.Empty<object>()),
null);
}
}

View File

@@ -5,7 +5,6 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Options;
using Moq;
using NUnit.Framework;
using Umbraco.Core.Cache;
@@ -16,13 +15,12 @@ using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Tests.Common;
using Umbraco.Tests.Testing;
using Umbraco.Tests.UnitTests.TestHelpers.Objects;
using Umbraco.Web;
using Umbraco.Web.Common.Routing;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
using Umbraco.Web.Website;
using Umbraco.Web.Website.Controllers;
using Umbraco.Web.Website.Routing;
using CoreConstants = Umbraco.Core.Constants;
namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers
@@ -44,17 +42,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers
Mock.Get(backofficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(Mock.Of<IBackOfficeSecurity>());
var globalSettings = new GlobalSettings();
var umbracoContextFactory = new UmbracoContextFactory(
_umbracoContextAccessor,
Mock.Of<IPublishedSnapshotService>(),
new TestVariationContextAccessor(),
new TestDefaultCultureAccessor(),
Options.Create(globalSettings),
hostingEnvironment,
new UriUtility(hostingEnvironment),
Mock.Of<ICookieManager>(),
Mock.Of<IRequestAccessor>(),
backofficeSecurityAccessor);
var umbracoContextFactory = TestUmbracoContextFactory.Create(globalSettings, _umbracoContextAccessor);
UmbracoContextReference umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext();
IUmbracoContext umbracoContext = umbracoContextReference.UmbracoContext;
@@ -75,17 +63,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers
IHostingEnvironment hostingEnvironment = Mock.Of<IHostingEnvironment>();
IBackOfficeSecurityAccessor backofficeSecurityAccessor = Mock.Of<IBackOfficeSecurityAccessor>();
Mock.Get(backofficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(Mock.Of<IBackOfficeSecurity>());
var umbracoContextFactory = new UmbracoContextFactory(
_umbracoContextAccessor,
Mock.Of<IPublishedSnapshotService>(),
new TestVariationContextAccessor(),
new TestDefaultCultureAccessor(),
Options.Create(globalSettings),
hostingEnvironment,
new UriUtility(hostingEnvironment),
Mock.Of<ICookieManager>(),
Mock.Of<IRequestAccessor>(),
backofficeSecurityAccessor);
var umbracoContextFactory = TestUmbracoContextFactory.Create(globalSettings, _umbracoContextAccessor);
UmbracoContextReference umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext();
IUmbracoContext umbCtx = umbracoContextReference.UmbracoContext;
@@ -110,17 +88,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers
IHostingEnvironment hostingEnvironment = Mock.Of<IHostingEnvironment>();
var globalSettings = new GlobalSettings();
var umbracoContextFactory = new UmbracoContextFactory(
_umbracoContextAccessor,
publishedSnapshotService.Object,
new TestVariationContextAccessor(),
new TestDefaultCultureAccessor(),
Options.Create(globalSettings),
hostingEnvironment,
new UriUtility(hostingEnvironment),
Mock.Of<ICookieManager>(),
Mock.Of<IRequestAccessor>(),
backofficeSecurityAccessor);
var umbracoContextFactory = TestUmbracoContextFactory.Create(globalSettings, _umbracoContextAccessor);
UmbracoContextReference umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext();
IUmbracoContext umbracoContext = umbracoContextReference.UmbracoContext;
@@ -144,17 +112,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers
IHostingEnvironment hostingEnvironment = Mock.Of<IHostingEnvironment>();
IBackOfficeSecurityAccessor backofficeSecurityAccessor = Mock.Of<IBackOfficeSecurityAccessor>();
Mock.Get(backofficeSecurityAccessor).Setup(x => x.BackOfficeSecurity).Returns(Mock.Of<IBackOfficeSecurity>());
var umbracoContextFactory = new UmbracoContextFactory(
_umbracoContextAccessor,
Mock.Of<IPublishedSnapshotService>(),
new TestVariationContextAccessor(),
new TestDefaultCultureAccessor(),
Options.Create(globalSettings),
hostingEnvironment,
new UriUtility(hostingEnvironment),
Mock.Of<ICookieManager>(),
Mock.Of<IRequestAccessor>(),
backofficeSecurityAccessor);
var umbracoContextFactory = TestUmbracoContextFactory.Create(globalSettings, _umbracoContextAccessor);
UmbracoContextReference umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext();
IUmbracoContext umbracoContext = umbracoContextReference.UmbracoContext;

View File

@@ -1,79 +0,0 @@
using System;
using System.Web.Mvc;
using System.Web.Routing;
using NUnit.Framework;
using Umbraco.Core.Configuration.Models;
using Umbraco.Tests.Common.Builders;
using Umbraco.Tests.TestHelpers;
using Umbraco.Web;
namespace Umbraco.Tests.Routing
{
[TestFixture]
public class RoutableDocumentFilterTests : BaseWebTest
{
[TestCase("/umbraco/editContent.aspx")]
[TestCase("/install/default.aspx")]
[TestCase("/install/")]
[TestCase("/install")]
[TestCase("/install/?installStep=asdf")]
[TestCase("/install/test.aspx")]
public void Is_Reserved_Path_Or_Url(string url)
{
var globalSettings = TestObjects.GetGlobalSettings();
var routableDocFilter = new RoutableDocumentFilter(globalSettings, IOHelper);
Assert.IsTrue(routableDocFilter.IsReservedPathOrUrl(url));
}
[TestCase("/base/somebasehandler")]
[TestCase("/")]
[TestCase("/home.aspx")]
[TestCase("/umbraco-test")]
[TestCase("/install-test")]
[TestCase("/install.aspx")]
public void Is_Not_Reserved_Path_Or_Url(string url)
{
var globalSettings = TestObjects.GetGlobalSettings();
var routableDocFilter = new RoutableDocumentFilter(globalSettings, IOHelper);
Assert.IsFalse(routableDocFilter.IsReservedPathOrUrl(url));
}
[TestCase("/Do/Not/match", false)]
[TestCase("/Umbraco/RenderMvcs", false)]
[TestCase("/Umbraco/RenderMvc", true)]
[TestCase("/Umbraco/RenderMvc/Index", true)]
[TestCase("/Umbraco/RenderMvc/Index/1234", true)]
[TestCase("/Umbraco/RenderMvc/Index/1234/9876", false)]
[TestCase("/api", true)]
[TestCase("/api/WebApiTest", true)]
[TestCase("/api/WebApiTest/1234", true)]
[TestCase("/api/WebApiTest/Index/1234", false)]
public void Is_Reserved_By_Route(string url, bool shouldMatch)
{
//reset the app config, we only want to test routes not the hard coded paths
var globalSettings = new GlobalSettings { ReservedPaths = string.Empty, ReservedUrls = string.Empty };
var routableDocFilter = new RoutableDocumentFilter(globalSettings, IOHelper);
var routes = new RouteCollection();
routes.MapRoute(
"Umbraco_default",
"Umbraco/RenderMvc/{action}/{id}",
new { controller = "RenderMvc", action = "Index", id = UrlParameter.Optional });
routes.MapRoute(
"WebAPI",
"api/{controller}/{id}",
new { controller = "WebApiTestController", action = "Index", id = UrlParameter.Optional });
var context = new FakeHttpContextFactory(url);
Assert.AreEqual(
shouldMatch,
routableDocFilter.IsReservedPathOrUrl(url, context.HttpContext, routes));
}
}
}

View File

@@ -32,9 +32,7 @@ namespace Umbraco.Tests.Routing
(
runtime,
logger,
null, // FIXME: PublishedRouter complexities...
Mock.Of<IUmbracoContextFactory>(),
new RoutableDocumentFilter(globalSettings, IOHelper),
globalSettings,
HostingEnvironment
);
@@ -78,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.

View File

@@ -150,7 +150,6 @@
<Compile Include="PublishedContent\PublishedContentSnapshotTestBase.cs" />
<Compile Include="PublishedContent\NuCacheTests.cs" />
<Compile Include="Routing\MediaUrlProviderTests.cs" />
<Compile Include="Routing\RoutableDocumentFilterTests.cs" />
<Compile Include="Routing\GetContentUrlsTests.cs" />
<Compile Include="TestHelpers\RandomIdRamDirectory.cs" />
<Compile Include="TestHelpers\Stubs\TestUserPasswordConfig.cs" />

View File

@@ -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;
}

View File

@@ -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
/// </summary>
public class PreviewAuthenticationMiddleware : IMiddleware
{
private readonly GlobalSettings _globalSettings;
private readonly IHostingEnvironment _hostingEnvironment;
public PreviewAuthenticationMiddleware(
IOptions<GlobalSettings> globalSettings,
IHostingEnvironment hostingEnvironment)
{
_globalSettings = globalSettings.Value;
_hostingEnvironment = hostingEnvironment;
}
/// <inheritdoc/>
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);
}
}
}

View File

@@ -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
/// <summary>
/// Map the minimal routes required to load the back office login and auth
/// </summary>
/// <param name="endpoints"></param>
private void MapMinimalBackOffice(IEndpointRouteBuilder endpoints)
{
endpoints.MapUmbracoRoute<BackOfficeController>(_umbracoPathSegment, Constants.Web.Mvc.BackOfficeArea,
endpoints.MapUmbracoRoute<BackOfficeController>(
_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).

View File

@@ -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

View File

@@ -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;
/// <summary>
/// Initializes a new instance of the <see cref="BackOfficeCookieManager"/> 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)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BackOfficeCookieManager"/> class.
@@ -44,21 +38,18 @@ namespace Umbraco.Web.BackOffice.Security
public BackOfficeCookieManager(
IUmbracoContextAccessor umbracoContextAccessor,
IRuntimeState runtime,
IHostingEnvironment hostingEnvironment,
GlobalSettings globalSettings,
IEnumerable<string> explicitPaths)
IEnumerable<string> explicitPaths,
UmbracoRequestPaths umbracoRequestPaths)
{
_umbracoContextAccessor = umbracoContextAccessor;
_runtime = runtime;
_hostingEnvironment = hostingEnvironment;
_globalSettings = globalSettings;
_explicitPaths = explicitPaths?.ToArray();
_umbracoRequestPaths = umbracoRequestPaths;
}
/// <summary>
/// Determines if we should authenticate the request
/// </summary>
/// <param name="requestUri">The <see cref="Uri"/> to check</param>
/// <returns>true if the request should be authenticated</returns>
/// <remarks>
/// We auth the request when:
@@ -66,7 +57,7 @@ namespace Umbraco.Web.BackOffice.Security
/// * it is an installer request
/// * it is a preview request
/// </remarks>
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
/// <inheritdoc/>
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);
}

View File

@@ -1,3 +1,5 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
using System.Security.Claims;
@@ -8,15 +10,12 @@ 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;
using Umbraco.Infrastructure.Security;
namespace Umbraco.Web.BackOffice.Security
{
using ICookieManager = Microsoft.AspNetCore.Authentication.Cookies.ICookieManager;
/// <summary>
/// Used to validate a cookie against a user's session id
/// </summary>
@@ -37,21 +36,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> globalSettings, IHostingEnvironment hostingEnvironment, IBackOfficeUserManager userManager)
/// <summary>
/// Initializes a new instance of the <see cref="BackOfficeSessionIdValidator"/> class.
/// </summary>
public BackOfficeSessionIdValidator(ISystemClock systemClock, IOptions<GlobalSettings> 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);
@@ -65,7 +67,7 @@ namespace Umbraco.Web.BackOffice.Security
private async Task<bool> ValidateSessionAsync(
TimeSpan validateInterval,
HttpContext httpContext,
ICookieManager cookieManager,
Microsoft.AspNetCore.Authentication.Cookies.ICookieManager cookieManager,
ISystemClock systemClock,
DateTimeOffset? authTicketIssueDate,
ClaimsIdentity currentIdentity)
@@ -82,7 +84,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)
@@ -93,7 +95,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;
@@ -108,18 +110,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,

View File

@@ -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;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigureBackOfficeCookieOptions"/> 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;
}
/// <inheritdoc />
@@ -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
{

View File

@@ -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
{
/// <summary>
/// Used to map Umbraco controllers consistently
/// </summary>
/// <param name="endpoints"></param>
/// <param name="controllerType"></param>
/// <param name="rootSegment"></param>
/// <param name="areaName"></param>
/// <param name="prefixPathSegment"></param>
/// <param name="defaultAction"></param>
/// <param name="includeControllerNameInRoute"></param>
/// <param name="constraints"></param>
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);
}
}
/// <summary>
/// Used to map Umbraco controllers consistently
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="endpoints"></param>
/// <param name="rootSegment"></param>
/// <param name="areaName"></param>
/// <param name="prefixPathSegment"></param>
/// <param name="defaultAction"></param>
/// <param name="constraints"></param>
public static void MapUmbracoRoute<T>(
this IEndpointRouteBuilder endpoints,
string rootSegment,
@@ -92,12 +84,6 @@ namespace Umbraco.Web.Common.Routing
/// <summary>
/// Used to map Umbraco api controllers consistently
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="endpoints"></param>
/// <param name="rootSegment"></param>
/// <param name="areaName"></param>
/// <param name="isBackOffice">If the route is a back office route</param>
/// <param name="constraints"></param>
public static void MapUmbracoApiRoute<T>(
this IEndpointRouteBuilder endpoints,
string rootSegment,
@@ -111,13 +97,6 @@ namespace Umbraco.Web.Common.Routing
/// <summary>
/// Used to map Umbraco api controllers consistently
/// </summary>
/// <param name="endpoints"></param>
/// <param name="controllerType"></param>
/// <param name="rootSegment"></param>
/// <param name="areaName"></param>
/// <param name="isBackOffice">If the route is a back office route</param>
/// <param name="defaultAction"></param>
/// <param name="constraints"></param>
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);
}
}
}

View File

@@ -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
{
/// <summary>
/// Extension methods for <see cref="HttpRequest"/>
/// </summary>
public static class HttpRequestExtensions
{
/// <summary>
/// Check if a preview cookie exist
/// </summary>
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);
/// <summary>
/// Returns true if the request is a back office request
/// </summary>
public static bool IsBackOfficeRequest(this HttpRequest request)
{
PathString absPath = request.Path;
UmbracoRequestPaths umbReqPaths = request.HttpContext.RequestServices.GetService<UmbracoRequestPaths>();
return umbReqPaths.IsBackOfficeRequest(absPath);
}
/// <summary>
/// Returns true if the request is for a client side extension
/// </summary>
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<UmbracoRequestPaths>();
return umbReqPaths.IsClientSideRequest(absPath);
}
public static string ClientCulture(this HttpRequest request)
=> request.Headers.TryGetValue("X-UMB-CULTURE", out var values) ? values[0] : null;

View File

@@ -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

View File

@@ -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
/// <inheritdoc/>
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
/// <summary>
/// Any object that is in the HttpContext.Items collection that is IDisposable will get disposed on the end of the request
/// </summary>
private static void DisposeRequestCacheItems(ILogger<UmbracoRequestMiddleware> logger, IRequestCache requestCache, Uri requestUri)
private static void DisposeRequestCacheItems(ILogger<UmbracoRequestMiddleware> 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();

View File

@@ -0,0 +1,198 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Template;
using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Hosting;
namespace Umbraco.Web.Common.Routing
{
/// <summary>
/// Utility class used to check if the current request is for a front-end request
/// </summary>
/// <remarks>
/// There are various checks to determine if this is a front-end request such as checking if the request is part of any reserved paths or existing MVC routes.
/// </remarks>
public sealed class RoutableDocumentFilter
{
private readonly ConcurrentDictionary<string, bool> _routeChecks = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
private readonly GlobalSettings _globalSettings;
private readonly WebRoutingSettings _routingSettings;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly EndpointDataSource _endpointDataSource;
private readonly object _routeLocker = new object();
private object _initLocker = new object();
private bool _isInit = false;
private HashSet<string> _reservedList;
/// <summary>
/// Initializes a new instance of the <see cref="RoutableDocumentFilter"/> class.
/// </summary>
public RoutableDocumentFilter(IOptions<GlobalSettings> globalSettings, IOptions<WebRoutingSettings> routingSettings, IHostingEnvironment hostingEnvironment, EndpointDataSource endpointDataSource)
{
_globalSettings = globalSettings.Value;
_routingSettings = routingSettings.Value;
_hostingEnvironment = hostingEnvironment;
_endpointDataSource = endpointDataSource;
_endpointDataSource.GetChangeToken().RegisterChangeCallback(EndpointsChanged, null);
}
private void EndpointsChanged(object value)
{
lock (_routeLocker)
{
// try clearing each entry
foreach (var r in _routeChecks.Keys.ToList())
{
_routeChecks.TryRemove(r, out _);
}
// re-register after it has changed so we keep listening
_endpointDataSource.GetChangeToken().RegisterChangeCallback(EndpointsChanged, null);
}
}
/// <summary>
/// Checks if the request is a document request (i.e. one that the module should handle)
/// </summary>
public bool IsDocumentRequest(string absPath)
{
var maybeDoc = true;
// a document request should be
// /foo/bar/nil
// /foo/bar/nil/
// where /foo is not a reserved path
// if the path contains an extension
// then it cannot be a document request
var extension = Path.GetExtension(absPath);
if (maybeDoc && !extension.IsNullOrWhiteSpace())
{
maybeDoc = false;
}
// at that point we have no extension
// if the path is reserved then it cannot be a document request
if (maybeDoc && IsReservedPathOrUrl(absPath))
{
maybeDoc = false;
}
return maybeDoc;
}
/// <summary>
/// Determines whether the specified URL is reserved or is inside a reserved path.
/// </summary>
/// <param name="absPath">The Path of the URL to check.</param>
/// <returns>
/// <c>true</c> if the specified URL is reserved; otherwise, <c>false</c>.
/// </returns>
private bool IsReservedPathOrUrl(string absPath)
{
LazyInitializer.EnsureInitialized(ref _reservedList, ref _isInit, ref _initLocker, () =>
{
// store references to strings to determine changes
var reservedPathsCache = _globalSettings.ReservedPaths;
var reservedUrlsCache = _globalSettings.ReservedUrls;
// add URLs and paths to a new list
var newReservedList = new HashSet<string>();
foreach (var reservedUrlTrimmed in NormalizePaths(reservedUrlsCache, false))
{
newReservedList.Add(reservedUrlTrimmed);
}
foreach (var reservedPathTrimmed in NormalizePaths(reservedPathsCache, true))
{
newReservedList.Add(reservedPathTrimmed);
}
// use the new list from now on
return newReservedList;
});
// The URL should be cleaned up before checking:
// * If it doesn't contain an '.' in the path then we assume it is a path based URL, if that is the case we should add an trailing '/' because all of our reservedPaths use a trailing '/'
// * We shouldn't be comparing the query at all
if (absPath.Contains('?'))
{
absPath = absPath.Split('?', StringSplitOptions.RemoveEmptyEntries)[0];
}
if (absPath.Contains('.') == false)
{
absPath = absPath.EnsureEndsWith('/');
}
// return true if URL starts with an element of the reserved list
var isReserved = _reservedList.Any(x => absPath.InvariantStartsWith(x));
if (isReserved)
{
return true;
}
// If configured, check if the current request matches a route, if so then it is reserved,
// else if not configured (default) proceed as normal since we assume the request is for an Umbraco content item.
var hasRoute = _routingSettings.TryMatchingEndpointsForAllPages && _routeChecks.GetOrAdd(absPath, x => MatchesEndpoint(absPath));
if (hasRoute)
{
return true;
}
return false;
}
private IEnumerable<string> NormalizePaths(string paths, bool ensureTrailingSlash) => paths
.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim().ToLowerInvariant())
.Where(x => x.IsNullOrWhiteSpace() == false)
.Select(reservedPath =>
{
var r = _hostingEnvironment.ToAbsolute(reservedPath).Trim().EnsureStartsWith('/');
return ensureTrailingSlash
? r.EnsureEndsWith('/')
: r;
})
.Where(reservedPathTrimmed => reservedPathTrimmed.IsNullOrWhiteSpace() == false);
private bool MatchesEndpoint(string absPath)
{
// Borrowed and modified from https://stackoverflow.com/a/59550580
// Return a collection of Microsoft.AspNetCore.Http.Endpoint instances.
IEnumerable<RouteEndpoint> routeEndpoints = _endpointDataSource?.Endpoints
.OfType<RouteEndpoint>()
.Where(x =>
{
// We don't want to include dynamic endpoints in this check since we would have no idea if that
// matches since they will probably match everything.
bool isDynamic = x.Metadata.OfType<IDynamicEndpointMetadata>().Any(x => x.IsDynamic);
return !isDynamic;
});
var routeValues = new RouteValueDictionary();
// To get the matchedEndpoint of the provide url
RouteEndpoint matchedEndpoint = routeEndpoints
.Where(e => new TemplateMatcher(
TemplateParser.Parse(e.RoutePattern.RawText),
new RouteValueDictionary())
.TryMatch(absPath, routeValues))
.OrderBy(c => c.Order)
.FirstOrDefault();
return matchedEndpoint != null;
}
}
}

View File

@@ -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
/// </summary>
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<IPublishedSnapshot>(() => 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

View File

@@ -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> 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,

View File

@@ -6,6 +6,7 @@ using Umbraco.Core.DependencyInjection;
using Umbraco.Extensions;
using Umbraco.Infrastructure.DependencyInjection;
using Umbraco.Infrastructure.PublishedCache.DependencyInjection;
using Umbraco.Web.Common.Routing;
using Umbraco.Web.Website.Collections;
using Umbraco.Web.Website.Controllers;
using Umbraco.Web.Website.Routing;
@@ -40,6 +41,7 @@ namespace Umbraco.Web.Website.DependencyInjection
builder.Services.AddSingleton<HijackedRouteEvaluator>();
builder.Services.AddSingleton<IUmbracoRouteValuesFactory, UmbracoRouteValuesFactory>();
builder.Services.AddSingleton<IUmbracoRenderingDefaults, UmbracoRenderingDefaults>();
builder.Services.AddSingleton<RoutableDocumentFilter>();
builder.AddDistributedCache();

View File

@@ -34,6 +34,7 @@ namespace Umbraco.Web.Website.Routing
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IRuntimeState _runtime;
private readonly IUmbracoRouteValuesFactory _routeValuesFactory;
private readonly RoutableDocumentFilter _routableDocumentFilter;
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoRouteValueTransformer"/> class.
@@ -45,7 +46,8 @@ namespace Umbraco.Web.Website.Routing
IOptions<GlobalSettings> globalSettings,
IHostingEnvironment hostingEnvironment,
IRuntimeState runtime,
IUmbracoRouteValuesFactory routeValuesFactory)
IUmbracoRouteValuesFactory routeValuesFactory,
RoutableDocumentFilter routableDocumentFilter)
{
_logger = logger;
_umbracoContextAccessor = umbracoContextAccessor;
@@ -54,6 +56,7 @@ namespace Umbraco.Web.Website.Routing
_hostingEnvironment = hostingEnvironment;
_runtime = runtime;
_routeValuesFactory = routeValuesFactory;
_routableDocumentFilter = routableDocumentFilter;
}
/// <inheritdoc/>
@@ -71,10 +74,7 @@ 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))
if (!_routableDocumentFilter.IsDocumentRequest(httpContext.Request.Path))
{
return values;
}

View File

@@ -1,210 +0,0 @@
using System;
using System.IO;
using System.Web;
using System.Web.Routing;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Concurrent;
using Umbraco.Core.Collections;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.IO;
using Umbraco.Web.Composing;
namespace Umbraco.Web
{
/// <summary>
/// Utility class used to check if the current request is for a front-end request
/// </summary>
/// <remarks>
/// There are various checks to determine if this is a front-end request such as checking if the request is part of any reserved paths or existing MVC routes.
/// </remarks>
public sealed class RoutableDocumentFilter
{
public RoutableDocumentFilter(GlobalSettings globalSettings, IIOHelper ioHelper)
{
_globalSettings = globalSettings;
_ioHelper = ioHelper;
}
private static readonly ConcurrentDictionary<string, bool> RouteChecks = new ConcurrentDictionary<string, bool>();
private readonly GlobalSettings _globalSettings;
private readonly IIOHelper _ioHelper;
private object _locker = new object();
private bool _isInit = false;
private int? _routeCount;
private HashSet<string> _reservedList;
/// <summary>
/// Checks if the request is a document request (i.e. one that the module should handle)
/// </summary>
/// <param name="httpContext"></param>
/// <param name="uri"></param>
/// <returns></returns>
public bool IsDocumentRequest(HttpContextBase httpContext, Uri uri)
{
var maybeDoc = true;
var lpath = uri.AbsolutePath.ToLowerInvariant();
// handle directory-URLs used for asmx
// TODO: legacy - what's the point really?
var asmxPos = lpath.IndexOf(".asmx/", StringComparison.OrdinalIgnoreCase);
if (asmxPos >= 0)
{
// use uri.AbsolutePath, not path, 'cos path has been lowercased
httpContext.RewritePath(uri.AbsolutePath.Substring(0, asmxPos + 5), // filePath
uri.AbsolutePath.Substring(asmxPos + 5), // pathInfo
uri.Query.TrimStart('?'));
maybeDoc = false;
}
// a document request should be
// /foo/bar/nil
// /foo/bar/nil/
// /foo/bar/nil.aspx
// where /foo is not a reserved path
// if the path contains an extension that is not .aspx
// then it cannot be a document request
var extension = Path.GetExtension(lpath);
if (maybeDoc && extension.IsNullOrWhiteSpace() == false && extension != ".aspx")
maybeDoc = false;
// at that point, either we have no extension, or it is .aspx
// if the path is reserved then it cannot be a document request
if (maybeDoc && IsReservedPathOrUrl(lpath, httpContext, RouteTable.Routes))
maybeDoc = false;
//NOTE: No need to warn, plus if we do we should log the document, as this message doesn't really tell us anything :)
//if (!maybeDoc)
//{
// Logger.LogWarning<UmbracoModule>("Not a document");
//}
return maybeDoc;
}
/// <summary>
/// Determines whether the specified URL is reserved or is inside a reserved path.
/// </summary>
/// <param name="url">The URL to check.</param>
/// <returns>
/// <c>true</c> if the specified URL is reserved; otherwise, <c>false</c>.
/// </returns>
internal bool IsReservedPathOrUrl(string url)
{
LazyInitializer.EnsureInitialized(ref _reservedList, ref _isInit, ref _locker, () =>
{
// store references to strings to determine changes
var reservedPathsCache = _globalSettings.ReservedPaths;
var reservedUrlsCache = _globalSettings.ReservedUrls;
// add URLs and paths to a new list
var newReservedList = new HashSet<string>();
foreach (var reservedUrlTrimmed in reservedUrlsCache
.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim().ToLowerInvariant())
.Where(x => x.IsNullOrWhiteSpace() == false)
.Select(reservedUrl => _ioHelper.ResolveUrl(reservedUrl).Trim().EnsureStartsWith("/"))
.Where(reservedUrlTrimmed => reservedUrlTrimmed.IsNullOrWhiteSpace() == false))
{
newReservedList.Add(reservedUrlTrimmed);
}
foreach (var reservedPathTrimmed in NormalizePaths(reservedPathsCache.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)))
{
newReservedList.Add(reservedPathTrimmed);
}
foreach (var reservedPathTrimmed in NormalizePaths(ReservedPaths))
{
newReservedList.Add(reservedPathTrimmed);
}
// use the new list from now on
return newReservedList;
});
//The URL should be cleaned up before checking:
// * If it doesn't contain an '.' in the path then we assume it is a path based URL, if that is the case we should add an trailing '/' because all of our reservedPaths use a trailing '/'
// * We shouldn't be comparing the query at all
var pathPart = url.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0].ToLowerInvariant();
if (pathPart.Contains(".") == false)
{
pathPart = pathPart.EnsureEndsWith('/');
}
// return true if URL starts with an element of the reserved list
return _reservedList.Any(x => pathPart.InvariantStartsWith(x));
}
private IEnumerable<string> NormalizePaths(IEnumerable<string> paths)
{
return paths
.Select(x => x.Trim().ToLowerInvariant())
.Where(x => x.IsNullOrWhiteSpace() == false)
.Select(reservedPath => _ioHelper.ResolveUrl(reservedPath).Trim().EnsureStartsWith("/").EnsureEndsWith("/"))
.Where(reservedPathTrimmed => reservedPathTrimmed.IsNullOrWhiteSpace() == false);
}
/// <summary>
/// Determines whether the current request is reserved based on the route table and
/// whether the specified URL is reserved or is inside a reserved path.
/// </summary>
/// <param name="url"></param>
/// <param name="httpContext"></param>
/// <param name="routes">The route collection to lookup the request in</param>
/// <returns></returns>
internal bool IsReservedPathOrUrl(string url, HttpContextBase httpContext, RouteCollection routes)
{
if (httpContext == null) throw new ArgumentNullException(nameof(httpContext));
if (routes == null) throw new ArgumentNullException(nameof(routes));
//This is some rudimentary code to check if the route table has changed at runtime, we're basically just keeping a count
//of the routes. This isn't fail safe but there's no way to monitor changes to the route table. Else we need to create a hash
//of all routes and then recompare but that will be annoying to do on each request and then we might as well just do the whole MVC
//route on each request like we were doing before instead of caching the result of GetRouteData.
var changed = false;
using (routes.GetReadLock())
{
if (!_routeCount.HasValue || _routeCount.Value != routes.Count)
{
//the counts are not set or have changed, need to reset
changed = true;
}
}
if (changed)
{
using (routes.GetWriteLock())
{
_routeCount = routes.Count;
//try clearing each entry
foreach(var r in RouteChecks.Keys.ToList())
RouteChecks.TryRemove(r, out _);
}
}
var absPath = httpContext?.Request?.Url.AbsolutePath;
if (absPath.IsNullOrWhiteSpace())
return false;
//check if the current request matches a route, if so then it is reserved.
var hasRoute = RouteChecks.GetOrAdd(absPath, x => routes.GetRouteData(httpContext) != null);
if (hasRoute)
return true;
//continue with the standard ignore routine
return IsReservedPathOrUrl(url);
}
/// <summary>
/// This is used internally to track any registered callback paths for Identity providers. If the request path matches
/// any of the registered paths, then the module will let the request keep executing
/// </summary>
internal static readonly ConcurrentHashSet<string> ReservedPaths = new ConcurrentHashSet<string>();
}
}

View File

@@ -46,8 +46,6 @@ namespace Umbraco.Web.Runtime
return new UmbracoHelper();
});
builder.Services.AddUnique<RoutableDocumentFilter>();
// configure the container for web
//composition.ConfigureForWeb();

View File

@@ -150,7 +150,6 @@
<Compile Include="AspNet\AspNetHttpContextAccessor.cs" />
<Compile Include="AspNet\AspNetIpResolver.cs" />
<Compile Include="AspNet\AspNetPasswordHasher.cs" />
<Compile Include="RoutableDocumentFilter.cs" />
<Compile Include="Runtime\AspNetUmbracoBootPermissionChecker.cs" />
<Compile Include="Security\MembershipProviderBase.cs" />
<Compile Include="Security\MembershipProviderExtensions.cs" />

View File

@@ -12,18 +12,12 @@ using Umbraco.Web.Security;
namespace Umbraco.Web
{
/// <summary>
/// Class that encapsulates Umbraco information of a specific HTTP request
/// </summary>
// 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<IPublishedSnapshot> _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
/// </summary>
public IVariationContextAccessor VariationContextAccessor { get; }
/// <summary>
/// Gets a value indicating whether the request has debugging enabled
/// </summary>
/// <value><c>true</c> if this instance is debug; otherwise, <c>false</c>.</value>
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;
/// <summary>
/// Determines whether the current user is in a preview mode and browsing the site (ie. not in the admin UI)
/// </summary>
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() { }
}
}

View File

@@ -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,42 +32,29 @@ namespace Umbraco.Web
{
private readonly IRuntimeState _runtime;
private readonly ILogger _logger;
private readonly IPublishedRouter _publishedRouter;
private readonly IUmbracoContextFactory _umbracoContextFactory;
private readonly RoutableDocumentFilter _routableDocumentLookup;
private readonly GlobalSettings _globalSettings;
private readonly IHostingEnvironment _hostingEnvironment;
public UmbracoInjectedModule(
IRuntimeState runtime,
ILogger logger,
IPublishedRouter publishedRouter,
IUmbracoContextFactory umbracoContextFactory,
RoutableDocumentFilter routableDocumentLookup,
GlobalSettings globalSettings,
IHostingEnvironment hostingEnvironment)
{
_runtime = runtime;
_logger = logger;
_publishedRouter = publishedRouter;
_umbracoContextFactory = umbracoContextFactory;
_routableDocumentLookup = routableDocumentLookup;
_globalSettings = globalSettings;
_hostingEnvironment = hostingEnvironment;
}
#region HttpModule event handlers
/// <summary>
/// Begins to process a request.
/// </summary>
/// <param name="httpContext"></param>
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");
@@ -85,69 +70,25 @@ namespace Umbraco.Web
/// <summary>
/// Processes the Umbraco Request
/// </summary>
/// <param name="httpContext"></param>
/// <remarks>
///
/// 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.
///
/// </remarks>
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
/// <summary>
/// Checks the current request and ensures that it is routable based on the structure of the request and URI
/// </summary>
@@ -157,14 +98,15 @@ namespace Umbraco.Web
var reason = EnsureRoutableOutcome.IsRoutable;
// ensure this is a document request
if (!_routableDocumentLookup.IsDocumentRequest(httpContext, context.OriginalRequestUrl))
{
reason = EnsureRoutableOutcome.NotDocumentRequest;
}
//// ensure this is a document request
//if (!_routableDocumentLookup.IsDocumentRequest(httpContext, context.OriginalRequestUrl))
//{
// reason = EnsureRoutableOutcome.NotDocumentRequest;
//}
// ensure the runtime is in the proper state
// and deal with needed redirects, etc
else if (!EnsureRuntime(httpContext, uri))
if (!EnsureRuntime(httpContext, uri))
{
reason = EnsureRoutableOutcome.NotReady;
}
@@ -253,7 +195,6 @@ namespace Umbraco.Web
}
#endregion
#region IHttpModule