Merge pull request #8603 from umbraco/netcore/feature/6976-migrate-preview-middleware
Migrates preview auth Middleware
This commit is contained in:
@@ -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<ILanguage> Languages { get; }
|
||||
|
||||
public BackOfficePreviewModel(UmbracoFeatures features, IGlobalSettings globalSettings, IUmbracoVersion umbracoVersion, IEnumerable<ILanguage> languages, IContentSettings contentSettings, IHostingEnvironment hostingEnvironment, IRuntimeSettings runtimeSettings, ISecuritySettings securitySettings)
|
||||
: base(features, globalSettings, umbracoVersion, contentSettings, hostingEnvironment, runtimeSettings, securitySettings)
|
||||
|
||||
public BackOfficePreviewModel(UmbracoFeatures features, IEnumerable<ILanguage> languages)
|
||||
{
|
||||
_features = features;
|
||||
Languages = languages;
|
||||
}
|
||||
|
||||
public IEnumerable<ILanguage> Languages { get; }
|
||||
public bool DisableDevicePreview => _features.Disabled.DisableDevicePreview;
|
||||
public string PreviewExtendedHeaderView => _features.Enabled.PreviewExtendedView;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -9,15 +9,6 @@ namespace Umbraco.Web
|
||||
return cookieManager.GetCookieValue(Constants.Web.PreviewCookieName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does a preview cookie exist ?
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
public static bool HasPreviewCookie(this ICookieManager cookieManager)
|
||||
{
|
||||
return cookieManager.HasCookie(Constants.Web.PreviewCookieName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<IWebHostEnvironment>();
|
||||
_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/")]
|
||||
@@ -429,7 +429,6 @@
|
||||
</Compile>
|
||||
<Compile Include="UmbracoExamine\ExamineDemoDataMediaService.cs" />
|
||||
<Compile Include="UmbracoExamine\ExamineDemoDataContentService.cs" />
|
||||
<Compile Include="CoreThings\UriExtensionsTests.cs" />
|
||||
<Compile Include="Composing\PackageActionCollectionTests.cs" />
|
||||
<Compile Include="Composing\TypeLoaderExtensions.cs" />
|
||||
<Compile Include="Composing\TypeLoaderTests.cs" />
|
||||
@@ -558,6 +557,7 @@
|
||||
<Content Include="Services\Importing\XsltSearch-Package.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Models\Mapping\" />
|
||||
<Folder Include="Models\Mapping" />
|
||||
<Folder Include="Web\Validation" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -68,7 +68,9 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> 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));
|
||||
|
||||
@@ -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<PreviewController>() + ".cshtml")
|
||||
.Replace("\\", "/"); // convert to forward slashes since it's a virtual path
|
||||
|
||||
return View(viewPath, model);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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)
|
||||
|
||||
@@ -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<PreviewAuthenticationMiddleware>();
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace Umbraco.Web.BackOffice.Runtime
|
||||
composition.Register<BackOfficeSessionIdValidator>(Lifetime.Request);
|
||||
composition.Register<BackOfficeSecurityStampValidator>(Lifetime.Request);
|
||||
|
||||
composition.RegisterUnique<PreviewAuthenticationMiddleware>();
|
||||
composition.RegisterUnique<IBackOfficeAntiforgery, BackOfficeAntiforgery>();
|
||||
|
||||
// register back office trees
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
/// A custom cookie manager that is used to read the cookie from the request.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
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
|
||||
/// </remarks>
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
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<IOptionsSnapshot<CookieAuthenticationOptions>>()
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,17 @@ namespace Umbraco.Extensions
|
||||
{
|
||||
public static class HttpRequestExtensions
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Check if a preview cookie exist
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Umbraco Preview</title>
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="pinterest" content="nopin" />
|
||||
|
||||
@Html.Raw(await runtimeMinifier.RenderCssHereAsync(BackOfficeWebAssets.UmbracoPreviewCssBundleName))
|
||||
|
||||
</head>
|
||||
<body id="canvasdesignerPanel" ng-mouseover="outlinePositionHide()" ng-controller="previewController" ng-class="{'tabbing-active': tabbingActive === true}" ng-click="windowClickHandler($event)">
|
||||
<div class="wait" ng-show="!frameLoaded"></div>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Model.PreviewExtendedHeaderView))
|
||||
{
|
||||
@await Html.PartialAsync(Model.PreviewExtendedHeaderView)
|
||||
}
|
||||
|
||||
<div id="demo-iframe-wrapper" class="{{previewDevice.css}}">
|
||||
<preview-i-frame src="pageUrl" on-loaded="onFrameLoaded(iframe)"></preview-i-frame>
|
||||
</div>
|
||||
<div class="canvasdesigner" ng-init="showDevicesPreview = true; showDevices = !@(disableDevicePreview);" ng-mouseenter="positionSelectedHide()">
|
||||
<div class="menu-bar selected">
|
||||
|
||||
<div class="menu-bar__title">Preview Mode</div>
|
||||
|
||||
<div class="menu-bar__right-part">
|
||||
|
||||
<div class="preview-menu-option" ng-class="{'--open': sizeOpen === true}" ng-click="$event.stopPropagation()">
|
||||
<button class="menu-bar__button umb-outline" ng-click="toggleSizeOpen()"><i class="icon {{previewDevice.icon}}"></i><span>{{previewDevice.title}}</span></button>
|
||||
<div class="dropdown-menu">
|
||||
<button ng-repeat="device in devices" class="menu-bar__button umb-outline" ng-class="{ '--active':previewDevice === device }" ng-click="updatePreviewDevice(device)">
|
||||
<i class="icon {{device.icon}}"></i><span>{{device.title}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.Languages != null && Model.Languages.Count() > 1)
|
||||
{
|
||||
<div class="preview-menu-option" ng-class="{'--open': cultureOpen === true}" ng-click="$event.stopPropagation()">
|
||||
<button class="menu-bar__button umb-outline" ng-click="toggleCultureOpen()"><i class="icon icon-globe-europe---africa"></i><span>{{currentCulture.title}}</span></button>
|
||||
<div class="dropdown-menu">
|
||||
@foreach (var language in Model.Languages)
|
||||
{
|
||||
<button class="menu-bar__button umb-outline" ng-class="{ '--active': currentCultureIso === '@language.IsoCode' || (@language.IsDefault.ToString().ToLower() && currentCultureIso === null) }" ng-click="changeCulture('@language.IsoCode')" ng-init="registerCulture('@language.IsoCode', '@language.CultureName', @language.IsDefault.ToString().ToLower())">
|
||||
<i class="icon icon-globe-europe---africa"></i><span>@language.CultureName</span>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<button ng-click="exitPreview()" title="Exit Preview" class="menu-bar__button umb-outline">
|
||||
<i class="icon icon-power"></i><span>Exit</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="../lib/lazyload-js/lazyload.min.js"></script>
|
||||
<script src="@Url.GetUrlWithCacheBust("Application", "Preview", null, hostingEnvironment, umbracoVersion, runtimeMinifier)"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -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());
|
||||
|
||||
@@ -97,16 +97,6 @@ namespace Umbraco.Web
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does a preview cookie exist ?
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
public static bool HasPreviewCookie(this IOwinRequest request)
|
||||
{
|
||||
return request.Cookies[Constants.Web.PreviewCookieName] != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cookie's string value
|
||||
/// </summary>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In order for preview to work this needs to be called
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <param name="umbracoContextAccessor"></param>
|
||||
/// <param name="runtimeState"></param>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="securitySettings"></param>
|
||||
/// <param name="ioHelper"></param>
|
||||
/// <param name="hostingEnvironment"></param>
|
||||
/// <param name="requestCache"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// By default this will be configured to execute on PipelineStage.PostAuthenticate
|
||||
/// </remarks>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In order for preview to work this needs to be called
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <param name="umbracoContextAccessor"></param>
|
||||
/// <param name="runtimeState"></param>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="securitySettings"></param>
|
||||
/// <param name="ioHelper"></param>
|
||||
/// <param name="hostingEnvironment"></param>
|
||||
/// <param name="requestCache"></param>
|
||||
/// <param name="stage"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates the middleware with an optional pointer to the next component.
|
||||
/// </summary>
|
||||
/// <param name="next"/>
|
||||
/// <param name="cookieOptions"></param>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="hostingEnvironment"></param>
|
||||
public PreviewAuthenticationMiddleware(OwinMiddleware next,
|
||||
UmbracoBackOfficeCookieAuthOptions cookieOptions, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) : base(next)
|
||||
{
|
||||
_cookieOptions = cookieOptions;
|
||||
_globalSettings = globalSettings;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process an individual request.
|
||||
/// </summary>
|
||||
/// <param name="context"/>
|
||||
/// <returns/>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<CodeAnalysisRuleSet>..\UnusedCode.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>portable</DebugType>
|
||||
@@ -262,7 +263,6 @@
|
||||
<Compile Include="Security\ForceRenewalCookieAuthenticationMiddleware.cs" />
|
||||
<Compile Include="Security\GetUserSecondsMiddleWare.cs" />
|
||||
<Compile Include="Security\IUmbracoBackOfficeTwoFactorOptions.cs" />
|
||||
<Compile Include="Security\PreviewAuthenticationMiddleware.cs" />
|
||||
<Compile Include="Security\WebAuthExtensions.cs" />
|
||||
<Compile Include="WebAssets\CDF\UmbracoClientDependencyLoader.cs" />
|
||||
<Compile Include="UmbracoDefaultOwinStartup.cs" />
|
||||
@@ -410,5 +410,6 @@
|
||||
<Link>Mvc\web.config</Link>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -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<OwinMiddlewareConfiguredEventArgs> MiddlewareConfigured;
|
||||
|
||||
@@ -18,9 +18,6 @@ namespace Umbraco.Web
|
||||
/// </summary>
|
||||
public static class UrlHelperRenderExtensions
|
||||
{
|
||||
|
||||
private static readonly IHtmlString EmptyHtmlString = new HtmlString(string.Empty);
|
||||
|
||||
// #region GetCropUrl
|
||||
//
|
||||
// /// <summary>
|
||||
|
||||
Reference in New Issue
Block a user