Moves UrlExtensions methods to new service and reduce the huge amount of allocated strings during routing
This commit is contained in:
@@ -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>();
|
||||
|
||||
151
src/Umbraco.Core/Routing/UmbracoRequestPaths.cs
Normal file
151
src/Umbraco.Core/Routing/UmbracoRequestPaths.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Hosting;
|
||||
|
||||
namespace Umbraco.Core.Routing
|
||||
{
|
||||
/// <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 _installPath;
|
||||
private readonly string _appPath;
|
||||
private readonly List<string> _aspLegacyJsExt = new List<string> { ".asmx/", ".aspx/", ".ashx/", ".axd/", ".svc/" };
|
||||
private readonly List<string> _aspLegacyExt = new List<string> { ".asmx", ".aspx", ".ashx", ".axd", ".svc" };
|
||||
|
||||
|
||||
/// <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);
|
||||
|
||||
_backOfficeMvcPath = "/" + _mvcArea + "/BackOffice/";
|
||||
_previewMvcPath = "/" + _mvcArea + "/Preview/";
|
||||
_installPath = hostingEnvironment.ToAbsolute(Constants.SystemDirectories.Install);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the current uri is a back office request
|
||||
/// </summary>
|
||||
/// <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 bool IsBackOfficeRequest(string absPath)
|
||||
{
|
||||
var fullUrlPath = absPath.TrimStart('/');
|
||||
var urlPath = fullUrlPath.TrimStart(_appPath).EnsureStartsWith('/');
|
||||
|
||||
// check if this is in the umbraco back office
|
||||
var isUmbracoPath = urlPath.InvariantStartsWith(_backOfficePath);
|
||||
|
||||
// if not, then def not back office
|
||||
if (isUmbracoPath == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// if its the normal /umbraco path
|
||||
if (urlPath.InvariantEquals("/" + _mvcArea)
|
||||
|| urlPath.InvariantEquals("/" + _mvcArea + "/"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// check for a file extension
|
||||
var extension = Path.GetExtension(absPath);
|
||||
|
||||
// has an extension, def back office
|
||||
if (extension.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// check for special case asp.net calls like:
|
||||
// /umbraco/webservices/legacyAjaxCalls.asmx/js which will return a null file extension but are still considered requests with an extension
|
||||
if (_aspLegacyJsExt.Any(x => urlPath.InvariantContains(x)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// check for special back office paths
|
||||
if (urlPath.InvariantStartsWith(_backOfficeMvcPath)
|
||||
|| urlPath.InvariantStartsWith(_previewMvcPath))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// check for special front-end paths
|
||||
// TODO: These should be constants - will need to update when we do front-end routing
|
||||
if (urlPath.InvariantStartsWith("/" + _mvcArea + "/Surface/")
|
||||
|| urlPath.InvariantStartsWith("/" + _mvcArea + "/Api/"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// if its none of the above, we will have to try to detect if it's a PluginController route, we can detect this by
|
||||
// checking how many parts the route has, for example, all PluginController routes will be routed like
|
||||
// Umbraco/MYPLUGINAREA/MYCONTROLLERNAME/{action}/{id}
|
||||
// so if the path contains at a minimum 3 parts: Umbraco + MYPLUGINAREA + MYCONTROLLERNAME then we will have to assume it is a
|
||||
// plugin controller for the front-end.
|
||||
if (urlPath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).Length >= 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// if its anything else we can assume it's back office
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
if (ext.IsNullOrWhiteSpace())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _aspLegacyExt.Any(ext.InvariantEquals) == false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.Routing;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Tests.Common;
|
||||
using Umbraco.Web;
|
||||
@@ -63,7 +64,7 @@ namespace Umbraco.Tests.UnitTests.TestHelpers.Objects
|
||||
snapshotService.Object,
|
||||
new TestVariationContextAccessor(),
|
||||
new TestDefaultCultureAccessor(),
|
||||
Options.Create<GlobalSettings>(globalSettings),
|
||||
new UmbracoRequestPaths(Options.Create(globalSettings), hostingEnvironment),
|
||||
hostingEnvironment,
|
||||
new UriUtility(hostingEnvironment),
|
||||
new AspNetCoreCookieManager(httpContextAccessor),
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Routing;
|
||||
using Umbraco.Web.Common.AspNetCore;
|
||||
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core.Routing
|
||||
{
|
||||
[TestFixture]
|
||||
public class UmbracoRequestPathsTests
|
||||
{
|
||||
private IWebHostEnvironment _hostEnvironment;
|
||||
private GlobalSettings _globalSettings;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_hostEnvironment = Mock.Of<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", false)]
|
||||
public void Is_Client_Side_Request(string url, bool assert)
|
||||
{
|
||||
var umbracoRequestPaths = new UmbracoRequestPaths(null, null);
|
||||
|
||||
var uri = new Uri("http://test.com" + url);
|
||||
var result = umbracoRequestPaths.IsClientSideRequest(uri.AbsolutePath);
|
||||
Assert.AreEqual(assert, result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Is_Client_Side_Request_InvalidPath_ReturnFalse()
|
||||
{
|
||||
var umbracoRequestPaths = new UmbracoRequestPaths(null, null);
|
||||
|
||||
// This URL is invalid. Default to false when the extension cannot be determined
|
||||
var uri = new Uri("http://test.com/installing-modules+foobar+\"yipee\"");
|
||||
var result = umbracoRequestPaths.IsClientSideRequest(uri.AbsolutePath);
|
||||
Assert.AreEqual(false, result);
|
||||
}
|
||||
|
||||
[TestCase("http://www.domain.com/umbraco/preview/frame?id=1234", "", true)]
|
||||
[TestCase("http://www.domain.com/umbraco", "", true)]
|
||||
[TestCase("http://www.domain.com/Umbraco/", "", true)]
|
||||
[TestCase("http://www.domain.com/umbraco/default.aspx", "", true)]
|
||||
[TestCase("http://www.domain.com/umbraco/test/test", "", false)]
|
||||
[TestCase("http://www.domain.com/umbraco/test/test/test", "", false)]
|
||||
[TestCase("http://www.domain.com/Umbraco/test/test.aspx", "", true)]
|
||||
[TestCase("http://www.domain.com/umbraco/test/test.js", "", true)]
|
||||
[TestCase("http://www.domain.com/umbrac", "", false)]
|
||||
[TestCase("http://www.domain.com/test", "", false)]
|
||||
[TestCase("http://www.domain.com/test/umbraco", "", false)]
|
||||
[TestCase("http://www.domain.com/Umbraco/Backoffice/blah", "", true)]
|
||||
[TestCase("http://www.domain.com/Umbraco/anything", "", true)]
|
||||
[TestCase("http://www.domain.com/Umbraco/anything/", "", true)]
|
||||
[TestCase("http://www.domain.com/Umbraco/surface/blah", "", false)]
|
||||
[TestCase("http://www.domain.com/umbraco/api/blah", "", false)]
|
||||
[TestCase("http://www.domain.com/myvdir/umbraco/api/blah", "myvdir", false)]
|
||||
[TestCase("http://www.domain.com/MyVdir/umbraco/api/blah", "/myvdir", false)]
|
||||
[TestCase("http://www.domain.com/MyVdir/Umbraco/", "myvdir", true)]
|
||||
[TestCase("http://www.domain.com/umbraco/test/legacyAjaxCalls.ashx?some=query&blah=js", "", true)]
|
||||
public void Is_Back_Office_Request(string input, string virtualPath, bool expected)
|
||||
{
|
||||
var source = new Uri(input);
|
||||
var hostingEnvironment = CreateHostingEnvironment(virtualPath);
|
||||
var umbracoRequestPaths = new UmbracoRequestPaths(Options.Create(_globalSettings), hostingEnvironment);
|
||||
Assert.AreEqual(expected, umbracoRequestPaths.IsBackOfficeRequest(source.AbsolutePath));
|
||||
}
|
||||
|
||||
[TestCase("http://www.domain.com/install", true)]
|
||||
[TestCase("http://www.domain.com/Install/", true)]
|
||||
[TestCase("http://www.domain.com/install/default.aspx", true)]
|
||||
[TestCase("http://www.domain.com/install/test/test", true)]
|
||||
[TestCase("http://www.domain.com/Install/test/test.aspx", true)]
|
||||
[TestCase("http://www.domain.com/install/test/test.js", true)]
|
||||
[TestCase("http://www.domain.com/instal", false)]
|
||||
[TestCase("http://www.domain.com/umbraco", false)]
|
||||
[TestCase("http://www.domain.com/umbraco/umbraco", false)]
|
||||
public void Is_Installer_Request(string input, bool expected)
|
||||
{
|
||||
var source = new Uri(input);
|
||||
var hostingEnvironment = CreateHostingEnvironment();
|
||||
var umbracoRequestPaths = new UmbracoRequestPaths(Options.Create(_globalSettings), hostingEnvironment);
|
||||
Assert.AreEqual(expected, umbracoRequestPaths.IsInstallerRequest(source.AbsolutePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,13 @@
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.Routing;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.BackOffice.Controllers;
|
||||
@@ -26,10 +28,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Backoffice.Security
|
||||
var mgr = new BackOfficeCookieManager(
|
||||
Mock.Of<IUmbracoContextAccessor>(),
|
||||
runtime,
|
||||
Mock.Of<IHostingEnvironment>(),
|
||||
globalSettings);
|
||||
new UmbracoRequestPaths(Options.Create(globalSettings), Mock.Of<IHostingEnvironment>()));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -76,13 +76,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing
|
||||
RouteEndpoint endpoint1 = CreateEndpoint(
|
||||
"Umbraco/RenderMvc/{action?}/{id?}",
|
||||
new { controller = "RenderMvc" },
|
||||
"Umbraco_default",
|
||||
0);
|
||||
|
||||
RouteEndpoint endpoint2 = CreateEndpoint(
|
||||
"api/{controller?}/{id?}",
|
||||
new { action = "Index" },
|
||||
"WebAPI",
|
||||
1);
|
||||
|
||||
var endpointDataSource = new DefaultEndpointDataSource(endpoint1, endpoint2);
|
||||
@@ -97,16 +95,42 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Common.Routing
|
||||
routableDocFilter.IsDocumentRequest(url));
|
||||
}
|
||||
|
||||
[TestCase("/umbraco", true)]
|
||||
[TestCase("/umbraco/", true)]
|
||||
[TestCase("/umbraco/Default", true)]
|
||||
[TestCase("/umbraco/default/", true)]
|
||||
[TestCase("/umbraco/default/123", true)]
|
||||
[TestCase("/umbraco/default/blah/123", false)]
|
||||
public void Is_Reserved_By_Default_Back_Office_Route(string url, bool isReserved)
|
||||
{
|
||||
var globalSettings = new GlobalSettings { ReservedPaths = string.Empty, ReservedUrls = string.Empty };
|
||||
|
||||
RouteEndpoint endpoint1 = CreateEndpoint(
|
||||
"umbraco/{action}/{id?}",
|
||||
new { controller = "BackOffice", action = "Default" },
|
||||
0);
|
||||
|
||||
var endpointDataSource = new DefaultEndpointDataSource(endpoint1);
|
||||
|
||||
var routableDocFilter = new RoutableDocumentFilter(
|
||||
globalSettings,
|
||||
GetHostingEnvironment(),
|
||||
endpointDataSource);
|
||||
|
||||
Assert.AreEqual(
|
||||
!isReserved, // not reserved if it's a document request
|
||||
routableDocFilter.IsDocumentRequest(url));
|
||||
}
|
||||
|
||||
// borrowed from https://github.com/dotnet/aspnetcore/blob/19559e73da2b6d335b864ed2855dd8a0c7a207a0/src/Mvc/Mvc.Core/test/Routing/ControllerLinkGeneratorExtensionsTest.cs#L171
|
||||
private RouteEndpoint CreateEndpoint(
|
||||
string template,
|
||||
object defaults = null,
|
||||
string name = null,
|
||||
int order = 0) => new RouteEndpoint(
|
||||
(httpContext) => Task.CompletedTask,
|
||||
RoutePatternFactory.Parse(template, defaults, null),
|
||||
order,
|
||||
new EndpointMetadataCollection(Array.Empty<object>()),
|
||||
name);
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Routing;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Tests.Common;
|
||||
@@ -49,7 +50,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers
|
||||
Mock.Of<IPublishedSnapshotService>(),
|
||||
new TestVariationContextAccessor(),
|
||||
new TestDefaultCultureAccessor(),
|
||||
Options.Create(globalSettings),
|
||||
new UmbracoRequestPaths(Options.Create(globalSettings), hostingEnvironment),
|
||||
hostingEnvironment,
|
||||
new UriUtility(hostingEnvironment),
|
||||
Mock.Of<ICookieManager>(),
|
||||
@@ -80,7 +81,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers
|
||||
Mock.Of<IPublishedSnapshotService>(),
|
||||
new TestVariationContextAccessor(),
|
||||
new TestDefaultCultureAccessor(),
|
||||
Options.Create(globalSettings),
|
||||
new UmbracoRequestPaths(Options.Create(globalSettings), hostingEnvironment),
|
||||
hostingEnvironment,
|
||||
new UriUtility(hostingEnvironment),
|
||||
Mock.Of<ICookieManager>(),
|
||||
@@ -115,7 +116,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers
|
||||
publishedSnapshotService.Object,
|
||||
new TestVariationContextAccessor(),
|
||||
new TestDefaultCultureAccessor(),
|
||||
Options.Create(globalSettings),
|
||||
new UmbracoRequestPaths(Options.Create(globalSettings), hostingEnvironment),
|
||||
hostingEnvironment,
|
||||
new UriUtility(hostingEnvironment),
|
||||
Mock.Of<ICookieManager>(),
|
||||
@@ -149,7 +150,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Controllers
|
||||
Mock.Of<IPublishedSnapshotService>(),
|
||||
new TestVariationContextAccessor(),
|
||||
new TestDefaultCultureAccessor(),
|
||||
Options.Create(globalSettings),
|
||||
new UmbracoRequestPaths(Options.Create(globalSettings), hostingEnvironment),
|
||||
hostingEnvironment,
|
||||
new UriUtility(hostingEnvironment),
|
||||
Mock.Of<ICookieManager>(),
|
||||
|
||||
@@ -32,7 +32,6 @@ namespace Umbraco.Tests.Routing
|
||||
(
|
||||
runtime,
|
||||
logger,
|
||||
null, // FIXME: PublishedRouter complexities...
|
||||
Mock.Of<IUmbracoContextFactory>(),
|
||||
globalSettings,
|
||||
HostingEnvironment
|
||||
@@ -77,28 +76,6 @@ namespace Umbraco.Tests.Routing
|
||||
Assert.AreEqual(assert, result.Success);
|
||||
}
|
||||
|
||||
[TestCase("/favicon.ico", true)]
|
||||
[TestCase("/umbraco_client/Tree/treeIcons.css", true)]
|
||||
[TestCase("/umbraco_client/Tree/Themes/umbraco/style.css?cdv=37", true)]
|
||||
[TestCase("/base/somebasehandler", false)]
|
||||
[TestCase("/", false)]
|
||||
[TestCase("/home.aspx", false)]
|
||||
public void Is_Client_Side_Request(string url, bool assert)
|
||||
{
|
||||
var uri = new Uri("http://test.com" + url);
|
||||
var result = uri.IsClientSideRequest();
|
||||
Assert.AreEqual(assert, result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Is_Client_Side_Request_InvalidPath_ReturnFalse()
|
||||
{
|
||||
//This URL is invalid. Default to false when the extension cannot be determined
|
||||
var uri = new Uri("http://test.com/installing-modules+foobar+\"yipee\"");
|
||||
var result = uri.IsClientSideRequest();
|
||||
Assert.AreEqual(false, result);
|
||||
}
|
||||
|
||||
//NOTE: This test shows how we can test most of the HttpModule, it however is testing a method that no longer exists and is testing too much,
|
||||
// we need to write unit tests for each of the components: NiceUrlProvider, all of the Lookup classes, etc...
|
||||
// to ensure that each one is individually tested.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -8,13 +8,14 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Security
|
||||
{
|
||||
#pragma warning disable IDE0065 // Misplaced using directive
|
||||
using ICookieManager = Microsoft.AspNetCore.Authentication.Cookies.ICookieManager;
|
||||
#pragma warning restore IDE0065 // Misplaced using directive
|
||||
|
||||
/// <summary>
|
||||
/// Used to validate a cookie against a user's session id
|
||||
@@ -36,21 +37,24 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
public const string CookieName = "UMB_UCONTEXT_C";
|
||||
private readonly ISystemClock _systemClock;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IBackOfficeUserManager _userManager;
|
||||
|
||||
public BackOfficeSessionIdValidator(ISystemClock systemClock, IOptions<GlobalSettings> 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);
|
||||
|
||||
@@ -81,7 +85,7 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
DateTimeOffset? issuedUtc = null;
|
||||
var currentUtc = systemClock.UtcNow;
|
||||
|
||||
//read the last checked time from a custom cookie
|
||||
// read the last checked time from a custom cookie
|
||||
var lastCheckedCookie = cookieManager.GetRequestCookie(httpContext, CookieName);
|
||||
|
||||
if (lastCheckedCookie.IsNullOrWhiteSpace() == false)
|
||||
@@ -92,7 +96,7 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
}
|
||||
}
|
||||
|
||||
//no cookie, use the issue time of the auth ticket
|
||||
// no cookie, use the issue time of the auth ticket
|
||||
if (issuedUtc.HasValue == false)
|
||||
{
|
||||
issuedUtc = authTicketIssueDate;
|
||||
@@ -107,18 +111,24 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
}
|
||||
|
||||
if (validate == false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var userId = currentIdentity.GetUserId();
|
||||
var user = await _userManager.FindByIdAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var sessionId = currentIdentity.FindFirstValue(Constants.Security.SessionIdClaimType);
|
||||
if (await _userManager.ValidateSessionIdAsync(userId, sessionId) == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//we will re-issue the cookie last checked cookie
|
||||
// we will re-issue the cookie last checked cookie
|
||||
cookieManager.AppendResponseCookie(
|
||||
httpContext,
|
||||
CookieName,
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -7,10 +7,9 @@ using System.Threading;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Collections;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
namespace Umbraco.Web.Common.Routing
|
||||
{
|
||||
@@ -27,12 +26,10 @@ namespace Umbraco.Web.Common.Routing
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly EndpointDataSource _endpointDataSource;
|
||||
private readonly object _routeLocker = new object();
|
||||
|
||||
#pragma warning disable IDE0044 // Add readonly modifier
|
||||
private readonly List<string> _backOfficePaths;
|
||||
private object _initLocker = new object();
|
||||
private bool _isInit = false;
|
||||
private HashSet<string> _reservedList;
|
||||
#pragma warning restore IDE0044 // Add readonly modifier
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RoutableDocumentFilter"/> class.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -71,14 +71,6 @@ namespace Umbraco.Web.Website.Routing
|
||||
return values;
|
||||
}
|
||||
|
||||
// Check for back office request
|
||||
// TODO: This is how the module was doing it before but could just as easily be part of the RoutableDocumentFilter
|
||||
// which still needs to be migrated.
|
||||
if (httpContext.Request.IsDefaultBackOfficeRequest(_globalSettings, _hostingEnvironment))
|
||||
{
|
||||
return values;
|
||||
}
|
||||
|
||||
// Check if there is no existing content and return the no content controller
|
||||
if (!_umbracoContextAccessor.UmbracoContext.Content.HasContent())
|
||||
{
|
||||
|
||||
@@ -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() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Web;
|
||||
using System.Web.Routing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.Models;
|
||||
using Umbraco.Core.Exceptions;
|
||||
@@ -11,7 +10,6 @@ using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Web.Routing;
|
||||
using RouteDirection = Umbraco.Web.Routing.RouteDirection;
|
||||
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
@@ -34,7 +32,6 @@ namespace Umbraco.Web
|
||||
{
|
||||
private readonly IRuntimeState _runtime;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IPublishedRouter _publishedRouter;
|
||||
private readonly IUmbracoContextFactory _umbracoContextFactory;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
@@ -42,31 +39,22 @@ namespace Umbraco.Web
|
||||
public UmbracoInjectedModule(
|
||||
IRuntimeState runtime,
|
||||
ILogger logger,
|
||||
IPublishedRouter publishedRouter,
|
||||
IUmbracoContextFactory umbracoContextFactory,
|
||||
GlobalSettings globalSettings,
|
||||
IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
_runtime = runtime;
|
||||
_logger = logger;
|
||||
_publishedRouter = publishedRouter;
|
||||
_umbracoContextFactory = umbracoContextFactory;
|
||||
_globalSettings = globalSettings;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
}
|
||||
|
||||
#region HttpModule event handlers
|
||||
|
||||
/// <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");
|
||||
|
||||
@@ -82,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>
|
||||
@@ -251,7 +195,6 @@ namespace Umbraco.Web
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region IHttpModule
|
||||
|
||||
|
||||
Reference in New Issue
Block a user