From cca7303abda03377dfa9a0fb68cefa727c4ec84c Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 7 Aug 2020 00:48:32 +1000 Subject: [PATCH 1/2] Migrates preview auth Middleware Migrates UriExtensionsTests to netcore, fixes preview controller bits, adds tests for preview path for back office route check, fixes virtual paths for views, --- .../Editors/BackOfficePreviewModel.cs | 11 +-- src/Umbraco.Core/UriExtensions.cs | 3 +- .../Web/CookieManagerExtensions.cs | 9 -- .../Extensions}/UriExtensionsTests.cs | 35 +++++--- src/Umbraco.Tests/Umbraco.Tests.csproj | 3 +- .../Controllers/BackOfficeController.cs | 4 +- .../Controllers/PreviewController.cs | 32 +++---- ...oBackOfficeApplicationBuilderExtensions.cs | 3 + .../Runtime/BackOfficeComposer.cs | 1 + .../Security/BackOfficeCookieManager.cs | 4 +- .../PreviewAuthenticationMiddleware.cs | 73 +++++++++++++++ .../AspNetCoreHostingEnvironment.cs | 5 +- .../Extensions/HttpRequestExtensions.cs | 11 +++ .../src/preview/preview.controller.js | 42 +++++---- .../Umbraco/UmbracoBackOffice/Preview.cshtml | 89 +++++++++++++++++++ src/Umbraco.Web/HttpCookieExtensions.cs | 10 --- .../Security/AppBuilderExtensions.cs | 69 -------------- .../PreviewAuthenticationMiddleware.cs | 78 ---------------- src/Umbraco.Web/Umbraco.Web.csproj | 5 +- src/Umbraco.Web/UmbracoDefaultOwinStartup.cs | 5 +- 20 files changed, 257 insertions(+), 235 deletions(-) rename src/{Umbraco.Tests/CoreThings => Umbraco.Tests.UnitTests/Umbraco.Core/Extensions}/UriExtensionsTests.cs (90%) create mode 100644 src/Umbraco.Web.BackOffice/Security/PreviewAuthenticationMiddleware.cs create mode 100644 src/Umbraco.Web.UI.NetCore/Umbraco/UmbracoBackOffice/Preview.cshtml delete mode 100644 src/Umbraco.Web/Security/PreviewAuthenticationMiddleware.cs diff --git a/src/Umbraco.Core/Editors/BackOfficePreviewModel.cs b/src/Umbraco.Core/Editors/BackOfficePreviewModel.cs index b18896295a..10d12f30f8 100644 --- a/src/Umbraco.Core/Editors/BackOfficePreviewModel.cs +++ b/src/Umbraco.Core/Editors/BackOfficePreviewModel.cs @@ -4,23 +4,20 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Hosting; using Umbraco.Core.Models; using Umbraco.Web.Features; -using Umbraco.Web.Trees; namespace Umbraco.Web.Editors { - // TODO: Almost nothing here needs to exist since we can inject these into the view - public class BackOfficePreviewModel : BackOfficeModel + public class BackOfficePreviewModel { private readonly UmbracoFeatures _features; - public IEnumerable Languages { get; } - - public BackOfficePreviewModel(UmbracoFeatures features, IGlobalSettings globalSettings, IUmbracoVersion umbracoVersion, IEnumerable languages, IContentSettings contentSettings, IHostingEnvironment hostingEnvironment, IRuntimeSettings runtimeSettings, ISecuritySettings securitySettings) - : base(features, globalSettings, umbracoVersion, contentSettings, hostingEnvironment, runtimeSettings, securitySettings) + + public BackOfficePreviewModel(UmbracoFeatures features, IEnumerable languages) { _features = features; Languages = languages; } + public IEnumerable Languages { get; } public bool DisableDevicePreview => _features.Disabled.DisableDevicePreview; public string PreviewExtendedHeaderView => _features.Enabled.PreviewExtendedView; } diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index 8f0c7beff8..13aa6bde46 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -76,7 +76,8 @@ namespace Umbraco.Core } //check for special back office paths - if (urlPath.InvariantStartsWith("/" + mvcArea + "/" + Constants.Web.Mvc.BackOfficePathSegment + "/")) + if (urlPath.InvariantStartsWith("/" + mvcArea + "/BackOffice/") + || urlPath.InvariantStartsWith("/" + mvcArea + "/Preview/")) { return true; } diff --git a/src/Umbraco.Core/Web/CookieManagerExtensions.cs b/src/Umbraco.Core/Web/CookieManagerExtensions.cs index 5a1f748aaf..41f2fa1aca 100644 --- a/src/Umbraco.Core/Web/CookieManagerExtensions.cs +++ b/src/Umbraco.Core/Web/CookieManagerExtensions.cs @@ -9,15 +9,6 @@ namespace Umbraco.Web return cookieManager.GetCookieValue(Constants.Web.PreviewCookieName); } - /// - /// Does a preview cookie exist ? - /// - /// - /// - public static bool HasPreviewCookie(this ICookieManager cookieManager) - { - return cookieManager.HasCookie(Constants.Web.PreviewCookieName); - } } } diff --git a/src/Umbraco.Tests/CoreThings/UriExtensionsTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UriExtensionsTests.cs similarity index 90% rename from src/Umbraco.Tests/CoreThings/UriExtensionsTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UriExtensionsTests.cs index 6fdf9d9385..4c8903735a 100644 --- a/src/Umbraco.Tests/CoreThings/UriExtensionsTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Extensions/UriExtensionsTests.cs @@ -1,18 +1,31 @@ using System; +using System.Reflection; +using Microsoft.AspNetCore.Hosting; using Moq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Composing; -using Umbraco.Core.IO; -using Umbraco.Tests.TestHelpers; -using Umbraco.Web.Hosting; +using Umbraco.Core.Configuration; +using Umbraco.Tests.Common; +using Umbraco.Web.Common.AspNetCore; -namespace Umbraco.Tests.CoreThings +namespace Umbraco.Tests.UnitTests.Umbraco.Core.Extensions { [TestFixture] public class UriExtensionsTests { + [OneTimeSetUp] + public void Setup() + { + _settingsForTests = new SettingsForTests(); + _hostEnvironment = Mock.Of(); + _globalSettings = _settingsForTests.GenerateMockGlobalSettings(); + } + private SettingsForTests _settingsForTests; + private IWebHostEnvironment _hostEnvironment; + private IGlobalSettings _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)] @@ -34,13 +47,13 @@ namespace Umbraco.Tests.CoreThings [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 globalSettings = SettingsForTests.GenerateMockGlobalSettings(); - var mockHostingSettings = Mock.Get(SettingsForTests.GenerateMockHostingSettings()); + + var mockHostingSettings = Mock.Get(_settingsForTests.GenerateMockHostingSettings()); mockHostingSettings.Setup(x => x.ApplicationVirtualPath).Returns(virtualPath); - var hostingEnvironment = new AspNetHostingEnvironment(mockHostingSettings.Object); + var hostingEnvironment = new AspNetCoreHostingEnvironment(mockHostingSettings.Object, _hostEnvironment); var source = new Uri(input); - Assert.AreEqual(expected, source.IsBackOfficeRequest(globalSettings, hostingEnvironment)); + Assert.AreEqual(expected, source.IsBackOfficeRequest(_globalSettings, hostingEnvironment)); } [TestCase("http://www.domain.com/install", true)] @@ -55,7 +68,9 @@ namespace Umbraco.Tests.CoreThings public void Is_Installer_Request(string input, bool expected) { var source = new Uri(input); - Assert.AreEqual(expected, source.IsInstallerRequest(TestHelper.GetHostingEnvironment())); + var mockHostingSettings = Mock.Get(_settingsForTests.GenerateMockHostingSettings()); + var hostingEnvironment = new AspNetCoreHostingEnvironment(mockHostingSettings.Object, _hostEnvironment); + Assert.AreEqual(expected, source.IsInstallerRequest(hostingEnvironment)); } [TestCase("http://www.domain.com/foo/bar", "/", "http://www.domain.com/")] diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index a93652ffd8..4fcb511bab 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -432,7 +432,6 @@ - @@ -561,7 +560,7 @@ - + diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index c17e977951..ce192ec377 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -68,7 +68,9 @@ namespace Umbraco.Web.BackOffice.Controllers [HttpGet] public async Task Default() { - var viewPath = Path.Combine(_globalSettings.UmbracoPath , Umbraco.Core.Constants.Web.Mvc.BackOfficeArea, nameof(Default) + ".cshtml"); + var viewPath = Path.Combine(_globalSettings.UmbracoPath , Constants.Web.Mvc.BackOfficeArea, nameof(Default) + ".cshtml") + .Replace("\\", "/"); // convert to forward slashes since it's a virtual path + return await RenderDefaultOrProcessExternalLoginAsync( () => View(viewPath), () => View(viewPath)); diff --git a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs index 6b5c12df47..fec9d23f19 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/PreviewController.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ViewEngines; using System; +using System.IO; using System.Threading.Tasks; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -9,6 +10,7 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Hosting; using Umbraco.Core.Services; using Umbraco.Core.WebAssets; +using Umbraco.Extensions; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.ActionResults; using Umbraco.Web.Common.Filters; @@ -29,14 +31,9 @@ namespace Umbraco.Web.BackOffice.Controllers private readonly IGlobalSettings _globalSettings; private readonly IPublishedSnapshotService _publishedSnapshotService; private readonly IWebSecurity _webSecurity; - private readonly ILocalizationService _localizationService; - private readonly IUmbracoVersion _umbracoVersion; - private readonly IContentSettings _contentSettings; - private readonly IHttpContextAccessor _httpContextAccessor; + private readonly ILocalizationService _localizationService; private readonly IHostingEnvironment _hostingEnvironment; private readonly ICookieManager _cookieManager; - private readonly IRuntimeSettings _runtimeSettings; - private readonly ISecuritySettings _securitySettings; private readonly IRuntimeMinifier _runtimeMinifier; private readonly ICompositeViewEngine _viewEngines; @@ -46,13 +43,8 @@ namespace Umbraco.Web.BackOffice.Controllers IPublishedSnapshotService publishedSnapshotService, IWebSecurity webSecurity, ILocalizationService localizationService, - IUmbracoVersion umbracoVersion, - IContentSettings contentSettings, - IHttpContextAccessor httpContextAccessor, IHostingEnvironment hostingEnvironment, ICookieManager cookieManager, - IRuntimeSettings settings, - ISecuritySettings securitySettings, IRuntimeMinifier runtimeMinifier, ICompositeViewEngine viewEngines) { @@ -61,13 +53,8 @@ namespace Umbraco.Web.BackOffice.Controllers _publishedSnapshotService = publishedSnapshotService; _webSecurity = webSecurity; _localizationService = localizationService; - _umbracoVersion = umbracoVersion; - _contentSettings = contentSettings ?? throw new ArgumentNullException(nameof(contentSettings)); - _httpContextAccessor = httpContextAccessor; _hostingEnvironment = hostingEnvironment; _cookieManager = cookieManager; - _runtimeSettings = settings; - _securitySettings = securitySettings; _runtimeMinifier = runtimeMinifier; _viewEngines = viewEngines; } @@ -78,7 +65,7 @@ namespace Umbraco.Web.BackOffice.Controllers { var availableLanguages = _localizationService.GetAllLanguages(); - var model = new BackOfficePreviewModel(_features, _globalSettings, _umbracoVersion, availableLanguages, _contentSettings, _hostingEnvironment, _runtimeSettings, _securitySettings); + var model = new BackOfficePreviewModel(_features, availableLanguages); if (model.PreviewExtendedHeaderView.IsNullOrWhiteSpace() == false) { @@ -87,7 +74,13 @@ namespace Umbraco.Web.BackOffice.Controllers throw new InvalidOperationException("Could not find the view " + model.PreviewExtendedHeaderView + ", the following locations were searched: " + Environment.NewLine + string.Join(Environment.NewLine, viewEngineResult.SearchedLocations)); } - return View(_globalSettings.GetBackOfficePath(_hostingEnvironment).EnsureEndsWith('/') + "Views/Preview/" + "Index.cshtml", model); + var viewPath = Path.Combine( + _globalSettings.UmbracoPath, + Constants.Web.Mvc.BackOfficeArea, + ControllerExtensions.GetControllerName() + ".cshtml") + .Replace("\\", "/"); // convert to forward slashes since it's a virtual path + + return View(viewPath, model); } /// @@ -120,9 +113,8 @@ namespace Umbraco.Web.BackOffice.Controllers // use a numeric url because content may not be in cache and so .Url would fail var query = culture.IsNullOrWhiteSpace() ? string.Empty : $"?culture={culture}"; - Response.Redirect($"../../{id}.aspx{query}", true); - return null; + return RedirectPermanent($"../../{id}.aspx{query}"); } public ActionResult End(string redir = null) diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeApplicationBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeApplicationBuilderExtensions.cs index 4f0ba8097d..cbc958214c 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeApplicationBuilderExtensions.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using SixLabors.ImageSharp.Web.DependencyInjection; using Umbraco.Web.BackOffice.Routing; +using Umbraco.Web.BackOffice.Security; namespace Umbraco.Extensions { @@ -27,6 +28,8 @@ namespace Umbraco.Extensions app.UseImageSharp(); app.UseStaticFiles(); + app.UseMiddleware(); + return app; } diff --git a/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs b/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs index 8ee3e7813e..600602f5b5 100644 --- a/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs +++ b/src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs @@ -27,6 +27,7 @@ namespace Umbraco.Web.BackOffice.Runtime composition.Register(Lifetime.Request); composition.Register(Lifetime.Request); + composition.RegisterUnique(); composition.RegisterUnique(); // register back office trees diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs index f63b2380af..75112e9a22 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeCookieManager.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web.BackOffice.Security /// A custom cookie manager that is used to read the cookie from the request. /// /// - /// Umbraco's back office cookie needs to be read on two paths: /umbraco and /install and /base therefore we cannot just set the cookie path to be /umbraco, + /// Umbraco's back office cookie needs to be read on two paths: /umbraco and /install, therefore we cannot just set the cookie path to be /umbraco, /// instead we'll specify our own cookie manager and return null if the request isn't for an acceptable path. /// public class BackOfficeCookieManager : ChunkingCookieManager, ICookieManager @@ -69,7 +69,6 @@ namespace Umbraco.Web.BackOffice.Security /// We auth the request when: /// * it is a back office request /// * it is an installer request - /// * it is a /base request /// * it is a preview request /// public bool ShouldAuthenticateRequest(Uri requestUri, bool checkForceAuthTokens = true) @@ -94,6 +93,7 @@ namespace Umbraco.Web.BackOffice.Security //check installer || requestUri.IsInstallerRequest(_hostingEnvironment)) return true; + return false; } diff --git a/src/Umbraco.Web.BackOffice/Security/PreviewAuthenticationMiddleware.cs b/src/Umbraco.Web.BackOffice/Security/PreviewAuthenticationMiddleware.cs new file mode 100644 index 0000000000..ff182b9f7b --- /dev/null +++ b/src/Umbraco.Web.BackOffice/Security/PreviewAuthenticationMiddleware.cs @@ -0,0 +1,73 @@ +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.Hosting; +using Umbraco.Extensions; + +namespace Umbraco.Web.BackOffice.Security +{ + /// + /// Ensures that preview pages (front-end routed) are authenticated with the back office identity appended to the principal alongside any default authentication that takes place + /// + public class PreviewAuthenticationMiddleware : IMiddleware + { + private readonly IGlobalSettings _globalSettings; + private readonly IHostingEnvironment _hostingEnvironment; + + public PreviewAuthenticationMiddleware( + IGlobalSettings globalSettings, + IHostingEnvironment hostingEnvironment) + { + _globalSettings = globalSettings; + _hostingEnvironment = hostingEnvironment; + } + + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + var request = context.Request; + if (!request.IsClientSideRequest()) + { + var isPreview = request.HasPreviewCookie() + && context.User != null + && !request.IsBackOfficeRequest(_globalSettings, _hostingEnvironment); + + if (isPreview) + { + var cookieOptions = context.RequestServices.GetRequiredService>() + .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 + // for the user here so that the preview capability can be authorized otherwise only the non-preview page will be rendered. + if (request.Cookies.TryGetValue(cookieOptions.Cookie.Name, out var cookie)) + { + var unprotected = cookieOptions.TicketDataFormat.Unprotect(cookie); + if (unprotected != null) + { + 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); + } + } + } + + } + } + + await next(context); + } + } +} diff --git a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs index f5b1b988cf..efa1a52ad4 100644 --- a/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs +++ b/src/Umbraco.Web.Common/AspNetCore/AspNetCoreHostingEnvironment.cs @@ -22,7 +22,10 @@ namespace Umbraco.Web.Common.AspNetCore ApplicationId = AppDomain.CurrentDomain.Id.ToString(); ApplicationPhysicalPath = webHostEnvironment.ContentRootPath; - ApplicationVirtualPath = "/"; //TODO how to find this, This is a server thing, not application thing. + //TODO how to find this, This is a server thing, not application thing. + ApplicationVirtualPath = hostingSettings.ApplicationVirtualPath?.EnsureStartsWith('/') + ?? "/"; + IISVersion = new Version(0, 0); // TODO not necessary IIS } diff --git a/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs b/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs index fb92af11fc..5baedf3ded 100644 --- a/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/HttpRequestExtensions.cs @@ -13,6 +13,17 @@ namespace Umbraco.Extensions { public static class HttpRequestExtensions { + + /// + /// Check if a preview cookie exist + /// + /// + /// + public static bool HasPreviewCookie(this HttpRequest request) + { + return request.Cookies.TryGetValue(Constants.Web.PreviewCookieName, out var cookieVal) && !cookieVal.IsNullOrWhiteSpace(); + } + public static bool IsBackOfficeRequest(this HttpRequest request, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) { return new Uri(request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsBackOfficeRequest(globalSettings, hostingEnvironment); diff --git a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js index 5ff8dd3633..5d1047de1b 100644 --- a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js @@ -61,28 +61,34 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi } }); - previewHub.client.refreshed = function (message, sender) { - console.log("Notified by SignalR preview hub (" + message + ")."); + if (previewHub && previewHub.client) { + previewHub.client.refreshed = function (message, sender) { + console.log("Notified by SignalR preview hub (" + message + ")."); - if ($scope.pageId != message) { - console.log("Not a notification for us (" + $scope.pageId + ")."); - return; - } + if ($scope.pageId != message) { + console.log("Not a notification for us (" + $scope.pageId + ")."); + return; + } - if (!visibleContent) { - console.log("Not visible, will reload."); - dirtyContent = true; - return; - } + if (!visibleContent) { + console.log("Not visible, will reload."); + dirtyContent = true; + return; + } - console.log("Reloading."); - var iframeDoc = (iframe.contentWindow || iframe.contentDocument); - iframeDoc.location.reload(); - }; + console.log("Reloading."); + var iframeDoc = (iframe.contentWindow || iframe.contentDocument); + iframeDoc.location.reload(); + }; + } - $.connection.hub.start() - .done(function () { console.log("Connected to SignalR preview hub (ID=" + $.connection.hub.id + ")"); }) - .fail(function () { console.log("Could not connect to SignalR preview hub."); }); + try { + $.connection.hub.start() + .done(function () { console.log("Connected to SignalR preview hub (ID=" + $.connection.hub.id + ")"); }) + .fail(function () { console.log("Could not connect to SignalR preview hub."); }); + } catch (e) { + console.error("Could not establish signalr connection. Error: " + e); + } } var isInit = getParameterByName("init"); diff --git a/src/Umbraco.Web.UI.NetCore/Umbraco/UmbracoBackOffice/Preview.cshtml b/src/Umbraco.Web.UI.NetCore/Umbraco/UmbracoBackOffice/Preview.cshtml new file mode 100644 index 0000000000..ac5d6851e7 --- /dev/null +++ b/src/Umbraco.Web.UI.NetCore/Umbraco/UmbracoBackOffice/Preview.cshtml @@ -0,0 +1,89 @@ +@using Umbraco.Core +@using Umbraco.Web.WebAssets +@using Umbraco.Web.Common.Security +@using Umbraco.Core.WebAssets +@using Umbraco.Core.Configuration +@using Umbraco.Core.Hosting +@using Umbraco.Extensions +@using Umbraco.Core.Logging +@using Umbraco.Web.BackOffice.Controllers +@inject BackOfficeSignInManager signInManager +@inject BackOfficeServerVariables backOfficeServerVariables +@inject IUmbracoVersion umbracoVersion +@inject IHostingEnvironment hostingEnvironment +@inject IGlobalSettings globalSettings +@inject IRuntimeMinifier runtimeMinifier +@inject IProfilerHtml profilerHtml + + +@model Umbraco.Web.Editors.BackOfficePreviewModel +@{ + var disableDevicePreview = Model.DisableDevicePreview.ToString().ToLowerInvariant(); +} + + + + + Umbraco Preview + + + + @Html.Raw(await runtimeMinifier.RenderCssHereAsync(BackOfficeWebAssets.UmbracoPreviewCssBundleName)) + + + +
+ + @if (!string.IsNullOrWhiteSpace(Model.PreviewExtendedHeaderView)) + { + @await Html.PartialAsync(Model.PreviewExtendedHeaderView) + } + +
+ +
+
+ + +
+ + + + + + diff --git a/src/Umbraco.Web/HttpCookieExtensions.cs b/src/Umbraco.Web/HttpCookieExtensions.cs index 9e780f8740..df43528178 100644 --- a/src/Umbraco.Web/HttpCookieExtensions.cs +++ b/src/Umbraco.Web/HttpCookieExtensions.cs @@ -97,16 +97,6 @@ namespace Umbraco.Web return null; } - /// - /// Does a preview cookie exist ? - /// - /// - /// - public static bool HasPreviewCookie(this IOwinRequest request) - { - return request.Cookies[Constants.Web.PreviewCookieName] != null; - } - /// /// Returns the cookie's string value /// diff --git a/src/Umbraco.Web/Security/AppBuilderExtensions.cs b/src/Umbraco.Web/Security/AppBuilderExtensions.cs index d95c19bedf..ac5434aa4e 100644 --- a/src/Umbraco.Web/Security/AppBuilderExtensions.cs +++ b/src/Umbraco.Web/Security/AppBuilderExtensions.cs @@ -1,24 +1,14 @@ using System; using System.Threading; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Owin; -using Microsoft.Owin.Extensions; -using Microsoft.Owin.Logging; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Cookies; -using Microsoft.Owin.Security.DataHandler; using Microsoft.Owin.Security.DataProtection; using Owin; using Umbraco.Core; -using Umbraco.Core.BackOffice; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Hosting; -using Umbraco.Core.Mapping; -using Umbraco.Net; -using Umbraco.Core.Services; using Umbraco.Web.Composing; using Constants = Umbraco.Core.Constants; @@ -84,65 +74,6 @@ namespace Umbraco.Web.Security return app; } - /// - /// In order for preview to work this needs to be called - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// This ensures that during a preview request that the back office use is also Authenticated and that the back office Identity - /// is added as a secondary identity to the current IPrincipal so it can be used to Authorize the previewed document. - /// - /// - /// By default this will be configured to execute on PipelineStage.PostAuthenticate - /// - public static IAppBuilder UseUmbracoPreviewAuthentication(this IAppBuilder app, IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState, IGlobalSettings globalSettings, ISecuritySettings securitySettings, IHostingEnvironment hostingEnvironment, IRequestCache requestCache) - { - return app.UseUmbracoPreviewAuthentication(umbracoContextAccessor, runtimeState, globalSettings, securitySettings, hostingEnvironment, requestCache, PipelineStage.PostAuthenticate); - } - - /// - /// In order for preview to work this needs to be called - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// This ensures that during a preview request that the back office use is also Authenticated and that the back office Identity - /// is added as a secondary identity to the current IPrincipal so it can be used to Authorize the previewed document. - /// - public static IAppBuilder UseUmbracoPreviewAuthentication(this IAppBuilder app, IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState, IGlobalSettings globalSettings, ISecuritySettings securitySettings, IHostingEnvironment hostingEnvironment, IRequestCache requestCache, PipelineStage stage) - { - if (runtimeState.Level != RuntimeLevel.Run) return app; - - //var authOptions = app.CreateUmbracoCookieAuthOptions(umbracoContextAccessor, globalSettings, runtimeState, securitySettings, hostingEnvironment, requestCache); - app.Use(typeof(PreviewAuthenticationMiddleware), /*authOptions*/null, globalSettings, hostingEnvironment); - - // This middleware must execute at least on PostAuthentication, by default it is on Authorize - // The middleware needs to execute after the RoleManagerModule executes which is during PostAuthenticate, - // currently I've had 100% success with ensuring this fires after RoleManagerModule even if this is set - // to PostAuthenticate though not sure if that's always a guarantee so by default it's Authorize. - if (stage < PipelineStage.PostAuthenticate) - throw new InvalidOperationException("The stage specified for UseUmbracoPreviewAuthentication must be greater than or equal to " + PipelineStage.PostAuthenticate); - - app.UseStageMarker(stage); - return app; - } - public static void SanitizeThreadCulture(this IAppBuilder app) { Thread.CurrentThread.SanitizeThreadCulture(); diff --git a/src/Umbraco.Web/Security/PreviewAuthenticationMiddleware.cs b/src/Umbraco.Web/Security/PreviewAuthenticationMiddleware.cs deleted file mode 100644 index feecd85364..0000000000 --- a/src/Umbraco.Web/Security/PreviewAuthenticationMiddleware.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Security.Claims; -using System.Threading.Tasks; -using Microsoft.Owin; -using Umbraco.Core; -using Umbraco.Core.BackOffice; -using Umbraco.Core.Configuration; -using Umbraco.Core.Hosting; - -namespace Umbraco.Web.Security -{ - internal class PreviewAuthenticationMiddleware : OwinMiddleware - { - private readonly UmbracoBackOfficeCookieAuthOptions _cookieOptions; - private readonly IGlobalSettings _globalSettings; - private readonly IHostingEnvironment _hostingEnvironment; - - /// - /// Instantiates the middleware with an optional pointer to the next component. - /// - /// - /// - /// - /// - public PreviewAuthenticationMiddleware(OwinMiddleware next, - UmbracoBackOfficeCookieAuthOptions cookieOptions, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) : base(next) - { - _cookieOptions = cookieOptions; - _globalSettings = globalSettings; - _hostingEnvironment = hostingEnvironment; - } - - /// - /// Process an individual request. - /// - /// - /// - public override async Task Invoke(IOwinContext context) - { - var request = context.Request; - if (request.Uri.IsClientSideRequest() == false) - { - var claimsPrincipal = context.Request.User as ClaimsPrincipal; - var isPreview = request.HasPreviewCookie() - && claimsPrincipal != null - && request.Uri != null - && request.Uri.IsBackOfficeRequest(_globalSettings, _hostingEnvironment) == false; - if (isPreview) - { - //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 - // for the user here so that the preview capability can be authorized otherwise only the non-preview page will be rendered. - - var cookie = request.Cookies[_cookieOptions.CookieName]; - if (cookie.IsNullOrWhiteSpace() == false) - { - var unprotected = _cookieOptions.TicketDataFormat.Unprotect(cookie); - if (unprotected != 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. - if (!UmbracoBackOfficeIdentity.FromClaimsIdentity(unprotected.Identity, out var umbracoIdentity)) - throw new InvalidOperationException("Cannot convert identity"); - - claimsPrincipal.AddIdentity(umbracoIdentity); - } - } - } - } - - if (Next != null) - { - await Next.Invoke(context); - } - } - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 08a6de41d7..5fcea0f085 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -261,7 +261,6 @@ - @@ -410,7 +409,7 @@
- + - + \ No newline at end of file diff --git a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs index f800707476..600b58cf06 100644 --- a/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs +++ b/src/Umbraco.Web/UmbracoDefaultOwinStartup.cs @@ -89,10 +89,7 @@ namespace Umbraco.Web // Ensure owin is configured for Umbraco back office authentication. // Front-end OWIN cookie configuration must be declared after this code. app - // already moved to netcore - //.UseUmbracoBackOfficeCookieAuthentication(UmbracoContextAccessor, RuntimeState, Services.UserService, GlobalSettings, SecuritySettings, HostingEnvironment, RequestCache, PipelineStage.Authenticate) - .UseUmbracoBackOfficeExternalCookieAuthentication(UmbracoContextAccessor, RuntimeState, GlobalSettings, HostingEnvironment, RequestCache, PipelineStage.Authenticate) - .UseUmbracoPreviewAuthentication(UmbracoContextAccessor, RuntimeState, GlobalSettings, SecuritySettings, HostingEnvironment, RequestCache, PipelineStage.Authorize); + .UseUmbracoBackOfficeExternalCookieAuthentication(UmbracoContextAccessor, RuntimeState, GlobalSettings, HostingEnvironment, RequestCache, PipelineStage.Authenticate); } public static event EventHandler MiddlewareConfigured; From 635f1ba98490bbe7c510228a3ce0fe3a921d5f3b Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 10 Aug 2020 15:48:13 +1000 Subject: [PATCH 2/2] removes unused code --- src/Umbraco.Web/Editors/BackOfficeController.cs | 15 +-------------- src/Umbraco.Web/Umbraco.Web.csproj | 5 ++--- src/Umbraco.Web/UrlHelperRenderExtensions.cs | 3 --- 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 328ce92052..a93f8182cd 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -40,17 +40,13 @@ namespace Umbraco.Web.Editors public class BackOfficeController : UmbracoController { private readonly UmbracoFeatures _features; - private readonly IRuntimeState _runtimeState; private BackOfficeOwinUserManager _userManager; private BackOfficeSignInManager _signInManager; private readonly IUmbracoVersion _umbracoVersion; - private readonly IGridConfig _gridConfig; private readonly IContentSettings _contentSettings; - private readonly TreeCollection _treeCollection; private readonly IHostingEnvironment _hostingEnvironment; private readonly IRuntimeSettings _runtimeSettings; private readonly ISecuritySettings _securitySettings; - private readonly IRuntimeMinifier _runtimeMinifier; public BackOfficeController( UmbracoFeatures features, @@ -59,29 +55,20 @@ namespace Umbraco.Web.Editors ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger, - IRuntimeState runtimeState, IUmbracoVersion umbracoVersion, - IGridConfig gridConfig, IContentSettings contentSettings, - TreeCollection treeCollection, IHostingEnvironment hostingEnvironment, - IHttpContextAccessor httpContextAccessor, IRuntimeSettings settings, - ISecuritySettings securitySettings, - IRuntimeMinifier runtimeMinifier) + ISecuritySettings securitySettings) : base(globalSettings, umbracoContextAccessor, services, appCaches, profilingLogger) { _features = features; - _runtimeState = runtimeState; _umbracoVersion = umbracoVersion; - _gridConfig = gridConfig ?? throw new ArgumentNullException(nameof(gridConfig)); _contentSettings = contentSettings ?? throw new ArgumentNullException(nameof(contentSettings)); - _treeCollection = treeCollection ?? throw new ArgumentNullException(nameof(treeCollection)); _hostingEnvironment = hostingEnvironment; _runtimeSettings = settings; _securitySettings = securitySettings; - _runtimeMinifier = runtimeMinifier; } protected BackOfficeSignInManager SignInManager => _signInManager ?? (_signInManager = OwinContext.GetBackOfficeSignInManager()); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 5fcea0f085..1a03040777 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -25,6 +25,7 @@ 4 false latest + ..\UnusedCode.ruleset portable @@ -408,8 +409,6 @@ Mvc\web.config - - - + \ No newline at end of file diff --git a/src/Umbraco.Web/UrlHelperRenderExtensions.cs b/src/Umbraco.Web/UrlHelperRenderExtensions.cs index 3a836f5d8b..ba6a0540f0 100644 --- a/src/Umbraco.Web/UrlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/UrlHelperRenderExtensions.cs @@ -18,9 +18,6 @@ namespace Umbraco.Web /// public static class UrlHelperRenderExtensions { - - private static readonly IHtmlString EmptyHtmlString = new HtmlString(string.Empty); - // #region GetCropUrl // // ///