Merge remote-tracking branch 'origin/netcore/netcore' into netcore/feature/migrate-remaining-trees
This commit is contained in:
@@ -39,7 +39,7 @@ namespace Umbraco.Configuration.Models
|
||||
}
|
||||
|
||||
public int TimeOutInMinutes => _configuration.GetValue(Prefix + "TimeOutInMinutes", 20);
|
||||
public string DefaultUILanguage => _configuration.GetValue(Prefix + "TimeOutInMinutes", "en-US");
|
||||
public string DefaultUILanguage => _configuration.GetValue(Prefix + "DefaultUILanguage", "en-US");
|
||||
|
||||
public bool HideTopLevelNodeFromPath =>
|
||||
_configuration.GetValue(Prefix + "HideTopLevelNodeFromPath", false);
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Umbraco.Configuration.Models
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public bool KeepUserLoggedIn => _configuration.GetValue(Prefix + "KeepUserLoggedIn", true);
|
||||
public bool KeepUserLoggedIn => _configuration.GetValue(Prefix + "KeepUserLoggedIn", false);
|
||||
|
||||
public bool HideDisabledUsersInBackoffice =>
|
||||
_configuration.GetValue(Prefix + "HideDisabledUsersInBackoffice", false);
|
||||
|
||||
@@ -36,7 +36,6 @@ namespace Umbraco.Core.BackOffice
|
||||
private UmbracoBackOfficeIdentity(ClaimsIdentity identity)
|
||||
: base(identity.Claims, Constants.Security.BackOfficeAuthenticationType)
|
||||
{
|
||||
Actor = identity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -89,7 +88,7 @@ namespace Umbraco.Core.BackOffice
|
||||
if (string.IsNullOrWhiteSpace(realName)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(realName));
|
||||
if (string.IsNullOrWhiteSpace(culture)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(culture));
|
||||
if (string.IsNullOrWhiteSpace(securityStamp)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(securityStamp));
|
||||
Actor = childIdentity;
|
||||
|
||||
AddRequiredClaims(userId, username, realName, startContentNodes, startMediaNodes, culture, securityStamp, allowedApps, roles);
|
||||
}
|
||||
|
||||
@@ -204,5 +203,18 @@ namespace Umbraco.Core.BackOffice
|
||||
|
||||
public string[] Roles => this.FindAll(x => x.Type == DefaultRoleClaimType).Select(role => role.Value).ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Overridden to remove any temporary claims that shouldn't be copied
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override ClaimsIdentity Clone()
|
||||
{
|
||||
var clone = base.Clone();
|
||||
|
||||
foreach (var claim in clone.FindAll(x => x.Type == Constants.Security.TicketExpiresClaimType).ToList())
|
||||
clone.RemoveClaim(claim);
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ namespace Umbraco.Core
|
||||
/// </summary>
|
||||
public static class AppSettings
|
||||
{
|
||||
// TODO: Are these all obsolete in netcore now?
|
||||
|
||||
public const string MainDomLock = "Umbraco.Core.MainDom.Lock";
|
||||
|
||||
// TODO: Kill me - still used in Umbraco.Core.IO.SystemFiles:27
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
public static class Mvc
|
||||
{
|
||||
public const string InstallArea = "UmbracoInstall";
|
||||
public const string BackOfficePathSegment = "BackOffice"; // The path segment prefix for all back office controllers
|
||||
public const string BackOfficeArea = "UmbracoBackOffice"; // Used for area routes of non-api controllers
|
||||
public const string BackOfficeApiArea = "UmbracoApi"; // Same name as v8 so all routing remains the same
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ namespace Umbraco.Core.IO
|
||||
public class PhysicalFileSystem : IFileSystem
|
||||
{
|
||||
private readonly IIOHelper _ioHelper;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
// the rooted, filesystem path, using directory separator chars, NOT ending with a separator
|
||||
@@ -34,19 +33,19 @@ namespace Umbraco.Core.IO
|
||||
public PhysicalFileSystem(IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, ILogger logger, string virtualRoot)
|
||||
{
|
||||
_ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper));
|
||||
_hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
|
||||
if (hostingEnvironment == null) throw new ArgumentNullException(nameof(hostingEnvironment));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
if (virtualRoot == null) throw new ArgumentNullException("virtualRoot");
|
||||
if (virtualRoot == null) throw new ArgumentNullException(nameof(virtualRoot));
|
||||
if (virtualRoot.StartsWith("~/") == false)
|
||||
throw new ArgumentException("The virtualRoot argument must be a virtual path and start with '~/'");
|
||||
|
||||
_rootPath = EnsureDirectorySeparatorChar(_hostingEnvironment.MapPathContentRoot(virtualRoot)).TrimEnd(Path.DirectorySeparatorChar);
|
||||
_rootPath = EnsureDirectorySeparatorChar(hostingEnvironment.MapPathContentRoot(virtualRoot)).TrimEnd(Path.DirectorySeparatorChar);
|
||||
_rootPathFwd = EnsureUrlSeparatorChar(_rootPath);
|
||||
_rootUrl = EnsureUrlSeparatorChar(_ioHelper.ResolveUrl(virtualRoot)).TrimEnd('/');
|
||||
_rootUrl = EnsureUrlSeparatorChar(hostingEnvironment.ToAbsolute(virtualRoot)).TrimEnd('/');
|
||||
}
|
||||
|
||||
public PhysicalFileSystem(IIOHelper ioHelper, ILogger logger, string rootPath, string rootUrl)
|
||||
public PhysicalFileSystem(IIOHelper ioHelper,IHostingEnvironment hostingEnvironment, ILogger logger, string rootPath, string rootUrl)
|
||||
{
|
||||
_ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
@@ -62,8 +61,7 @@ namespace Umbraco.Core.IO
|
||||
if (Path.IsPathRooted(rootPath) == false)
|
||||
{
|
||||
// but the test suite App.config cannot really "root" anything so we have to do it here
|
||||
// TODO: This will map to the web content root (www) not the web app root, is that what we want? Else we need to use IHostingEnvironment.ApplicationPhysicalPath
|
||||
var localRoot = _ioHelper.MapPath("~");
|
||||
var localRoot = hostingEnvironment.MapPathContentRoot("~");
|
||||
rootPath = Path.Combine(localRoot, rootPath);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Strings;
|
||||
|
||||
namespace Umbraco.Core.Net
|
||||
namespace Umbraco.Core.Templates
|
||||
{
|
||||
/// <summary>
|
||||
/// Methods used to render umbraco components as HTML in templates
|
||||
@@ -2,7 +2,6 @@
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Web;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Templates;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
@@ -10,7 +9,7 @@ using Umbraco.Core.Strings;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.Macros;
|
||||
|
||||
namespace Umbraco.Core.Net
|
||||
namespace Umbraco.Core.Templates
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
@@ -19,7 +18,6 @@ namespace Umbraco.Core.Net
|
||||
/// <remarks>
|
||||
/// Used by UmbracoHelper
|
||||
/// </remarks>
|
||||
// Migrated to .NET Core
|
||||
public class UmbracoComponentRenderer : IUmbracoComponentRenderer
|
||||
{
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
@@ -76,13 +76,13 @@ namespace Umbraco.Core
|
||||
}
|
||||
|
||||
//check for special back office paths
|
||||
if (urlPath.InvariantStartsWith("/" + mvcArea + "/BackOffice/")
|
||||
|| urlPath.InvariantStartsWith("/" + mvcArea + "/Preview/"))
|
||||
if (urlPath.InvariantStartsWith("/" + mvcArea + "/" + Constants.Web.Mvc.BackOfficePathSegment + "/"))
|
||||
{
|
||||
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/"))
|
||||
{
|
||||
|
||||
@@ -29,5 +29,7 @@ namespace Umbraco.Web.Models
|
||||
public string EventElement { get; set; }
|
||||
[DataMember(Name = "customProperties")]
|
||||
public JObject CustomProperties { get; set; }
|
||||
[DataMember(Name = "skipStepIfVisible")]
|
||||
public string SkipStepIfVisible { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ using Umbraco.Core.Migrations;
|
||||
using Umbraco.Core.Migrations.Install;
|
||||
using Umbraco.Core.Migrations.PostMigrations;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Net;
|
||||
using Umbraco.Core.Templates;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.PropertyEditors.Validators;
|
||||
@@ -40,7 +40,6 @@ using Umbraco.Web.Features;
|
||||
using Umbraco.Web.HealthCheck;
|
||||
using Umbraco.Web.HealthCheck.NotificationMethods;
|
||||
using Umbraco.Web.Install;
|
||||
using Umbraco.Web.Macros;
|
||||
using Umbraco.Web.Media.EmbedProviders;
|
||||
using Umbraco.Web.Migrations.PostMigrations;
|
||||
using Umbraco.Web.Models.PublishedContent;
|
||||
|
||||
77
src/Umbraco.Tests.Common/TestClone.cs
Normal file
77
src/Umbraco.Tests.Common/TestClone.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Tests.Common
|
||||
{
|
||||
public class TestClone : IDeepCloneable, IEquatable<TestClone>
|
||||
{
|
||||
public TestClone(Guid id)
|
||||
{
|
||||
Id = id;
|
||||
IsClone = true;
|
||||
}
|
||||
|
||||
public TestClone()
|
||||
{
|
||||
Id = Guid.NewGuid();
|
||||
}
|
||||
|
||||
public Guid Id { get; }
|
||||
public bool IsClone { get; }
|
||||
|
||||
public object DeepClone()
|
||||
{
|
||||
return new TestClone(Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the current object is equal to another object of the same type.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
|
||||
/// </returns>
|
||||
/// <param name="other">An object to compare with this object.</param>
|
||||
public bool Equals(TestClone other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return Id.Equals(other.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified object is equal to the current object.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if the specified object is equal to the current object; otherwise, false.
|
||||
/// </returns>
|
||||
/// <param name="obj">The object to compare with the current object. </param>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((TestClone)obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serves as the default hash function.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A hash code for the current object.
|
||||
/// </returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Id.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator ==(TestClone left, TestClone right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(TestClone left, TestClone right)
|
||||
{
|
||||
return Equals(left, right) == false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Tests.Integration.Implementations;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.BackOffice.Controllers;
|
||||
using Umbraco.Web.BackOffice.Security;
|
||||
|
||||
namespace Umbraco.Tests.Security
|
||||
{
|
||||
[TestFixture]
|
||||
public class BackOfficeCookieManagerTests
|
||||
{
|
||||
[Test]
|
||||
public void ShouldAuthenticateRequest_When_Not_Configured()
|
||||
{
|
||||
var testHelper = new TestHelper();
|
||||
|
||||
var httpContextAccessor = testHelper.GetHttpContextAccessor();
|
||||
var globalSettings = testHelper.SettingsForTests.GenerateMockGlobalSettings();
|
||||
|
||||
var runtime = Mock.Of<IRuntimeState>(x => x.Level == RuntimeLevel.Install);
|
||||
var mgr = new BackOfficeCookieManager(
|
||||
Mock.Of<IUmbracoContextAccessor>(),
|
||||
runtime,
|
||||
Mock.Of<IHostingEnvironment>(),
|
||||
globalSettings,
|
||||
Mock.Of<IRequestCache>(),
|
||||
Mock.Of<LinkGenerator>());
|
||||
|
||||
var result = mgr.ShouldAuthenticateRequest(new Uri("http://localhost/umbraco"));
|
||||
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShouldAuthenticateRequest_When_Configured()
|
||||
{
|
||||
var testHelper = new TestHelper();
|
||||
|
||||
|
||||
//hostingEnvironment.ToAbsolute(globalSettings.UmbracoPath);
|
||||
|
||||
var httpContextAccessor = testHelper.GetHttpContextAccessor();
|
||||
var globalSettings = testHelper.SettingsForTests.GenerateMockGlobalSettings();
|
||||
|
||||
var runtime = Mock.Of<IRuntimeState>(x => x.Level == RuntimeLevel.Run);
|
||||
var mgr = new BackOfficeCookieManager(
|
||||
Mock.Of<IUmbracoContextAccessor>(),
|
||||
runtime,
|
||||
Mock.Of<IHostingEnvironment>(x => x.ApplicationVirtualPath == "/" && x.ToAbsolute(globalSettings.UmbracoPath) == "/umbraco"),
|
||||
globalSettings,
|
||||
Mock.Of<IRequestCache>(),
|
||||
Mock.Of<LinkGenerator>());
|
||||
|
||||
var result = mgr.ShouldAuthenticateRequest(new Uri("http://localhost/umbraco"));
|
||||
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShouldAuthenticateRequest_Is_Back_Office()
|
||||
{
|
||||
var testHelper = new TestHelper();
|
||||
|
||||
var httpContextAccessor = testHelper.GetHttpContextAccessor();
|
||||
var globalSettings = testHelper.SettingsForTests.GenerateMockGlobalSettings();
|
||||
|
||||
var runtime = Mock.Of<IRuntimeState>(x => x.Level == RuntimeLevel.Run);
|
||||
|
||||
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,
|
||||
Mock.Of<IRequestCache>(),
|
||||
GetMockLinkGenerator(out var remainingTimeoutSecondsPath, out var isAuthPath));
|
||||
|
||||
var result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost{remainingTimeoutSecondsPath}"));
|
||||
Assert.IsTrue(result);
|
||||
|
||||
result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost{isAuthPath}"));
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShouldAuthenticateRequest_Force_Auth()
|
||||
{
|
||||
var testHelper = new TestHelper();
|
||||
|
||||
var httpContextAccessor = testHelper.GetHttpContextAccessor();
|
||||
var globalSettings = testHelper.SettingsForTests.GenerateMockGlobalSettings();
|
||||
|
||||
var runtime = Mock.Of<IRuntimeState>(x => x.Level == RuntimeLevel.Run);
|
||||
|
||||
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,
|
||||
Mock.Of<IRequestCache>(x => x.IsAvailable == true && x.Get(Constants.Security.ForceReAuthFlag) == "not null"),
|
||||
GetMockLinkGenerator(out var remainingTimeoutSecondsPath, out var isAuthPath));
|
||||
|
||||
var result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost/notbackoffice"));
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShouldAuthenticateRequest_Not_Back_Office()
|
||||
{
|
||||
var testHelper = new TestHelper();
|
||||
|
||||
var httpContextAccessor = testHelper.GetHttpContextAccessor();
|
||||
var globalSettings = testHelper.SettingsForTests.GenerateMockGlobalSettings();
|
||||
|
||||
var runtime = Mock.Of<IRuntimeState>(x => x.Level == RuntimeLevel.Run);
|
||||
|
||||
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,
|
||||
Mock.Of<IRequestCache>(),
|
||||
GetMockLinkGenerator(out var remainingTimeoutSecondsPath, out var isAuthPath));
|
||||
|
||||
var result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost/notbackoffice"));
|
||||
Assert.IsFalse(result);
|
||||
result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost/umbraco/api/notbackoffice"));
|
||||
Assert.IsFalse(result);
|
||||
result = mgr.ShouldAuthenticateRequest(new Uri($"http://localhost/umbraco/surface/notbackoffice"));
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
private LinkGenerator GetMockLinkGenerator(out string remainingTimeoutSecondsPath, out string isAuthPath)
|
||||
{
|
||||
var controllerName = ControllerExtensions.GetControllerName<AuthenticationController>();
|
||||
|
||||
// this path is not a back office request even though it's in the same controller - it's a 'special' endpoint
|
||||
var rPath = remainingTimeoutSecondsPath = $"/umbraco/{Constants.Web.Mvc.BackOfficePathSegment}/{Constants.Web.Mvc.BackOfficeApiArea}/{controllerName}/{nameof(AuthenticationController.GetRemainingTimeoutSeconds)}".ToLower();
|
||||
|
||||
// this is on the same controller but is considered a back office request
|
||||
var aPath = isAuthPath = $"/umbraco/{Constants.Web.Mvc.BackOfficePathSegment}/{Constants.Web.Mvc.BackOfficeApiArea}/{controllerName}/{nameof(AuthenticationController.IsAuthenticated)}".ToLower();
|
||||
|
||||
var linkGenerator = new Mock<LinkGenerator>();
|
||||
linkGenerator.Setup(x => x.GetPathByAddress(
|
||||
//It.IsAny<HttpContext>(),
|
||||
It.IsAny<RouteValuesAddress>(),
|
||||
//It.IsAny<RouteValueDictionary>(),
|
||||
It.IsAny<RouteValueDictionary>(),
|
||||
It.IsAny<PathString>(),
|
||||
It.IsAny<FragmentString>(),
|
||||
It.IsAny<LinkOptions>())).Returns((RouteValuesAddress address, RouteValueDictionary routeVals1, PathString path, FragmentString fragment, LinkOptions options) =>
|
||||
{
|
||||
if (routeVals1["action"].ToString() == nameof(AuthenticationController.GetRemainingTimeoutSeconds))
|
||||
return rPath;
|
||||
if (routeVals1["action"].ToString() == nameof(AuthenticationController.IsAuthenticated).ToLower())
|
||||
return aPath;
|
||||
return null;
|
||||
});
|
||||
|
||||
return linkGenerator.Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Tests.CoreThings
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
[TestFixture]
|
||||
public class AttemptTests
|
||||
{
|
||||
|
||||
[Test]
|
||||
public void AttemptIf()
|
||||
{
|
||||
@@ -71,7 +71,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
}
|
||||
|
||||
[TestCase(ClaimTypes.NameIdentifier, _testUserId)]
|
||||
[TestCase(ClaimTypes.Name, _testUserName)]
|
||||
[TestCase(ClaimTypes.Name, _testUserName)]
|
||||
public async Task CreateAsync_Should_Include_Claim(string expectedClaimType, object expectedClaimValue)
|
||||
{
|
||||
var sut = CreateSut();
|
||||
@@ -79,7 +79,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
var claimsPrincipal = await sut.CreateAsync(_testUser);
|
||||
|
||||
Assert.True(claimsPrincipal.HasClaim(expectedClaimType, expectedClaimValue.ToString()));
|
||||
Assert.True(claimsPrincipal.GetUmbracoIdentity().Actor.HasClaim(expectedClaimType, expectedClaimValue.ToString()));
|
||||
Assert.True(claimsPrincipal.GetUmbracoIdentity().HasClaim(expectedClaimType, expectedClaimValue.ToString()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -94,9 +94,9 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
var sut = CreateSut();
|
||||
|
||||
var claimsPrincipal = await sut.CreateAsync(_testUser);
|
||||
|
||||
|
||||
Assert.True(claimsPrincipal.HasClaim(expectedClaimType, expectedClaimValue));
|
||||
Assert.True(claimsPrincipal.GetUmbracoIdentity().Actor.HasClaim(expectedClaimType, expectedClaimValue));
|
||||
Assert.True(claimsPrincipal.GetUmbracoIdentity().HasClaim(expectedClaimType, expectedClaimValue));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -112,7 +112,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
var sut = CreateSut();
|
||||
|
||||
var claimsPrincipal = await sut.CreateAsync(_testUser);
|
||||
|
||||
|
||||
Assert.True(claimsPrincipal.HasClaim(expectedClaimType, expectedClaimValue));
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
|
||||
var claimsPrincipal = await sut.CreateAsync(_testUser);
|
||||
|
||||
Assert.True(claimsPrincipal.GetUmbracoIdentity().Actor.HasClaim(expectedClaimType, expectedClaimValue));
|
||||
Assert.True(claimsPrincipal.GetUmbracoIdentity().HasClaim(expectedClaimType, expectedClaimValue));
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
if (!UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity, out var backofficeIdentity))
|
||||
Assert.Fail();
|
||||
|
||||
Assert.IsNull(backofficeIdentity.Actor);
|
||||
Assert.AreEqual(1234, backofficeIdentity.Id);
|
||||
//Assert.AreEqual(sessionId, backofficeIdentity.SessionId);
|
||||
Assert.AreEqual(securityStamp, backofficeIdentity.SecurityStamp);
|
||||
@@ -60,7 +61,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
new Claim(ClaimTypes.Name, "testing", ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
});
|
||||
|
||||
if (UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity, out var backofficeIdentity))
|
||||
if (UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity, out _))
|
||||
Assert.Fail();
|
||||
|
||||
Assert.Pass();
|
||||
@@ -83,7 +84,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
new Claim(ClaimsIdentity.DefaultRoleClaimType, "admin", ClaimValueTypes.String, TestIssuer, TestIssuer),
|
||||
});
|
||||
|
||||
if (UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity, out var backofficeIdentity))
|
||||
if (UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity, out _))
|
||||
Assert.Fail();
|
||||
|
||||
Assert.Pass();
|
||||
@@ -105,6 +106,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
1234, "testing", "hello world", new[] { 654 }, new[] { 654 }, "en-us", securityStamp, new[] { "content", "media" }, new[] { "admin" });
|
||||
|
||||
Assert.AreEqual(12, identity.Claims.Count());
|
||||
Assert.IsNull(identity.Actor);
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +118,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Core.BackOffice
|
||||
var identity = new UmbracoBackOfficeIdentity(
|
||||
1234, "testing", "hello world", new[] { 654 }, new[] { 654 }, "en-us", securityStamp, new[] { "content", "media" }, new[] { "admin" });
|
||||
|
||||
// this will be filtered out during cloning
|
||||
identity.AddClaim(new Claim(Constants.Security.TicketExpiresClaimType, "test"));
|
||||
|
||||
var cloned = identity.Clone();
|
||||
Assert.IsNull(cloned.Actor);
|
||||
|
||||
Assert.AreEqual(10, cloned.Claims.Count());
|
||||
}
|
||||
|
||||
@@ -3,9 +3,8 @@ using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web;
|
||||
|
||||
namespace Umbraco.Tests.CoreThings
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
public class ClaimsIdentityExtensionsTests
|
||||
{
|
||||
@@ -1,13 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Collections;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Tests.Common;
|
||||
|
||||
namespace Umbraco.Tests.Collections
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core.Collections
|
||||
{
|
||||
[TestFixture]
|
||||
public class DeepCloneableListTests
|
||||
@@ -85,7 +81,7 @@ namespace Umbraco.Tests.Collections
|
||||
list.Add(new TestClone());
|
||||
list.Add(new TestClone());
|
||||
|
||||
var cloned = (DeepCloneableList<TestClone>)list.DeepClone();
|
||||
var cloned = (DeepCloneableList<TestClone>) list.DeepClone();
|
||||
|
||||
//Test that each item in the sequence is equal - based on the equality comparer of TestClone (i.e. it's ID)
|
||||
Assert.IsTrue(list.SequenceEqual(cloned));
|
||||
@@ -97,77 +93,5 @@ namespace Umbraco.Tests.Collections
|
||||
Assert.AreNotSame(item, clone);
|
||||
}
|
||||
}
|
||||
|
||||
public class TestClone : IDeepCloneable, IEquatable<TestClone>
|
||||
{
|
||||
public TestClone(Guid id)
|
||||
{
|
||||
Id = id;
|
||||
IsClone = true;
|
||||
}
|
||||
|
||||
public TestClone()
|
||||
{
|
||||
Id = Guid.NewGuid();
|
||||
}
|
||||
|
||||
public Guid Id { get; private set; }
|
||||
public bool IsClone { get; private set; }
|
||||
|
||||
public object DeepClone()
|
||||
{
|
||||
return new TestClone(Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the current object is equal to another object of the same type.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
|
||||
/// </returns>
|
||||
/// <param name="other">An object to compare with this object.</param>
|
||||
public bool Equals(TestClone other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return Id.Equals(other.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified object is equal to the current object.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if the specified object is equal to the current object; otherwise, false.
|
||||
/// </returns>
|
||||
/// <param name="obj">The object to compare with the current object. </param>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((TestClone)obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serves as the default hash function.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A hash code for the current object.
|
||||
/// </returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Id.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator ==(TestClone left, TestClone right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(TestClone left, TestClone right)
|
||||
{
|
||||
return Equals(left, right) == false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Collections;
|
||||
|
||||
namespace Umbraco.Tests.Collections
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core.Collections
|
||||
{
|
||||
[TestFixture]
|
||||
public class OrderedHashSetTests
|
||||
@@ -12,7 +11,7 @@ namespace Umbraco.Tests.Collections
|
||||
public void Keeps_Last()
|
||||
{
|
||||
var list = new OrderedHashSet<MyClass>(keepOldest: false);
|
||||
var items = new[] { new MyClass("test"), new MyClass("test"), new MyClass("test") };
|
||||
var items = new[] {new MyClass("test"), new MyClass("test"), new MyClass("test")};
|
||||
foreach (var item in items)
|
||||
{
|
||||
list.Add(item);
|
||||
@@ -27,7 +26,7 @@ namespace Umbraco.Tests.Collections
|
||||
public void Keeps_First()
|
||||
{
|
||||
var list = new OrderedHashSet<MyClass>(keepOldest: true);
|
||||
var items = new[] { new MyClass("test"), new MyClass("test"), new MyClass("test") };
|
||||
var items = new[] {new MyClass("test"), new MyClass("test"), new MyClass("test")};
|
||||
foreach (var item in items)
|
||||
{
|
||||
list.Add(item);
|
||||
@@ -60,7 +59,7 @@ namespace Umbraco.Tests.Collections
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((MyClass)obj);
|
||||
return Equals((MyClass) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
@@ -3,7 +3,7 @@ using Lucene.Net.Index;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Tests.CoreThings
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
[TestFixture]
|
||||
public class DelegateExtensionsTests
|
||||
@@ -3,7 +3,7 @@ using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Trees;
|
||||
|
||||
namespace Umbraco.Tests.CoreThings
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
[TestFixture]
|
||||
public class EnumExtensionsTests
|
||||
@@ -1,10 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Tests.CoreThings
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
[TestFixture]
|
||||
public class EnumerableExtensionsTests
|
||||
@@ -2,7 +2,7 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Tests.CoreThings
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
public class GuidUtilsTests
|
||||
{
|
||||
@@ -3,7 +3,7 @@ using System.Text;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Tests.CoreThings
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
public class HexEncoderTests
|
||||
{
|
||||
@@ -2,7 +2,7 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Tests.Clr
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
[TestFixture]
|
||||
public class ReflectionTests
|
||||
@@ -25,37 +25,16 @@ namespace Umbraco.Tests.Clr
|
||||
Assert.Contains(typeof(object), types);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetInterfacesIsOk()
|
||||
{
|
||||
// tests that GetInterfaces gets _all_ interfaces
|
||||
// so the AllInterfaces extension method is useless
|
||||
|
||||
var type = typeof(Class2);
|
||||
var interfaces = type.GetInterfaces();
|
||||
Assert.AreEqual(2, interfaces.Length);
|
||||
Assert.Contains(typeof(IInterface1), interfaces);
|
||||
Assert.Contains(typeof(IInterface2), interfaces);
|
||||
}
|
||||
|
||||
#region Test Objects
|
||||
|
||||
interface IInterface1
|
||||
{ }
|
||||
|
||||
interface IInterface2 : IInterface1
|
||||
private class Class1
|
||||
{
|
||||
void Method();
|
||||
}
|
||||
|
||||
class Class1 : IInterface2
|
||||
private class Class2 : Class1
|
||||
{
|
||||
public void Method() { }
|
||||
}
|
||||
|
||||
class Class2 : Class1
|
||||
{ }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Umbraco.Tests.Clr
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
[TestFixture]
|
||||
public class ReflectionUtilitiesTests
|
||||
@@ -104,8 +104,6 @@ namespace Umbraco.Tests.Clr
|
||||
[Test]
|
||||
public void EmitMethodEmitsStaticStatic()
|
||||
{
|
||||
// static types cannot be used as type arguments
|
||||
//var method = ReflectionUtilities.EmitMethod<StaticClass1, Action>("Method");
|
||||
var method = ReflectionUtilities.EmitMethod<Action>(typeof (StaticClass1), "Method");
|
||||
method();
|
||||
}
|
||||
@@ -205,10 +203,6 @@ namespace Umbraco.Tests.Clr
|
||||
(var getter3, var setter3) = ReflectionUtilities.EmitPropertyGetterAndSetter<Class1, int>("Value3");
|
||||
Assert.AreEqual(42, getter3(class1));
|
||||
setter3(class1, 42);
|
||||
|
||||
// this is not supported yet
|
||||
//var getter4 = ReflectionUtilities.EmitPropertyGetter<Class1, object>("Value1", returned: typeof(int));
|
||||
//Assert.AreEqual(42, getter1(class1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -448,7 +442,7 @@ namespace Umbraco.Tests.Clr
|
||||
var propInt4 = type4.GetProperty("IntValue");
|
||||
Assert.IsNotNull(propInt4);
|
||||
|
||||
// ... if explicitely getting a value type
|
||||
// ... if explicitly getting a value type
|
||||
var getterInt4T = ReflectionUtilities.EmitPropertyGetter<Class4, int>(propInt4);
|
||||
Assert.IsNotNull(getterInt4T);
|
||||
var valueInt4T = getterInt4T(object4);
|
||||
@@ -2,7 +2,7 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Tests.CoreThings
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
[TestFixture]
|
||||
public class VersionExtensionTests
|
||||
@@ -18,7 +18,7 @@ namespace Umbraco.Tests.CoreThings
|
||||
[TestCase(0, 0, 1, 1, "0.0.1.0")]
|
||||
[TestCase(0, 0, 0, 1, "0.0.0.0")]
|
||||
[TestCase(7, 3, 0, 0, "7.2.2147483647.2147483647")]
|
||||
public void Subract_Revision(int major, int minor, int build, int rev, string outcome)
|
||||
public void Subtract_Revision(int major, int minor, int build, int rev, string outcome)
|
||||
{
|
||||
var version = new Version(major, minor, build, rev);
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.Xml.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Tests.CoreThings
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
[TestFixture]
|
||||
public class XmlExtensionsTests
|
||||
@@ -15,6 +15,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Lucene.Net.Contrib" Version="3.0.3" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
|
||||
|
||||
@@ -12,7 +12,7 @@ using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Tests.Collections;
|
||||
using Umbraco.Tests.Common;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Web.Cache;
|
||||
|
||||
@@ -41,12 +41,12 @@ namespace Umbraco.Tests.Cache
|
||||
[Test]
|
||||
public void Clones_List()
|
||||
{
|
||||
var original = new DeepCloneableList<DeepCloneableListTests.TestClone>(ListCloneBehavior.Always);
|
||||
original.Add(new DeepCloneableListTests.TestClone());
|
||||
original.Add(new DeepCloneableListTests.TestClone());
|
||||
original.Add(new DeepCloneableListTests.TestClone());
|
||||
var original = new DeepCloneableList<TestClone>(ListCloneBehavior.Always);
|
||||
original.Add(new TestClone());
|
||||
original.Add(new TestClone());
|
||||
original.Add(new TestClone());
|
||||
|
||||
var val = _provider.GetCacheItem<DeepCloneableList<DeepCloneableListTests.TestClone>>("test", () => original);
|
||||
var val = _provider.GetCacheItem<DeepCloneableList<TestClone>>("test", () => original);
|
||||
|
||||
Assert.AreEqual(original.Count, val.Count);
|
||||
foreach (var item in val)
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Umbraco.Tests.IO
|
||||
public class PhysicalFileSystemTests : AbstractFileSystemTests
|
||||
{
|
||||
public PhysicalFileSystemTests()
|
||||
: base(new PhysicalFileSystem(TestHelper.IOHelper, Mock.Of<ILogger>(), Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests"), "/Media/"))
|
||||
: base(new PhysicalFileSystem(TestHelper.IOHelper, TestHelper.GetHostingEnvironment(), Mock.Of<ILogger>(), Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FileSysTests"), "/Media/"))
|
||||
{ }
|
||||
|
||||
[SetUp]
|
||||
|
||||
@@ -54,14 +54,15 @@ namespace Umbraco.Tests.IO
|
||||
{
|
||||
var ioHelper = TestHelper.IOHelper;
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var hostingEnvironment = TestHelper.GetHostingEnvironment();
|
||||
|
||||
var path = ioHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
|
||||
var fs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowSystem/", "ignore");
|
||||
var fs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowSystem/", "ignore");
|
||||
var ss = new ShadowFileSystem(fs, sfs);
|
||||
|
||||
Directory.CreateDirectory(path + "/ShadowTests/d1");
|
||||
@@ -91,14 +92,15 @@ namespace Umbraco.Tests.IO
|
||||
{
|
||||
var ioHelper = TestHelper.IOHelper;
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var hostingEnvironment = TestHelper.GetHostingEnvironment();
|
||||
|
||||
var path = ioHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
|
||||
var fs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowSystem/", "ignore");
|
||||
var fs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowSystem/", "ignore");
|
||||
var ss = new ShadowFileSystem(fs, sfs);
|
||||
|
||||
Directory.CreateDirectory(path + "/ShadowTests/sub");
|
||||
@@ -143,14 +145,15 @@ namespace Umbraco.Tests.IO
|
||||
{
|
||||
var ioHelper = TestHelper.IOHelper;
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var hostingEnvironment = TestHelper.GetHostingEnvironment();
|
||||
|
||||
var path = ioHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
|
||||
var fs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowSystem/", "ignore");
|
||||
var fs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowSystem/", "ignore");
|
||||
var ss = new ShadowFileSystem(fs, sfs);
|
||||
|
||||
File.WriteAllText(path + "/ShadowTests/f1.txt", "foo");
|
||||
@@ -185,6 +188,7 @@ namespace Umbraco.Tests.IO
|
||||
{
|
||||
var ioHelper = TestHelper.IOHelper;
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var hostingEnvironment = TestHelper.GetHostingEnvironment();
|
||||
|
||||
var path = ioHelper.MapPath("FileSysTests");
|
||||
|
||||
@@ -192,8 +196,8 @@ namespace Umbraco.Tests.IO
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
|
||||
var fs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowSystem/", "ignore");
|
||||
var fs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowSystem/", "ignore");
|
||||
var ss = new ShadowFileSystem(fs, sfs);
|
||||
|
||||
Directory.CreateDirectory(path + "/ShadowTests/sub");
|
||||
@@ -244,14 +248,15 @@ namespace Umbraco.Tests.IO
|
||||
{
|
||||
var ioHelper = TestHelper.IOHelper;
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var hostingEnvironment = TestHelper.GetHostingEnvironment();
|
||||
|
||||
var path = ioHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
|
||||
var fs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowSystem/", "ignore");
|
||||
var fs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowSystem/", "ignore");
|
||||
var ss = new ShadowFileSystem(fs, sfs);
|
||||
|
||||
Assert.Throws<UnauthorizedAccessException>(() =>
|
||||
@@ -266,14 +271,15 @@ namespace Umbraco.Tests.IO
|
||||
{
|
||||
var ioHelper = TestHelper.IOHelper;
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var hostingEnvironment = TestHelper.GetHostingEnvironment();
|
||||
|
||||
var path = ioHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
|
||||
var fs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowSystem/", "ignore");
|
||||
var fs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper,hostingEnvironment, logger, path + "/ShadowSystem/", "ignore");
|
||||
var ss = new ShadowFileSystem(fs, sfs);
|
||||
|
||||
File.WriteAllText(path + "/ShadowTests/f2.txt", "foo");
|
||||
@@ -308,14 +314,15 @@ namespace Umbraco.Tests.IO
|
||||
{
|
||||
var ioHelper = TestHelper.IOHelper;
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var hostingEnvironment = TestHelper.GetHostingEnvironment();
|
||||
|
||||
var path = ioHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
|
||||
var fs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowSystem/", "ignore");
|
||||
var fs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowSystem/", "ignore");
|
||||
var ss = new ShadowFileSystem(fs, sfs);
|
||||
|
||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||
@@ -351,14 +358,15 @@ namespace Umbraco.Tests.IO
|
||||
{
|
||||
var ioHelper = TestHelper.IOHelper;
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var hostingEnvironment = TestHelper.GetHostingEnvironment();
|
||||
|
||||
var path = ioHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
|
||||
var fs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowSystem/", "ignore");
|
||||
var fs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowSystem/", "ignore");
|
||||
var ss = new ShadowFileSystem(fs, sfs);
|
||||
|
||||
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes("foo")))
|
||||
@@ -376,14 +384,15 @@ namespace Umbraco.Tests.IO
|
||||
{
|
||||
var ioHelper = TestHelper.IOHelper;
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var hostingEnvironment = TestHelper.GetHostingEnvironment();
|
||||
|
||||
var path = ioHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
|
||||
var fs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowSystem/", "ignore");
|
||||
var fs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowSystem/", "ignore");
|
||||
var ss = new ShadowFileSystem(fs, sfs);
|
||||
|
||||
Directory.CreateDirectory(path + "/ShadowTests/sub/sub");
|
||||
@@ -415,6 +424,7 @@ namespace Umbraco.Tests.IO
|
||||
{
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var ioHelper = TestHelper.IOHelper;
|
||||
var hostingEnvironment = TestHelper.GetHostingEnvironment();
|
||||
|
||||
var path = ioHelper.MapPath("FileSysTests");
|
||||
var shadowfs = ioHelper.MapPath(Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs");
|
||||
@@ -423,7 +433,7 @@ namespace Umbraco.Tests.IO
|
||||
|
||||
var scopedFileSystems = false;
|
||||
|
||||
var phy = new PhysicalFileSystem(ioHelper, logger, path, "ignore");
|
||||
var phy = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path, "ignore");
|
||||
|
||||
var container = Mock.Of<IFactory>();
|
||||
var fileSystems = new FileSystems(container, logger, ioHelper, SettingsForTests.GenerateMockGlobalSettings(), TestHelper.GetHostingEnvironment()) { IsScoped = () => scopedFileSystems };
|
||||
@@ -511,6 +521,7 @@ namespace Umbraco.Tests.IO
|
||||
{
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var ioHelper = TestHelper.IOHelper;
|
||||
var hostingEnvironment = TestHelper.GetHostingEnvironment();
|
||||
|
||||
var path = ioHelper.MapPath("FileSysTests");
|
||||
var shadowfs = ioHelper.MapPath(Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs");
|
||||
@@ -518,7 +529,7 @@ namespace Umbraco.Tests.IO
|
||||
|
||||
var scopedFileSystems = false;
|
||||
|
||||
var phy = new PhysicalFileSystem(ioHelper, logger, path, "ignore");
|
||||
var phy = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path, "ignore");
|
||||
|
||||
var container = Mock.Of<IFactory>();
|
||||
var fileSystems = new FileSystems(container, logger, ioHelper, SettingsForTests.GenerateMockGlobalSettings(), TestHelper.GetHostingEnvironment()) { IsScoped = () => scopedFileSystems };
|
||||
@@ -565,6 +576,7 @@ namespace Umbraco.Tests.IO
|
||||
{
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var ioHelper = TestHelper.IOHelper;
|
||||
var hostingEnvironment = TestHelper.GetHostingEnvironment();
|
||||
|
||||
var path = ioHelper.MapPath("FileSysTests");
|
||||
var shadowfs = ioHelper.MapPath(Constants.SystemDirectories.TempData.EnsureEndsWith('/') + "ShadowFs");
|
||||
@@ -572,7 +584,7 @@ namespace Umbraco.Tests.IO
|
||||
|
||||
var scopedFileSystems = false;
|
||||
|
||||
var phy = new PhysicalFileSystem(ioHelper, logger, path, "ignore");
|
||||
var phy = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path, "ignore");
|
||||
|
||||
var container = Mock.Of<IFactory>();
|
||||
var fileSystems = new FileSystems(container, logger, ioHelper, SettingsForTests.GenerateMockGlobalSettings(), TestHelper.GetHostingEnvironment()) { IsScoped = () => scopedFileSystems };
|
||||
@@ -683,14 +695,15 @@ namespace Umbraco.Tests.IO
|
||||
// Arrange
|
||||
var ioHelper = TestHelper.IOHelper;
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var hostingEnvironment = TestHelper.GetHostingEnvironment();
|
||||
|
||||
var path = ioHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
|
||||
var fs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowSystem/", "ignore");
|
||||
var fs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowSystem/", "ignore");
|
||||
var ss = new ShadowFileSystem(fs, sfs);
|
||||
|
||||
// Act
|
||||
@@ -718,14 +731,15 @@ namespace Umbraco.Tests.IO
|
||||
// Arrange
|
||||
var ioHelper = TestHelper.IOHelper;
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var hostingEnvironment = TestHelper.GetHostingEnvironment();
|
||||
|
||||
var path = ioHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
|
||||
var fs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowSystem/", "ignore");
|
||||
var fs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowSystem/", "ignore");
|
||||
var ss = new ShadowFileSystem(fs, sfs);
|
||||
|
||||
// Act
|
||||
@@ -756,14 +770,15 @@ namespace Umbraco.Tests.IO
|
||||
// Arrange
|
||||
var ioHelper = TestHelper.IOHelper;
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var hostingEnvironment = TestHelper.GetHostingEnvironment();
|
||||
|
||||
var path = ioHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
|
||||
var fs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowSystem/", "ignore");
|
||||
var fs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowSystem/", "ignore");
|
||||
var ss = new ShadowFileSystem(fs, sfs);
|
||||
|
||||
// Act
|
||||
@@ -791,14 +806,15 @@ namespace Umbraco.Tests.IO
|
||||
// Arrange
|
||||
var ioHelper = TestHelper.IOHelper;
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var hostingEnvironment = TestHelper.GetHostingEnvironment();
|
||||
|
||||
var path = ioHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
|
||||
var fs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowSystem/", "ignore");
|
||||
var fs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowSystem/", "ignore");
|
||||
var ss = new ShadowFileSystem(fs, sfs);
|
||||
|
||||
// Act
|
||||
@@ -829,14 +845,15 @@ namespace Umbraco.Tests.IO
|
||||
// Arrange
|
||||
var ioHelper = TestHelper.IOHelper;
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var hostingEnvironment = TestHelper.GetHostingEnvironment();
|
||||
|
||||
var path = ioHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
|
||||
var fs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowSystem/", "ignore");
|
||||
var fs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowSystem/", "ignore");
|
||||
var ss = new ShadowFileSystem(fs, sfs);
|
||||
|
||||
// Act
|
||||
@@ -879,14 +896,15 @@ namespace Umbraco.Tests.IO
|
||||
// Arrange
|
||||
var ioHelper = TestHelper.IOHelper;
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var hostingEnvironment = TestHelper.GetHostingEnvironment();
|
||||
|
||||
var path = ioHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
|
||||
var fs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowSystem/", "ignore");
|
||||
var fs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowSystem/", "ignore");
|
||||
var ss = new ShadowFileSystem(fs, sfs);
|
||||
|
||||
// Act
|
||||
@@ -916,14 +934,15 @@ namespace Umbraco.Tests.IO
|
||||
// Arrange
|
||||
var ioHelper = TestHelper.IOHelper;
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var hostingEnvironment = TestHelper.GetHostingEnvironment();
|
||||
|
||||
var path = ioHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
|
||||
var fs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowSystem/", "ignore");
|
||||
var fs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowTests/", "ignore");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowSystem/", "ignore");
|
||||
var ss = new ShadowFileSystem(fs, sfs);
|
||||
|
||||
// Act
|
||||
@@ -958,14 +977,15 @@ namespace Umbraco.Tests.IO
|
||||
// Arrange
|
||||
var ioHelper = TestHelper.IOHelper;
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var hostingEnvironment = TestHelper.GetHostingEnvironment();
|
||||
|
||||
var path = ioHelper.MapPath("FileSysTests");
|
||||
Directory.CreateDirectory(path);
|
||||
Directory.CreateDirectory(path + "/ShadowTests");
|
||||
Directory.CreateDirectory(path + "/ShadowSystem");
|
||||
|
||||
var fs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowTests/", "rootUrl");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, logger, path + "/ShadowSystem/", "rootUrl");
|
||||
var fs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowTests/", "rootUrl");
|
||||
var sfs = new PhysicalFileSystem(ioHelper, hostingEnvironment, logger, path + "/ShadowSystem/", "rootUrl");
|
||||
var ss = new ShadowFileSystem(fs, sfs);
|
||||
|
||||
// Act
|
||||
|
||||
@@ -0,0 +1,405 @@
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Web.Compose;
|
||||
|
||||
namespace Umbraco.Tests.PropertyEditors
|
||||
{
|
||||
[TestFixture]
|
||||
public class NestedContentPropertyComponentTests
|
||||
{
|
||||
[Test]
|
||||
public void Invalid_Json()
|
||||
{
|
||||
var component = new NestedContentPropertyComponent();
|
||||
|
||||
Assert.DoesNotThrow(() => component.CreateNestedContentKeys("this is not json", true));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void No_Nesting()
|
||||
{
|
||||
var guids = new[] { Guid.NewGuid(), Guid.NewGuid() };
|
||||
var guidCounter = 0;
|
||||
Func<Guid> guidFactory = () => guids[guidCounter++];
|
||||
|
||||
var json = @"[
|
||||
{""key"":""04a6dba8-813c-4144-8aca-86a3f24ebf08"",""name"":""Item 1"",""ncContentTypeAlias"":""nested"",""text"":""woot""},
|
||||
{""key"":""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"",""name"":""Item 2"",""ncContentTypeAlias"":""nested"",""text"":""zoot""}
|
||||
]";
|
||||
var expected = json
|
||||
.Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString())
|
||||
.Replace("d8e214d8-c5a5-4b45-9b51-4050dd47f5fa", guids[1].ToString());
|
||||
|
||||
var component = new NestedContentPropertyComponent();
|
||||
var result = component.CreateNestedContentKeys(json, false, guidFactory);
|
||||
|
||||
Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void One_Level_Nesting_Unescaped()
|
||||
{
|
||||
var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };
|
||||
var guidCounter = 0;
|
||||
Func<Guid> guidFactory = () => guids[guidCounter++];
|
||||
|
||||
var json = @"[{
|
||||
""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"",
|
||||
""name"": ""Item 1"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""woot""
|
||||
}, {
|
||||
""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"",
|
||||
""name"": ""Item 2"",
|
||||
""ncContentTypeAlias"": ""list"",
|
||||
""text"": ""zoot"",
|
||||
""subItems"": [{
|
||||
""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"",
|
||||
""name"": ""Item 1"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""woot""
|
||||
}, {
|
||||
""key"": ""fbde4288-8382-4e13-8933-ed9c160de050"",
|
||||
""name"": ""Item 2"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""zoot""
|
||||
}
|
||||
]
|
||||
}
|
||||
]";
|
||||
|
||||
var expected = json
|
||||
.Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString())
|
||||
.Replace("d8e214d8-c5a5-4b45-9b51-4050dd47f5fa", guids[1].ToString())
|
||||
.Replace("dccf550c-3a05-469e-95e1-a8f560f788c2", guids[2].ToString())
|
||||
.Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString());
|
||||
|
||||
var component = new NestedContentPropertyComponent();
|
||||
var result = component.CreateNestedContentKeys(json, false, guidFactory);
|
||||
|
||||
Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void One_Level_Nesting_Escaped()
|
||||
{
|
||||
var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };
|
||||
var guidCounter = 0;
|
||||
Func<Guid> guidFactory = () => guids[guidCounter++];
|
||||
|
||||
// we need to ensure the escaped json is consistent with how it will be re-escaped after parsing
|
||||
// and this is how to do that, the result will also include quotes around it.
|
||||
var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{
|
||||
""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"",
|
||||
""name"": ""Item 1"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""woot""
|
||||
}, {
|
||||
""key"": ""fbde4288-8382-4e13-8933-ed9c160de050"",
|
||||
""name"": ""Item 2"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""zoot""
|
||||
}
|
||||
]").ToString());
|
||||
|
||||
var json = @"[{
|
||||
""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"",
|
||||
""name"": ""Item 1"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""woot""
|
||||
}, {
|
||||
""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"",
|
||||
""name"": ""Item 2"",
|
||||
""ncContentTypeAlias"": ""list"",
|
||||
""text"": ""zoot"",
|
||||
""subItems"":" + subJsonEscaped + @"
|
||||
}
|
||||
]";
|
||||
|
||||
var expected = json
|
||||
.Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString())
|
||||
.Replace("d8e214d8-c5a5-4b45-9b51-4050dd47f5fa", guids[1].ToString())
|
||||
.Replace("dccf550c-3a05-469e-95e1-a8f560f788c2", guids[2].ToString())
|
||||
.Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString());
|
||||
|
||||
var component = new NestedContentPropertyComponent();
|
||||
var result = component.CreateNestedContentKeys(json, false, guidFactory);
|
||||
|
||||
Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Nested_In_Complex_Editor_Escaped()
|
||||
{
|
||||
var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };
|
||||
var guidCounter = 0;
|
||||
Func<Guid> guidFactory = () => guids[guidCounter++];
|
||||
|
||||
// we need to ensure the escaped json is consistent with how it will be re-escaped after parsing
|
||||
// and this is how to do that, the result will also include quotes around it.
|
||||
var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{
|
||||
""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"",
|
||||
""name"": ""Item 1"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""woot""
|
||||
}, {
|
||||
""key"": ""fbde4288-8382-4e13-8933-ed9c160de050"",
|
||||
""name"": ""Item 2"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""zoot""
|
||||
}
|
||||
]").ToString());
|
||||
|
||||
// Complex editor such as the grid
|
||||
var complexEditorJsonEscaped = @"{
|
||||
""name"": ""1 column layout"",
|
||||
""sections"": [
|
||||
{
|
||||
""grid"": ""12"",
|
||||
""rows"": [
|
||||
{
|
||||
""name"": ""Article"",
|
||||
""id"": ""b4f6f651-0de3-ef46-e66a-464f4aaa9c57"",
|
||||
""areas"": [
|
||||
{
|
||||
""grid"": ""4"",
|
||||
""controls"": [
|
||||
{
|
||||
""value"": ""I am quote"",
|
||||
""editor"": {
|
||||
""alias"": ""quote"",
|
||||
""view"": ""textstring""
|
||||
},
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
}],
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
},
|
||||
{
|
||||
""grid"": ""8"",
|
||||
""controls"": [
|
||||
{
|
||||
""value"": ""Header"",
|
||||
""editor"": {
|
||||
""alias"": ""headline"",
|
||||
""view"": ""textstring""
|
||||
},
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
},
|
||||
{
|
||||
""value"": " + subJsonEscaped + @",
|
||||
""editor"": {
|
||||
""alias"": ""madeUpNestedContent"",
|
||||
""view"": ""madeUpNestedContentInGrid""
|
||||
},
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
}],
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
}],
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
}]
|
||||
}]
|
||||
}";
|
||||
|
||||
|
||||
var json = @"[{
|
||||
""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"",
|
||||
""name"": ""Item 1"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""woot""
|
||||
}, {
|
||||
""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"",
|
||||
""name"": ""Item 2"",
|
||||
""ncContentTypeAlias"": ""list"",
|
||||
""text"": ""zoot"",
|
||||
""subItems"":" + complexEditorJsonEscaped + @"
|
||||
}
|
||||
]";
|
||||
|
||||
var expected = json
|
||||
.Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString())
|
||||
.Replace("d8e214d8-c5a5-4b45-9b51-4050dd47f5fa", guids[1].ToString())
|
||||
.Replace("dccf550c-3a05-469e-95e1-a8f560f788c2", guids[2].ToString())
|
||||
.Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString());
|
||||
|
||||
var component = new NestedContentPropertyComponent();
|
||||
var result = component.CreateNestedContentKeys(json, false, guidFactory);
|
||||
|
||||
Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString());
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void No_Nesting_Generates_Keys_For_Missing_Items()
|
||||
{
|
||||
var guids = new[] { Guid.NewGuid() };
|
||||
var guidCounter = 0;
|
||||
Func<Guid> guidFactory = () => guids[guidCounter++];
|
||||
|
||||
var json = @"[
|
||||
{""key"":""04a6dba8-813c-4144-8aca-86a3f24ebf08"",""name"":""Item 1 my key wont change"",""ncContentTypeAlias"":""nested"",""text"":""woot""},
|
||||
{""name"":""Item 2 was copied and has no key prop"",""ncContentTypeAlias"":""nested"",""text"":""zoot""}
|
||||
]";
|
||||
|
||||
var component = new NestedContentPropertyComponent();
|
||||
var result = component.CreateNestedContentKeys(json, true, guidFactory);
|
||||
|
||||
// Ensure the new GUID is put in a key into the JSON
|
||||
Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[0].ToString()));
|
||||
|
||||
// Ensure that the original key is NOT changed/modified & still exists
|
||||
Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains("04a6dba8-813c-4144-8aca-86a3f24ebf08"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void One_Level_Nesting_Escaped_Generates_Keys_For_Missing_Items()
|
||||
{
|
||||
var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };
|
||||
var guidCounter = 0;
|
||||
Func<Guid> guidFactory = () => guids[guidCounter++];
|
||||
|
||||
// we need to ensure the escaped json is consistent with how it will be re-escaped after parsing
|
||||
// and this is how to do that, the result will also include quotes around it.
|
||||
var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{
|
||||
""name"": ""Item 1"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""woot""
|
||||
}, {
|
||||
""name"": ""Nested Item 2 was copied and has no key"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""zoot""
|
||||
}
|
||||
]").ToString());
|
||||
|
||||
var json = @"[{
|
||||
""name"": ""Item 1 was copied and has no key"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""woot""
|
||||
}, {
|
||||
""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"",
|
||||
""name"": ""Item 2"",
|
||||
""ncContentTypeAlias"": ""list"",
|
||||
""text"": ""zoot"",
|
||||
""subItems"":" + subJsonEscaped + @"
|
||||
}
|
||||
]";
|
||||
|
||||
var component = new NestedContentPropertyComponent();
|
||||
var result = component.CreateNestedContentKeys(json, true, guidFactory);
|
||||
|
||||
// Ensure the new GUID is put in a key into the JSON for each item
|
||||
Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[0].ToString()));
|
||||
Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[1].ToString()));
|
||||
Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[2].ToString()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Nested_In_Complex_Editor_Escaped_Generates_Keys_For_Missing_Items()
|
||||
{
|
||||
var guids = new[] { Guid.NewGuid(), Guid.NewGuid() };
|
||||
var guidCounter = 0;
|
||||
Func<Guid> guidFactory = () => guids[guidCounter++];
|
||||
|
||||
// we need to ensure the escaped json is consistent with how it will be re-escaped after parsing
|
||||
// and this is how to do that, the result will also include quotes around it.
|
||||
var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{
|
||||
""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"",
|
||||
""name"": ""Item 1"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""woot""
|
||||
}, {
|
||||
""name"": ""Nested Item 2 was copied and has no key"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""zoot""
|
||||
}
|
||||
]").ToString());
|
||||
|
||||
// Complex editor such as the grid
|
||||
var complexEditorJsonEscaped = @"{
|
||||
""name"": ""1 column layout"",
|
||||
""sections"": [
|
||||
{
|
||||
""grid"": ""12"",
|
||||
""rows"": [
|
||||
{
|
||||
""name"": ""Article"",
|
||||
""id"": ""b4f6f651-0de3-ef46-e66a-464f4aaa9c57"",
|
||||
""areas"": [
|
||||
{
|
||||
""grid"": ""4"",
|
||||
""controls"": [
|
||||
{
|
||||
""value"": ""I am quote"",
|
||||
""editor"": {
|
||||
""alias"": ""quote"",
|
||||
""view"": ""textstring""
|
||||
},
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
}],
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
},
|
||||
{
|
||||
""grid"": ""8"",
|
||||
""controls"": [
|
||||
{
|
||||
""value"": ""Header"",
|
||||
""editor"": {
|
||||
""alias"": ""headline"",
|
||||
""view"": ""textstring""
|
||||
},
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
},
|
||||
{
|
||||
""value"": " + subJsonEscaped + @",
|
||||
""editor"": {
|
||||
""alias"": ""madeUpNestedContent"",
|
||||
""view"": ""madeUpNestedContentInGrid""
|
||||
},
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
}],
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
}],
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
}]
|
||||
}]
|
||||
}";
|
||||
|
||||
|
||||
var json = @"[{
|
||||
""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"",
|
||||
""name"": ""Item 1"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""woot""
|
||||
}, {
|
||||
""name"": ""Item 2 was copied and has no key"",
|
||||
""ncContentTypeAlias"": ""list"",
|
||||
""text"": ""zoot"",
|
||||
""subItems"":" + complexEditorJsonEscaped + @"
|
||||
}
|
||||
]";
|
||||
|
||||
var component = new NestedContentPropertyComponent();
|
||||
var result = component.CreateNestedContentKeys(json, true, guidFactory);
|
||||
|
||||
// Ensure the new GUID is put in a key into the JSON for each item
|
||||
Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[0].ToString()));
|
||||
Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[1].ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ namespace Umbraco.Tests.Scoping
|
||||
[TestCase(false)]
|
||||
public void CreateMediaTest(bool complete)
|
||||
{
|
||||
var physMediaFileSystem = new PhysicalFileSystem(IOHelper, Mock.Of<ILogger>(), IOHelper.MapPath("media"), "ignore");
|
||||
var physMediaFileSystem = new PhysicalFileSystem(IOHelper, HostingEnvironment, Mock.Of<ILogger>(), IOHelper.MapPath("media"), "ignore");
|
||||
var mediaFileSystem = Current.MediaFileSystem;
|
||||
|
||||
Assert.IsFalse(physMediaFileSystem.FileExists("f1.txt"));
|
||||
@@ -88,7 +88,7 @@ namespace Umbraco.Tests.Scoping
|
||||
[Test]
|
||||
public void MultiThread()
|
||||
{
|
||||
var physMediaFileSystem = new PhysicalFileSystem(IOHelper, Mock.Of<ILogger>(),IOHelper.MapPath("media"), "ignore");
|
||||
var physMediaFileSystem = new PhysicalFileSystem(IOHelper, HostingEnvironment, Mock.Of<ILogger>(),IOHelper.MapPath("media"), "ignore");
|
||||
var mediaFileSystem = Current.MediaFileSystem;
|
||||
|
||||
var scopeProvider = ScopeProvider;
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using Microsoft.Owin;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Web.Composing;
|
||||
using Umbraco.Tests.Testing;
|
||||
using Umbraco.Tests.Testing.Objects.Accessors;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Tests.Common;
|
||||
|
||||
namespace Umbraco.Tests.Security
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(WithApplication = true)]
|
||||
public class BackOfficeCookieManagerTests : UmbracoTestBase
|
||||
{
|
||||
[Test]
|
||||
public void ShouldAuthenticateRequest_When_Not_Configured()
|
||||
{
|
||||
//should force app ctx to show not-configured
|
||||
ConfigurationManager.AppSettings.Set(Constants.AppSettings.ConfigurationStatus, "");
|
||||
|
||||
var httpContextAccessor = TestHelper.GetHttpContextAccessor();
|
||||
var globalSettings = TestObjects.GetGlobalSettings();
|
||||
var umbracoContext = new UmbracoContext(
|
||||
httpContextAccessor,
|
||||
Mock.Of<IPublishedSnapshotService>(),
|
||||
Mock.Of<IWebSecurity>(),
|
||||
globalSettings,
|
||||
HostingEnvironment,
|
||||
new TestVariationContextAccessor(),
|
||||
UriUtility,
|
||||
new AspNetCookieManager(httpContextAccessor));
|
||||
|
||||
var runtime = Mock.Of<IRuntimeState>(x => x.Level == RuntimeLevel.Install);
|
||||
var mgr = new BackOfficeCookieManager(
|
||||
Mock.Of<IUmbracoContextAccessor>(accessor => accessor.UmbracoContext == umbracoContext), runtime, HostingEnvironment, globalSettings, AppCaches.RequestCache);
|
||||
|
||||
var result = mgr.ShouldAuthenticateRequest(Mock.Of<IOwinContext>(), new Uri("http://localhost/umbraco"));
|
||||
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShouldAuthenticateRequest_When_Configured()
|
||||
{
|
||||
var httpContextAccessor = TestHelper.GetHttpContextAccessor();
|
||||
var globalSettings = TestObjects.GetGlobalSettings();
|
||||
var umbCtx = new UmbracoContext(
|
||||
httpContextAccessor,
|
||||
Mock.Of<IPublishedSnapshotService>(),
|
||||
Mock.Of<IWebSecurity>(),
|
||||
globalSettings,
|
||||
HostingEnvironment,
|
||||
new TestVariationContextAccessor(),
|
||||
UriUtility,
|
||||
new AspNetCookieManager(httpContextAccessor));
|
||||
|
||||
var runtime = Mock.Of<IRuntimeState>(x => x.Level == RuntimeLevel.Run);
|
||||
var mgr = new BackOfficeCookieManager(Mock.Of<IUmbracoContextAccessor>(accessor => accessor.UmbracoContext == umbCtx), runtime, HostingEnvironment, globalSettings, AppCaches.RequestCache);
|
||||
|
||||
var request = new Mock<OwinRequest>();
|
||||
request.Setup(owinRequest => owinRequest.Uri).Returns(new Uri("http://localhost/umbraco"));
|
||||
|
||||
var result = mgr.ShouldAuthenticateRequest(
|
||||
Mock.Of<IOwinContext>(context => context.Request == request.Object),
|
||||
new Uri("http://localhost/umbraco"));
|
||||
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
// TODO: Write remaining tests for `ShouldAuthenticateRequest`
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Net;
|
||||
using Umbraco.Core.Templates;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Tests.Common;
|
||||
|
||||
@@ -118,17 +118,11 @@
|
||||
<Compile Include="Cache\DistributedCacheBinderTests.cs" />
|
||||
<Compile Include="Cache\RefresherTests.cs" />
|
||||
<Compile Include="Cache\SnapDictionaryTests.cs" />
|
||||
<Compile Include="Clr\ReflectionUtilitiesTests.cs" />
|
||||
<Compile Include="Collections\OrderedHashSetTests.cs" />
|
||||
<Compile Include="Composing\CompositionTests.cs" />
|
||||
<Compile Include="Composing\ContainerConformingTests.cs" />
|
||||
<Compile Include="Configurations\GlobalSettingsTests.cs" />
|
||||
<Compile Include="CoreThings\CallContextTests.cs" />
|
||||
<Compile Include="Components\ComponentTests.cs" />
|
||||
<Compile Include="CoreThings\ClaimsIdentityExtensionsTests.cs" />
|
||||
<Compile Include="CoreThings\EnumExtensionsTests.cs" />
|
||||
<Compile Include="CoreThings\GuidUtilsTests.cs" />
|
||||
<Compile Include="CoreThings\HexEncoderTests.cs" />
|
||||
<Compile Include="CoreXml\RenamedRootNavigatorTests.cs" />
|
||||
<Compile Include="LegacyXmlPublishedCache\ContentXmlDto.cs" />
|
||||
<Compile Include="LegacyXmlPublishedCache\PreviewXmlDto.cs" />
|
||||
@@ -162,6 +156,7 @@
|
||||
<Compile Include="TestHelpers\Entities\MockedUserGroup.cs" />
|
||||
<Compile Include="UmbracoExamine\ExamineExtensions.cs" />
|
||||
<Compile Include="PropertyEditors\DataValueReferenceFactoryCollectionTests.cs" />
|
||||
<Compile Include="PropertyEditors\NestedContentPropertyComponentTests.cs" />
|
||||
<Compile Include="PublishedContent\NuCacheChildrenTests.cs" />
|
||||
<Compile Include="PublishedContent\PublishedContentLanguageVariantTests.cs" />
|
||||
<Compile Include="PublishedContent\PublishedContentSnapshotTestBase.cs" />
|
||||
@@ -249,17 +244,14 @@
|
||||
<Compile Include="Web\AngularIntegration\ContentModelSerializationTests.cs" />
|
||||
<Compile Include="Web\AngularIntegration\JsInitializationTests.cs" />
|
||||
<Compile Include="Web\AngularIntegration\ServerVariablesParserTests.cs" />
|
||||
<Compile Include="CoreThings\AttemptTests.cs" />
|
||||
<Compile Include="Migrations\Stubs\DropForeignKeyMigrationStub.cs" />
|
||||
<Compile Include="Models\Mapping\ContentTypeModelMappingTests.cs" />
|
||||
<Compile Include="Cache\DeepCloneAppCacheTests.cs" />
|
||||
<Compile Include="Cache\DefaultCachePolicyTests.cs" />
|
||||
<Compile Include="Cache\FullDataSetCachePolicyTests.cs" />
|
||||
<Compile Include="Cache\SingleItemsOnlyCachePolicyTests.cs" />
|
||||
<Compile Include="Collections\DeepCloneableListTests.cs" />
|
||||
<Compile Include="Web\Controllers\AuthenticationControllerTests.cs" />
|
||||
<Compile Include="Web\Controllers\BackOfficeControllerUnitTests.cs" />
|
||||
<Compile Include="CoreThings\DelegateExtensionsTests.cs" />
|
||||
<Compile Include="Web\Controllers\ContentControllerTests.cs" />
|
||||
<Compile Include="Web\Controllers\UserEditorAuthorizationHelperTests.cs" />
|
||||
<Compile Include="Web\Controllers\UsersControllerTests.cs" />
|
||||
@@ -276,7 +268,6 @@
|
||||
<Compile Include="Composing\ComposingTestBase.cs" />
|
||||
<Compile Include="Routing\RoutesCacheTests.cs" />
|
||||
<Compile Include="Routing\UrlRoutingTestBase.cs" />
|
||||
<Compile Include="Security\BackOfficeCookieManagerTests.cs" />
|
||||
<Compile Include="Services\ContentTypeServiceExtensionsTests.cs" />
|
||||
<Compile Include="Services\PublicAccessServiceTests.cs" />
|
||||
<Compile Include="StringNewlineExtensions.cs" />
|
||||
@@ -308,7 +299,6 @@
|
||||
<Compile Include="Web\Controllers\FilterAllowedOutgoingContentAttributeTests.cs" />
|
||||
<Compile Include="Web\Controllers\MediaControllerUnitTests.cs" />
|
||||
<Compile Include="CoreXml\FrameworkXmlTests.cs" />
|
||||
<Compile Include="Clr\ReflectionTests.cs" />
|
||||
<Compile Include="Macros\MacroParserTests.cs" />
|
||||
<Compile Include="Models\ContentExtensionsTests.cs" />
|
||||
<Compile Include="Web\Mvc\MergeParentContextViewDataAttributeTests.cs" />
|
||||
@@ -457,7 +447,6 @@
|
||||
<Compile Include="Composing\TypeLoaderTests.cs" />
|
||||
<Compile Include="TestHelpers\Stubs\TestLastChanceFinder.cs" />
|
||||
<Compile Include="TestHelpers\TestHelper.cs" />
|
||||
<Compile Include="CoreThings\EnumerableExtensionsTests.cs" />
|
||||
<Compile Include="IO\AbstractFileSystemTests.cs" />
|
||||
<Compile Include="IO\FileSystemsTests.cs" />
|
||||
<Compile Include="IO\PhysicalFileSystemTests.cs" />
|
||||
@@ -465,10 +454,8 @@
|
||||
<Compile Include="Strings\StringExtensionsTests.cs" />
|
||||
<Compile Include="TestHelpers\FakeHttpContextFactory.cs" />
|
||||
<Compile Include="Routing\UmbracoModuleTests.cs" />
|
||||
<Compile Include="CoreThings\VersionExtensionTests.cs" />
|
||||
<Compile Include="Web\UrlHelperExtensionTests.cs" />
|
||||
<Compile Include="Web\WebExtensionMethodTests.cs" />
|
||||
<Compile Include="CoreThings\XmlExtensionsTests.cs" />
|
||||
<Compile Include="LegacyXmlPublishedCache\DictionaryPublishedContent.cs" />
|
||||
<Compile Include="LegacyXmlPublishedCache\DomainCache.cs" />
|
||||
<Compile Include="LegacyXmlPublishedCache\PreviewContent.cs" />
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.BackOffice;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Web.BackOffice.Filters;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
using Umbraco.Web.Common.Controllers;
|
||||
@@ -33,6 +37,8 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
private readonly IUserService _userService;
|
||||
private readonly UmbracoMapper _umbracoMapper;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IIpResolver _ipResolver;
|
||||
|
||||
// TODO: We need to import the logic from Umbraco.Web.Editors.AuthenticationController
|
||||
// TODO: We need to review all _userManager.Raise calls since many/most should be on the usermanager or signinmanager, very few should be here
|
||||
@@ -43,7 +49,8 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
BackOfficeSignInManager signInManager,
|
||||
IUserService userService,
|
||||
UmbracoMapper umbracoMapper,
|
||||
IGlobalSettings globalSettings)
|
||||
IGlobalSettings globalSettings,
|
||||
ILogger logger, IIpResolver ipResolver)
|
||||
{
|
||||
_webSecurity = webSecurity;
|
||||
_userManager = backOfficeUserManager;
|
||||
@@ -51,6 +58,27 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
_userService = userService;
|
||||
_umbracoMapper = umbracoMapper;
|
||||
_globalSettings = globalSettings;
|
||||
_logger = logger;
|
||||
_ipResolver = ipResolver;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public double GetRemainingTimeoutSeconds()
|
||||
{
|
||||
var backOfficeIdentity = HttpContext.User.GetUmbracoIdentity();
|
||||
var remainingSeconds = HttpContext.User.GetRemainingAuthSeconds();
|
||||
if (remainingSeconds <= 30 && backOfficeIdentity != null)
|
||||
{
|
||||
//NOTE: We are using 30 seconds because that is what is coded into angular to force logout to give some headway in
|
||||
// the timeout process.
|
||||
|
||||
_logger.Info<AuthenticationController>(
|
||||
"User logged will be logged out due to timeout: {Username}, IP Address: {IPAddress}",
|
||||
backOfficeIdentity.Name,
|
||||
_ipResolver.GetCurrentRequestIpAddress());
|
||||
}
|
||||
|
||||
return remainingSeconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -78,7 +106,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// cookies which means that the auth cookie could be valid but the csrf cookies are no longer there, in that case we need to re-set the csrf cookies.
|
||||
/// </remarks>
|
||||
[UmbracoAuthorize]
|
||||
[TypeFilter(typeof(SetAngularAntiForgeryTokens))]
|
||||
[SetAngularAntiForgeryTokens]
|
||||
//[CheckIfUserTicketDataIsStale] // TODO: Migrate this, though it will need to be done differently at the cookie auth level
|
||||
public UserDetail GetCurrentUser()
|
||||
{
|
||||
@@ -95,7 +123,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// Logs a user in
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[TypeFilter(typeof(SetAngularAntiForgeryTokens))]
|
||||
[SetAngularAntiForgeryTokens]
|
||||
public async Task<UserDetail> PostLogin(LoginModel loginModel)
|
||||
{
|
||||
// Sign the user in with username/password, this also gives a chance for developers to
|
||||
@@ -156,6 +184,22 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
throw new HttpResponseException(HttpStatusCode.BadRequest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs the current user out
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[ValidateAngularAntiForgeryToken]
|
||||
public IActionResult PostLogout()
|
||||
{
|
||||
HttpContext.SignOutAsync(Core.Constants.Security.BackOfficeAuthenticationType);
|
||||
|
||||
_logger.Info<AuthenticationController>("User {UserName} from IP address {RemoteIpAddress} has logged out", User.Identity == null ? "UNKNOWN" : User.Identity.Name, HttpContext.Connection.RemoteIpAddress);
|
||||
|
||||
_userManager.RaiseLogoutSuccessEvent(User, int.Parse(User.Identity.GetUserId()));
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the <see cref="UserDetail"/> for the given <see cref="IUser"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.IO;
|
||||
@@ -11,7 +12,7 @@ using Umbraco.Web.Common.Attributes;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
public class BackOfficeAssetsController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly IFileSystem _jsLibFileSystem;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
@@ -8,9 +7,8 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// resulting message is INotificationModel in which case it will append any Event Messages
|
||||
/// currently in the request.
|
||||
/// </summary>
|
||||
[TypeFilter(typeof(AppendCurrentEventMessagesAttribute))]
|
||||
[AppendCurrentEventMessagesAttribute]
|
||||
public abstract class BackOfficeNotificationsController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
// TODO: Put some exception filters in our webapi to return 404 instead of 500 when we throw ArgumentNullException
|
||||
// ref: https://www.exceptionnotfound.net/the-asp-net-web-api-exception-handling-pipeline-a-guided-tour/
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
//[PrefixlessBodyModelValidator]
|
||||
[UmbracoApplicationAuthorizeAttribute(Constants.Applications.Settings)]
|
||||
public class CodeFileController : BackOfficeNotificationsController
|
||||
|
||||
@@ -25,7 +25,7 @@ using Umbraco.Web.WebApi.Filters;
|
||||
namespace Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
//we need to fire up the controller like this to enable loading of remote css directly from this controller
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
[ValidationFilter]
|
||||
[AngularJsonOnlyConfiguration] // TODO: This could be applied with our Application Model conventions
|
||||
[IsBackOffice]
|
||||
@@ -67,7 +67,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
private static readonly HttpClient HttpClient = new HttpClient();
|
||||
|
||||
//we have baseurl as a param to make previewing easier, so we can test with a dev domain from client side
|
||||
[TypeFilter(typeof(ValidateAngularAntiForgeryTokenAttribute))]
|
||||
[ValidateAngularAntiForgeryToken]
|
||||
public async Task<JObject> GetRemoteDashboardContent(string section, string baseUrl = "https://dashboard.umbraco.org/")
|
||||
{
|
||||
var user = _umbracoContextAccessor.GetRequiredUmbracoContext().Security.CurrentUser;
|
||||
@@ -211,7 +211,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
}
|
||||
|
||||
// return IDashboardSlim - we don't need sections nor access rules
|
||||
[TypeFilter(typeof(ValidateAngularAntiForgeryTokenAttribute))]
|
||||
[ValidateAngularAntiForgeryToken]
|
||||
[TypeFilter(typeof(OutgoingEditorModelEventAttribute))]
|
||||
public IEnumerable<Tab<IDashboardSlim>> GetDashboard(string section)
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// The security for this controller is defined to allow full CRUD access to data types if the user has access to either:
|
||||
/// Content Types, Member Types or Media Types ... and of course to Data Types
|
||||
/// </remarks>
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
[UmbracoTreeAuthorizeAttribute(Constants.Trees.DataTypes, Constants.Trees.DocumentTypes, Constants.Trees.MediaTypes, Constants.Trees.MemberTypes)]
|
||||
public class DataTypeController : BackOfficeNotificationsController
|
||||
{
|
||||
@@ -264,7 +264,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// </summary>
|
||||
/// <param name="dataType"></param>
|
||||
/// <returns></returns>
|
||||
[TypeFilter(typeof(DataTypeValidateAttribute))]
|
||||
[DataTypeValidate]
|
||||
public ActionResult<DataTypeDisplay> PostSave(DataTypeSave dataType)
|
||||
{
|
||||
//If we've made it here, then everything has been wired up and validated by the attribute
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// The security for this controller is defined to allow full CRUD access to dictionary if the user has access to either:
|
||||
/// Dictionary
|
||||
/// </remarks>
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
[UmbracoTreeAuthorize(Constants.Trees.Dictionary)]
|
||||
public class DictionaryController : BackOfficeNotificationsController
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ using SearchResult = Umbraco.Web.Models.ContentEditing.SearchResult;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
public class ExamineManagementController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly IExamineManager _examineManager;
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Media;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
@@ -22,7 +23,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// building to generate correct URLs
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
public class ImageUrlGeneratorController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly IImageUrlGenerator _imageUrlGenerator;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Media;
|
||||
@@ -13,7 +14,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// <summary>
|
||||
/// A controller used to return images for media
|
||||
/// </summary>
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
public class ImagesController : UmbracoAuthorizedApiController
|
||||
{
|
||||
private readonly IMediaFileSystem _mediaFileSystem;
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// <summary>
|
||||
/// Backoffice controller supporting the dashboard for language administration.
|
||||
/// </summary>
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
//[PrefixlessBodyModelValidator]
|
||||
public class LanguageController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// <summary>
|
||||
/// The API controller used for getting log history
|
||||
/// </summary>
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
public class LogController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly IMediaFileSystem _mediaFileSystem;
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// <summary>
|
||||
/// Backoffice controller supporting the dashboard for viewing logs with some simple graphs & filtering
|
||||
/// </summary>
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
public class LogViewerController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly ILogViewer _logViewer;
|
||||
|
||||
@@ -12,7 +12,7 @@ using Umbraco.Core;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Net;
|
||||
using Umbraco.Core.Templates;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
|
||||
@@ -237,7 +237,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// <returns>
|
||||
/// The <see cref="HttpResponseMessage"/>.
|
||||
/// </returns>
|
||||
public List<string> GetPartialViews()
|
||||
public IEnumerable<string> GetPartialViews()
|
||||
{
|
||||
var views = new List<string>();
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// <summary>
|
||||
/// A controller used for managing packages in the back office
|
||||
/// </summary>
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
[UmbracoApplicationAuthorizeAttribute(Constants.Applications.Packages)]
|
||||
public class PackageController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// <summary>
|
||||
/// A controller used for installing packages and managing all of the data in the packages section in the back office
|
||||
/// </summary>
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
[UmbracoApplicationAuthorizeAttribute(Constants.Applications.Packages)]
|
||||
public class PackageInstallController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@ using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
public class RedirectUrlManagementController : UmbracoAuthorizedApiController
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@@ -17,7 +17,7 @@ using Constants = Umbraco.Core.Constants;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
[UmbracoApplicationAuthorizeAttribute(Constants.Applications.Content)]
|
||||
public class RelationController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// <summary>
|
||||
/// The API controller for editing relation types.
|
||||
/// </summary>
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
[UmbracoTreeAuthorizeAttribute(Constants.Trees.RelationTypes)]
|
||||
public class RelationTypeController : BackOfficeNotificationsController
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
@@ -17,7 +18,7 @@ namespace Umbraco.Web.Editors
|
||||
/// <summary>
|
||||
/// The API controller used for using the list of sections
|
||||
/// </summary>
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
public class SectionController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly IControllerFactory _controllerFactory;
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// <summary>
|
||||
/// The API controller used for retrieving available stylesheets
|
||||
/// </summary>
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
public class StylesheetController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly IFileService _fileService;
|
||||
|
||||
@@ -16,7 +16,7 @@ using Constants = Umbraco.Core.Constants;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
[UmbracoTreeAuthorizeAttribute(Constants.Trees.Templates)]
|
||||
public class TemplateController : BackOfficeNotificationsController
|
||||
{
|
||||
|
||||
@@ -19,7 +19,7 @@ using Constants = Umbraco.Core.Constants;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
[UmbracoApplicationAuthorize(
|
||||
Constants.Applications.Content,
|
||||
Constants.Applications.Media,
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.Services;
|
||||
@@ -13,7 +14,7 @@ using Umbraco.Web.Tour;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
public class TourController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly TourFilterCollection _filters;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Web.BackOffice.Controllers;
|
||||
using Umbraco.Web.BackOffice.Filters;
|
||||
using Umbraco.Web.BackOffice.Filters;
|
||||
using Umbraco.Web.Common.Filters;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Controllers
|
||||
@@ -12,10 +10,9 @@ namespace Umbraco.Web.BackOffice.Controllers
|
||||
/// Inheriting from this controller means that ALL of your methods are JSON methods that are called by Angular,
|
||||
/// methods that are not called by Angular or don't contain a valid csrf header will NOT work.
|
||||
/// </remarks>
|
||||
[TypeFilter(typeof(ValidateAngularAntiForgeryTokenAttribute))]
|
||||
[ValidateAngularAntiForgeryToken]
|
||||
[AngularJsonOnlyConfiguration] // TODO: This could be applied with our Application Model conventions
|
||||
public abstract class UmbracoAuthorizedJsonController : UmbracoAuthorizedApiController
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ using Umbraco.Web.Security;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Controllers
|
||||
{
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
public class UpdateCheckController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly IUpgradeService _upgradeService;
|
||||
|
||||
@@ -23,9 +23,8 @@ namespace Umbraco.Extensions
|
||||
{
|
||||
services.AddAntiforgery();
|
||||
|
||||
//We need to have runtime compilation of views when using backoffice. We could consider having only this when a specific config is set.
|
||||
//But as far as I can see, there are still precompiled views, even when this is activated, so maybe it is okay.
|
||||
services.AddControllersWithViews().AddRazorRuntimeCompilation();
|
||||
// TODO: We had this check in v8 where we don't enable these unless we can run...
|
||||
//if (runtimeState.Level != RuntimeLevel.Upgrade && runtimeState.Level != RuntimeLevel.Run) return app;
|
||||
|
||||
services
|
||||
.AddAuthentication(Constants.Security.BackOfficeAuthenticationType)
|
||||
|
||||
@@ -12,62 +12,73 @@ namespace Umbraco.Web.WebApi.Filters
|
||||
/// resulting message is INotificationModel in which case it will append any Event Messages
|
||||
/// currently in the request.
|
||||
/// </summary>
|
||||
internal sealed class AppendCurrentEventMessagesAttribute : ActionFilterAttribute
|
||||
internal sealed class AppendCurrentEventMessagesAttribute : TypeFilterAttribute
|
||||
{
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly IEventMessagesFactory _eventMessagesFactory;
|
||||
|
||||
public AppendCurrentEventMessagesAttribute(IUmbracoContextAccessor umbracoContextAccessor, IEventMessagesFactory eventMessagesFactory)
|
||||
public AppendCurrentEventMessagesAttribute() : base(typeof(AppendCurrentEventMessagesFilter))
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_eventMessagesFactory = eventMessagesFactory;
|
||||
}
|
||||
|
||||
public override void OnActionExecuted(ActionExecutedContext context)
|
||||
private class AppendCurrentEventMessagesFilter : IActionFilter
|
||||
{
|
||||
if (context.HttpContext.Response == null) return;
|
||||
if (context.HttpContext.Request.Method.Equals(HttpMethod.Get.ToString(), StringComparison.InvariantCultureIgnoreCase)) return;
|
||||
var umbracoContext = _umbracoContextAccessor.UmbracoContext;
|
||||
if (umbracoContext == null) return;
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly IEventMessagesFactory _eventMessagesFactory;
|
||||
|
||||
if(!(context.Result is ObjectResult obj)) return;
|
||||
|
||||
var notifications = obj.Value as INotificationModel;
|
||||
if (notifications == null) return;
|
||||
|
||||
var msgs = _eventMessagesFactory.GetOrDefault();
|
||||
if (msgs == null) return;
|
||||
|
||||
foreach (var eventMessage in msgs.GetAll())
|
||||
public AppendCurrentEventMessagesFilter(IUmbracoContextAccessor umbracoContextAccessor, IEventMessagesFactory eventMessagesFactory)
|
||||
{
|
||||
NotificationStyle msgType;
|
||||
switch (eventMessage.MessageType)
|
||||
{
|
||||
case EventMessageType.Default:
|
||||
msgType = NotificationStyle.Save;
|
||||
break;
|
||||
case EventMessageType.Info:
|
||||
msgType = NotificationStyle.Info;
|
||||
break;
|
||||
case EventMessageType.Error:
|
||||
msgType = NotificationStyle.Error;
|
||||
break;
|
||||
case EventMessageType.Success:
|
||||
msgType = NotificationStyle.Success;
|
||||
break;
|
||||
case EventMessageType.Warning:
|
||||
msgType = NotificationStyle.Warning;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_eventMessagesFactory = eventMessagesFactory;
|
||||
}
|
||||
|
||||
notifications.Notifications.Add(new BackOfficeNotification
|
||||
public void OnActionExecuted(ActionExecutedContext context)
|
||||
{
|
||||
if (context.HttpContext.Response == null) return;
|
||||
if (context.HttpContext.Request.Method.Equals(HttpMethod.Get.ToString(), StringComparison.InvariantCultureIgnoreCase)) return;
|
||||
var umbracoContext = _umbracoContextAccessor.UmbracoContext;
|
||||
if (umbracoContext == null) return;
|
||||
|
||||
if (!(context.Result is ObjectResult obj)) return;
|
||||
|
||||
var notifications = obj.Value as INotificationModel;
|
||||
if (notifications == null) return;
|
||||
|
||||
var msgs = _eventMessagesFactory.GetOrDefault();
|
||||
if (msgs == null) return;
|
||||
|
||||
foreach (var eventMessage in msgs.GetAll())
|
||||
{
|
||||
Message = eventMessage.Message,
|
||||
Header = eventMessage.Category,
|
||||
NotificationType = msgType
|
||||
});
|
||||
NotificationStyle msgType;
|
||||
switch (eventMessage.MessageType)
|
||||
{
|
||||
case EventMessageType.Default:
|
||||
msgType = NotificationStyle.Save;
|
||||
break;
|
||||
case EventMessageType.Info:
|
||||
msgType = NotificationStyle.Info;
|
||||
break;
|
||||
case EventMessageType.Error:
|
||||
msgType = NotificationStyle.Error;
|
||||
break;
|
||||
case EventMessageType.Success:
|
||||
msgType = NotificationStyle.Success;
|
||||
break;
|
||||
case EventMessageType.Warning:
|
||||
msgType = NotificationStyle.Warning;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
notifications.Notifications.Add(new BackOfficeNotification
|
||||
{
|
||||
Message = eventMessage.Message,
|
||||
Header = eventMessage.Category,
|
||||
NotificationType = msgType
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Mapping;
|
||||
@@ -15,94 +16,98 @@ using Umbraco.Web.Models.ContentEditing;
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
/// <summary>
|
||||
/// An action filter that wires up the persisted entity of the DataTypeSave model and validates the whole request
|
||||
/// An attribute/filter that wires up the persisted entity of the DataTypeSave model and validates the whole request
|
||||
/// </summary>
|
||||
internal sealed class DataTypeValidateAttribute : ActionFilterAttribute
|
||||
internal sealed class DataTypeValidateAttribute : TypeFilterAttribute
|
||||
{
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
private readonly PropertyEditorCollection _propertyEditorCollection;
|
||||
private readonly UmbracoMapper _umbracoMapper;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// For use in unit tests. Not possible to use as attribute ctor.
|
||||
/// </summary>
|
||||
/// <param name="dataTypeService"></param>
|
||||
/// <param name="propertyEditorCollection"></param>
|
||||
/// <param name="umbracoMapper"></param>
|
||||
public DataTypeValidateAttribute(IDataTypeService dataTypeService, PropertyEditorCollection propertyEditorCollection, UmbracoMapper umbracoMapper)
|
||||
public DataTypeValidateAttribute() : base(typeof(DataTypeValidateFilter))
|
||||
{
|
||||
_dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService));
|
||||
_propertyEditorCollection = propertyEditorCollection ?? throw new ArgumentNullException(nameof(propertyEditorCollection));
|
||||
_umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper));
|
||||
}
|
||||
|
||||
public override void OnActionExecuting(ActionExecutingContext context)
|
||||
private class DataTypeValidateFilter : IActionFilter
|
||||
{
|
||||
var dataType = (DataTypeSave) context.ActionArguments["dataType"];
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
private readonly PropertyEditorCollection _propertyEditorCollection;
|
||||
private readonly UmbracoMapper _umbracoMapper;
|
||||
|
||||
dataType.Name = dataType.Name.CleanForXss('[', ']', '(', ')', ':');
|
||||
dataType.Alias = dataType.Alias == null ? dataType.Name : dataType.Alias.CleanForXss('[', ']', '(', ')', ':');
|
||||
|
||||
// get the property editor, ensuring that it exits
|
||||
if (!_propertyEditorCollection.TryGet(dataType.EditorAlias, out var propertyEditor))
|
||||
public DataTypeValidateFilter(IDataTypeService dataTypeService, PropertyEditorCollection propertyEditorCollection, UmbracoMapper umbracoMapper)
|
||||
{
|
||||
var message = $"Property editor \"{dataType.EditorAlias}\" was not found.";
|
||||
context.Result = new UmbracoProblemResult(message, HttpStatusCode.NotFound);
|
||||
return;
|
||||
_dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService));
|
||||
_propertyEditorCollection = propertyEditorCollection ?? throw new ArgumentNullException(nameof(propertyEditorCollection));
|
||||
_umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper));
|
||||
}
|
||||
|
||||
// assign
|
||||
dataType.PropertyEditor = propertyEditor;
|
||||
|
||||
// validate that the data type exists, or create one if required
|
||||
IDataType persisted;
|
||||
switch (dataType.Action)
|
||||
public void OnActionExecuted(ActionExecutedContext context)
|
||||
{
|
||||
case ContentSaveAction.Save:
|
||||
persisted = _dataTypeService.GetDataType(Convert.ToInt32(dataType.Id));
|
||||
if (persisted == null)
|
||||
{
|
||||
var message = $"Data type with id {dataType.Id} was not found.";
|
||||
context.Result = new UmbracoProblemResult(message, HttpStatusCode.NotFound);
|
||||
return;
|
||||
}
|
||||
// map the model to the persisted instance
|
||||
_umbracoMapper.Map(dataType, persisted);
|
||||
break;
|
||||
}
|
||||
|
||||
case ContentSaveAction.SaveNew:
|
||||
// create the persisted model from mapping the saved model
|
||||
persisted = _umbracoMapper.Map<IDataType>(dataType);
|
||||
((DataType) persisted).ResetIdentity();
|
||||
break;
|
||||
public void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
var dataType = (DataTypeSave)context.ActionArguments["dataType"];
|
||||
|
||||
default:
|
||||
context.Result = new UmbracoProblemResult($"Data type action {dataType.Action} was not found.", HttpStatusCode.NotFound);
|
||||
dataType.Name = dataType.Name.CleanForXss('[', ']', '(', ')', ':');
|
||||
dataType.Alias = dataType.Alias == null ? dataType.Name : dataType.Alias.CleanForXss('[', ']', '(', ')', ':');
|
||||
|
||||
// get the property editor, ensuring that it exits
|
||||
if (!_propertyEditorCollection.TryGet(dataType.EditorAlias, out var propertyEditor))
|
||||
{
|
||||
var message = $"Property editor \"{dataType.EditorAlias}\" was not found.";
|
||||
context.Result = new UmbracoProblemResult(message, HttpStatusCode.NotFound);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// assign (so it's available in the action)
|
||||
dataType.PersistedDataType = persisted;
|
||||
// assign
|
||||
dataType.PropertyEditor = propertyEditor;
|
||||
|
||||
// validate the configuration
|
||||
// which is posted as a set of fields with key (string) and value (object)
|
||||
var configurationEditor = propertyEditor.GetConfigurationEditor();
|
||||
foreach (var field in dataType.ConfigurationFields)
|
||||
{
|
||||
var editorField = configurationEditor.Fields.SingleOrDefault(x => x.Key == field.Key);
|
||||
if (editorField == null) continue;
|
||||
// validate that the data type exists, or create one if required
|
||||
IDataType persisted;
|
||||
switch (dataType.Action)
|
||||
{
|
||||
case ContentSaveAction.Save:
|
||||
persisted = _dataTypeService.GetDataType(Convert.ToInt32(dataType.Id));
|
||||
if (persisted == null)
|
||||
{
|
||||
var message = $"Data type with id {dataType.Id} was not found.";
|
||||
context.Result = new UmbracoProblemResult(message, HttpStatusCode.NotFound);
|
||||
return;
|
||||
}
|
||||
// map the model to the persisted instance
|
||||
_umbracoMapper.Map(dataType, persisted);
|
||||
break;
|
||||
|
||||
// run each IValueValidator (with null valueType and dataTypeConfiguration: not relevant here)
|
||||
foreach (var validator in editorField.Validators)
|
||||
foreach (var result in validator.Validate(field.Value, null, null))
|
||||
context.ModelState.AddValidationError(result, "Properties", field.Key);
|
||||
}
|
||||
case ContentSaveAction.SaveNew:
|
||||
// create the persisted model from mapping the saved model
|
||||
persisted = _umbracoMapper.Map<IDataType>(dataType);
|
||||
((DataType)persisted).ResetIdentity();
|
||||
break;
|
||||
|
||||
if (context.ModelState.IsValid == false)
|
||||
{
|
||||
// if it is not valid, do not continue and return the model state
|
||||
throw HttpResponseException.CreateValidationErrorResponse(context.ModelState);
|
||||
default:
|
||||
context.Result = new UmbracoProblemResult($"Data type action {dataType.Action} was not found.", HttpStatusCode.NotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
// assign (so it's available in the action)
|
||||
dataType.PersistedDataType = persisted;
|
||||
|
||||
// validate the configuration
|
||||
// which is posted as a set of fields with key (string) and value (object)
|
||||
var configurationEditor = propertyEditor.GetConfigurationEditor();
|
||||
foreach (var field in dataType.ConfigurationFields)
|
||||
{
|
||||
var editorField = configurationEditor.Fields.SingleOrDefault(x => x.Key == field.Key);
|
||||
if (editorField == null) continue;
|
||||
|
||||
// run each IValueValidator (with null valueType and dataTypeConfiguration: not relevant here)
|
||||
foreach (var validator in editorField.Validators)
|
||||
foreach (var result in validator.Validate(field.Value, null, null))
|
||||
context.ModelState.AddValidationError(result, "Properties", field.Key);
|
||||
}
|
||||
|
||||
if (context.ModelState.IsValid == false)
|
||||
{
|
||||
// if it is not valid, do not continue and return the model state
|
||||
throw HttpResponseException.CreateValidationErrorResponse(context.ModelState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
using Microsoft.AspNetCore.Antiforgery;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Web.BackOffice.Security;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// A filter to set the csrf cookie token based on angular conventions
|
||||
/// </summary>
|
||||
public sealed class SetAngularAntiForgeryTokens : IAsyncActionFilter
|
||||
{
|
||||
private readonly IBackOfficeAntiforgery _antiforgery;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
|
||||
public SetAngularAntiForgeryTokens(IBackOfficeAntiforgery antiforgery, IGlobalSettings globalSettings)
|
||||
{
|
||||
_antiforgery = antiforgery;
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
|
||||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
if (context.HttpContext.Response != null)
|
||||
{
|
||||
//DO not set the token cookies if the request has failed!!
|
||||
if (context.HttpContext.Response.StatusCode == (int)HttpStatusCode.OK)
|
||||
{
|
||||
//don't need to set the cookie if they already exist and they are valid
|
||||
if (context.HttpContext.Request.Cookies.TryGetValue(Constants.Web.AngularCookieName, out var angularCookieVal)
|
||||
&& context.HttpContext.Request.Cookies.TryGetValue(Constants.Web.CsrfValidationCookieName, out var csrfCookieVal))
|
||||
{
|
||||
//if they are not valid for some strange reason - we need to continue setting valid ones
|
||||
var valResult = await _antiforgery.ValidateRequestAsync(context.HttpContext);
|
||||
if (valResult.Success)
|
||||
{
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string cookieToken, headerToken;
|
||||
_antiforgery.GetTokens(context.HttpContext, out cookieToken, out headerToken);
|
||||
|
||||
//We need to set 2 cookies: one is the cookie value that angular will use to set a header value on each request,
|
||||
// the 2nd is the validation value generated by the anti-forgery helper that we use to validate the header token against.
|
||||
|
||||
context.HttpContext.Response.Cookies.Append(
|
||||
Constants.Web.AngularCookieName, headerToken,
|
||||
new Microsoft.AspNetCore.Http.CookieOptions
|
||||
{
|
||||
Path = "/",
|
||||
//must be js readable
|
||||
HttpOnly = false,
|
||||
Secure = _globalSettings.UseHttps
|
||||
});
|
||||
|
||||
context.HttpContext.Response.Cookies.Append(
|
||||
Constants.Web.CsrfValidationCookieName, cookieToken,
|
||||
new Microsoft.AspNetCore.Http.CookieOptions
|
||||
{
|
||||
Path = "/",
|
||||
HttpOnly = true,
|
||||
Secure = _globalSettings.UseHttps
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await next();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Web.BackOffice.Security;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// An attribute/filter to set the csrf cookie token based on angular conventions
|
||||
/// </summary>
|
||||
public class SetAngularAntiForgeryTokensAttribute : TypeFilterAttribute
|
||||
{
|
||||
public SetAngularAntiForgeryTokensAttribute() : base(typeof(SetAngularAntiForgeryTokensFilter))
|
||||
{
|
||||
}
|
||||
|
||||
private class SetAngularAntiForgeryTokensFilter : IAsyncActionFilter
|
||||
{
|
||||
private readonly IBackOfficeAntiforgery _antiforgery;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
|
||||
public SetAngularAntiForgeryTokensFilter(IBackOfficeAntiforgery antiforgery, IGlobalSettings globalSettings)
|
||||
{
|
||||
_antiforgery = antiforgery;
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
|
||||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
if (context.HttpContext.Response != null)
|
||||
{
|
||||
//DO not set the token cookies if the request has failed!!
|
||||
if (context.HttpContext.Response.StatusCode == (int)HttpStatusCode.OK)
|
||||
{
|
||||
//don't need to set the cookie if they already exist and they are valid
|
||||
if (context.HttpContext.Request.Cookies.TryGetValue(Constants.Web.AngularCookieName, out var angularCookieVal)
|
||||
&& context.HttpContext.Request.Cookies.TryGetValue(Constants.Web.CsrfValidationCookieName, out var csrfCookieVal))
|
||||
{
|
||||
//if they are not valid for some strange reason - we need to continue setting valid ones
|
||||
var valResult = await _antiforgery.ValidateRequestAsync(context.HttpContext);
|
||||
if (valResult.Success)
|
||||
{
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string cookieToken, headerToken;
|
||||
_antiforgery.GetTokens(context.HttpContext, out cookieToken, out headerToken);
|
||||
|
||||
//We need to set 2 cookies: one is the cookie value that angular will use to set a header value on each request,
|
||||
// the 2nd is the validation value generated by the anti-forgery helper that we use to validate the header token against.
|
||||
|
||||
context.HttpContext.Response.Cookies.Append(
|
||||
Constants.Web.AngularCookieName, headerToken,
|
||||
new Microsoft.AspNetCore.Http.CookieOptions
|
||||
{
|
||||
Path = "/",
|
||||
//must be js readable
|
||||
HttpOnly = false,
|
||||
Secure = _globalSettings.UseHttps
|
||||
});
|
||||
|
||||
context.HttpContext.Response.Cookies.Append(
|
||||
Constants.Web.CsrfValidationCookieName, cookieToken,
|
||||
new Microsoft.AspNetCore.Http.CookieOptions
|
||||
{
|
||||
Path = "/",
|
||||
HttpOnly = true,
|
||||
Secure = _globalSettings.UseHttps
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await next();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
@@ -15,100 +14,105 @@ using Umbraco.Web.BackOffice.Security;
|
||||
namespace Umbraco.Web.BackOffice.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// A filter to check for the csrf token based on Angular's standard approach
|
||||
/// An attribute/filter to check for the csrf token based on Angular's standard approach
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Code derived from http://ericpanorel.net/2013/07/28/spa-authentication-and-csrf-mvc4-antiforgery-implementation/
|
||||
///
|
||||
/// If the authentication type is cookie based, then this filter will execute, otherwise it will be disabled
|
||||
/// </remarks>
|
||||
public sealed class ValidateAngularAntiForgeryTokenAttribute : ActionFilterAttribute
|
||||
public sealed class ValidateAngularAntiForgeryTokenAttribute : TypeFilterAttribute
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IBackOfficeAntiforgery _antiforgery;
|
||||
private readonly ICookieManager _cookieManager;
|
||||
|
||||
public ValidateAngularAntiForgeryTokenAttribute(ILogger logger, IBackOfficeAntiforgery antiforgery, ICookieManager cookieManager)
|
||||
public ValidateAngularAntiForgeryTokenAttribute() : base(typeof(ValidateAngularAntiForgeryTokenFilter))
|
||||
{
|
||||
_logger = logger;
|
||||
_antiforgery = antiforgery;
|
||||
_cookieManager = cookieManager;
|
||||
}
|
||||
|
||||
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
private class ValidateAngularAntiForgeryTokenFilter : IAsyncActionFilter
|
||||
{
|
||||
if (context.Controller is ControllerBase controller && controller.User.Identity is ClaimsIdentity userIdentity)
|
||||
{
|
||||
//if there is not CookiePath claim, then exit
|
||||
if (userIdentity.HasClaim(x => x.Type == ClaimTypes.CookiePath) == false)
|
||||
{
|
||||
await base.OnActionExecutionAsync(context, next);
|
||||
return;
|
||||
}
|
||||
}
|
||||
var cookieToken = _cookieManager.GetCookieValue(Constants.Web.CsrfValidationCookieName);
|
||||
var httpContext = context.HttpContext;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IBackOfficeAntiforgery _antiforgery;
|
||||
private readonly ICookieManager _cookieManager;
|
||||
|
||||
var validateResult = await ValidateHeaders(httpContext, cookieToken);
|
||||
if (validateResult.Item1 == false)
|
||||
public ValidateAngularAntiForgeryTokenFilter(ILogger logger, IBackOfficeAntiforgery antiforgery, ICookieManager cookieManager)
|
||||
{
|
||||
//TODO we should update this behavior, as HTTP2 do not have ReasonPhrase. Could as well be returned in body
|
||||
// https://github.com/aspnet/HttpAbstractions/issues/395
|
||||
var httpResponseFeature = httpContext.Features.Get<IHttpResponseFeature>();
|
||||
if (!(httpResponseFeature is null))
|
||||
_logger = logger;
|
||||
_antiforgery = antiforgery;
|
||||
_cookieManager = cookieManager;
|
||||
}
|
||||
|
||||
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
if (context.Controller is ControllerBase controller && controller.User.Identity is ClaimsIdentity userIdentity)
|
||||
{
|
||||
httpResponseFeature.ReasonPhrase = validateResult.Item2;
|
||||
//if there is not CookiePath claim, then exit
|
||||
if (userIdentity.HasClaim(x => x.Type == ClaimTypes.CookiePath) == false)
|
||||
{
|
||||
await next();
|
||||
}
|
||||
}
|
||||
var cookieToken = _cookieManager.GetCookieValue(Constants.Web.CsrfValidationCookieName);
|
||||
var httpContext = context.HttpContext;
|
||||
|
||||
var validateResult = await ValidateHeaders(httpContext, cookieToken);
|
||||
if (validateResult.Item1 == false)
|
||||
{
|
||||
//TODO we should update this behavior, as HTTP2 do not have ReasonPhrase. Could as well be returned in body
|
||||
// https://github.com/aspnet/HttpAbstractions/issues/395
|
||||
var httpResponseFeature = httpContext.Features.Get<IHttpResponseFeature>();
|
||||
if (!(httpResponseFeature is null))
|
||||
{
|
||||
httpResponseFeature.ReasonPhrase = validateResult.Item2;
|
||||
}
|
||||
|
||||
context.Result = new StatusCodeResult((int)HttpStatusCode.ExpectationFailed);
|
||||
}
|
||||
|
||||
context.Result = new StatusCodeResult((int)HttpStatusCode.ExpectationFailed);
|
||||
return;
|
||||
await next();
|
||||
}
|
||||
|
||||
await next();
|
||||
}
|
||||
|
||||
private async Task<(bool,string)> ValidateHeaders(
|
||||
HttpContext httpContext,
|
||||
string cookieToken)
|
||||
{
|
||||
var requestHeaders = httpContext.Request.Headers;
|
||||
if (requestHeaders.Any(z => z.Key.InvariantEquals(Constants.Web.AngularHeadername)) == false)
|
||||
private async Task<(bool, string)> ValidateHeaders(
|
||||
HttpContext httpContext,
|
||||
string cookieToken)
|
||||
{
|
||||
return (false, "Missing token");
|
||||
var requestHeaders = httpContext.Request.Headers;
|
||||
if (requestHeaders.Any(z => z.Key.InvariantEquals(Constants.Web.AngularHeadername)) == false)
|
||||
{
|
||||
return (false, "Missing token");
|
||||
}
|
||||
|
||||
var headerToken = requestHeaders
|
||||
.Where(z => z.Key.InvariantEquals(Constants.Web.AngularHeadername))
|
||||
.Select(z => z.Value)
|
||||
.SelectMany(z => z)
|
||||
.FirstOrDefault();
|
||||
|
||||
// both header and cookie must be there
|
||||
if (cookieToken == null || headerToken == null)
|
||||
{
|
||||
return (false, "Missing token null");
|
||||
}
|
||||
|
||||
if (await ValidateTokens(httpContext) == false)
|
||||
{
|
||||
return (false, "Invalid token");
|
||||
}
|
||||
|
||||
return (true, "Success");
|
||||
}
|
||||
|
||||
var headerToken = requestHeaders
|
||||
.Where(z => z.Key.InvariantEquals(Constants.Web.AngularHeadername))
|
||||
.Select(z => z.Value)
|
||||
.SelectMany(z => z)
|
||||
.FirstOrDefault();
|
||||
|
||||
// both header and cookie must be there
|
||||
if (cookieToken == null || headerToken == null)
|
||||
private async Task<bool> ValidateTokens(HttpContext httpContext)
|
||||
{
|
||||
return (false, "Missing token null");
|
||||
}
|
||||
|
||||
if (await ValidateTokens(httpContext) == false)
|
||||
{
|
||||
return (false, "Invalid token");
|
||||
}
|
||||
|
||||
return (true, "Success");
|
||||
}
|
||||
|
||||
private async Task<bool> ValidateTokens(HttpContext httpContext)
|
||||
{
|
||||
// ensure that the cookie matches the header and then ensure it matches the correct value!
|
||||
try
|
||||
{
|
||||
await _antiforgery.ValidateRequestAsync(httpContext);
|
||||
return true;
|
||||
}
|
||||
catch (AntiforgeryValidationException ex)
|
||||
{
|
||||
_logger.Error<ValidateAntiForgeryTokenAttribute>(ex, "Could not validate XSRF token");
|
||||
return false;
|
||||
// ensure that the cookie matches the header and then ensure it matches the correct value!
|
||||
try
|
||||
{
|
||||
await _antiforgery.ValidateRequestAsync(httpContext);
|
||||
return true;
|
||||
}
|
||||
catch (AntiforgeryValidationException ex)
|
||||
{
|
||||
_logger.Error<ValidateAntiForgeryTokenAttribute>(ex, "Could not validate XSRF token");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
using Umbraco.Web.BackOffice.Controllers;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.PropertyEditors
|
||||
{
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
public class NestedContentController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Umbraco.Web.BackOffice.PropertyEditors
|
||||
/// <summary>
|
||||
/// ApiController to provide RTE configuration with available plugins and commands from the RTE config
|
||||
/// </summary>
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
public class RichTextPreValueController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
|
||||
@@ -5,13 +5,14 @@ using Umbraco.Web.BackOffice.Controllers;
|
||||
using Umbraco.Core.Media;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
using Umbraco.Web.Media.EmbedProviders;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.PropertyEditors
|
||||
{
|
||||
/// <summary>
|
||||
/// A controller used for the embed dialog
|
||||
/// </summary>
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
public class RteEmbedController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
private readonly EmbedProvidersCollection _embedCollection;
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Umbraco.Web.BackOffice.PropertyEditors
|
||||
/// DO NOT inherit from UmbracoAuthorizedJsonController since we don't want to use the angularized
|
||||
/// json formatter as it causes problems.
|
||||
/// </remarks>
|
||||
[PluginController("UmbracoApi")]
|
||||
[PluginController(Constants.Web.Mvc.BackOfficeApiArea)]
|
||||
public class TagsDataController : UmbracoAuthorizedApiController
|
||||
{
|
||||
private readonly ITagQuery _tagQuery;
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Extensions;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Web.BackOffice.Controllers;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Security
|
||||
{
|
||||
@@ -20,7 +23,7 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
/// 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,
|
||||
/// instead we'll specify our own cookie manager and return null if the request isn't for an acceptable path.
|
||||
/// </remarks>
|
||||
internal class BackOfficeCookieManager : ChunkingCookieManager, ICookieManager
|
||||
public class BackOfficeCookieManager : ChunkingCookieManager, ICookieManager
|
||||
{
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly IRuntimeState _runtime;
|
||||
@@ -28,13 +31,25 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IRequestCache _requestCache;
|
||||
private readonly string[] _explicitPaths;
|
||||
private readonly string _getRemainingSecondsPath;
|
||||
|
||||
public BackOfficeCookieManager(IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, IHostingEnvironment hostingEnvironment, IGlobalSettings globalSettings, IRequestCache requestCache)
|
||||
: this(umbracoContextAccessor, runtime, hostingEnvironment, globalSettings, requestCache, null)
|
||||
public BackOfficeCookieManager(
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IRuntimeState runtime,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IGlobalSettings globalSettings,
|
||||
IRequestCache requestCache,
|
||||
LinkGenerator linkGenerator)
|
||||
: this(umbracoContextAccessor, runtime, hostingEnvironment, globalSettings, requestCache, linkGenerator, null)
|
||||
{ }
|
||||
|
||||
public BackOfficeCookieManager(IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, IHostingEnvironment hostingEnvironment, IGlobalSettings globalSettings, IRequestCache requestCache, IEnumerable<string> explicitPaths)
|
||||
public BackOfficeCookieManager(
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IRuntimeState runtime,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IGlobalSettings globalSettings,
|
||||
IRequestCache requestCache,
|
||||
LinkGenerator linkGenerator,
|
||||
IEnumerable<string> explicitPaths)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_runtime = runtime;
|
||||
@@ -42,9 +57,6 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
_globalSettings = globalSettings;
|
||||
_requestCache = requestCache;
|
||||
_explicitPaths = explicitPaths?.ToArray();
|
||||
var backOfficePath = _globalSettings.GetBackOfficePath(_hostingEnvironment);
|
||||
// TODO: We shouldn't hard code this path
|
||||
_getRemainingSecondsPath = $"{backOfficePath}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -60,7 +72,7 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
/// * it is a /base request
|
||||
/// * it is a preview request
|
||||
/// </remarks>
|
||||
internal bool ShouldAuthenticateRequest(Uri requestUri, bool checkForceAuthTokens = true)
|
||||
public bool ShouldAuthenticateRequest(Uri requestUri, bool checkForceAuthTokens = true)
|
||||
{
|
||||
// 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
|
||||
@@ -75,9 +87,6 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
if (_explicitPaths != null)
|
||||
return _explicitPaths.Any(x => x.InvariantEquals(requestUri.AbsolutePath));
|
||||
|
||||
//check user seconds path
|
||||
if (requestUri.AbsolutePath.InvariantEquals(_getRemainingSecondsPath)) return false;
|
||||
|
||||
if (//check the explicit flag
|
||||
checkForceAuthTokens && _requestCache.IsAvailable && _requestCache.Get(Constants.Security.ForceReAuthFlag) != null
|
||||
//check back office
|
||||
|
||||
@@ -9,7 +9,6 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
/// <summary>
|
||||
/// Custom secure format that ensures the Identity in the ticket is <see cref="UmbracoBackOfficeIdentity"/> and not just a ClaimsIdentity
|
||||
/// </summary>
|
||||
// TODO: Unsure if we really need this, there's no real reason why we have a custom Identity instead of just a ClaimsIdentity
|
||||
internal class BackOfficeSecureDataFormat : ISecureDataFormat<AuthenticationTicket>
|
||||
{
|
||||
private readonly int _loginTimeoutMinutes;
|
||||
@@ -23,7 +22,7 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
|
||||
public string Protect(AuthenticationTicket data, string purpose)
|
||||
{
|
||||
//create a new ticket based on the passed in tickets details, however, we'll adjust the expires utc based on the specified timeout mins
|
||||
// create a new ticket based on the passed in tickets details, however, we'll adjust the expires utc based on the specified timeout mins
|
||||
var ticket = new AuthenticationTicket(data.Principal,
|
||||
new AuthenticationProperties(data.Properties.Items)
|
||||
{
|
||||
|
||||
@@ -18,6 +18,7 @@ using Umbraco.Core.Security;
|
||||
using Umbraco.Extensions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Web.Common.Security;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace Umbraco.Web.BackOffice.Security
|
||||
{
|
||||
@@ -35,7 +36,9 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
private readonly IRequestCache _requestCache;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IIpResolver _ipResolver;
|
||||
private readonly ISystemClock _systemClock;
|
||||
private readonly BackOfficeSessionIdValidator _sessionIdValidator;
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
|
||||
public ConfigureBackOfficeCookieOptions(
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
@@ -47,7 +50,9 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
IRequestCache requestCache,
|
||||
IUserService userService,
|
||||
IIpResolver ipResolver,
|
||||
BackOfficeSessionIdValidator sessionIdValidator)
|
||||
ISystemClock systemClock,
|
||||
BackOfficeSessionIdValidator sessionIdValidator,
|
||||
LinkGenerator linkGenerator)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_securitySettings = securitySettings;
|
||||
@@ -58,7 +63,9 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
_requestCache = requestCache;
|
||||
_userService = userService;
|
||||
_ipResolver = ipResolver;
|
||||
_systemClock = systemClock;
|
||||
_sessionIdValidator = sessionIdValidator;
|
||||
_linkGenerator = linkGenerator;
|
||||
}
|
||||
|
||||
public void Configure(string name, CookieAuthenticationOptions options)
|
||||
@@ -98,7 +105,8 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
_runtimeState,
|
||||
_hostingEnvironment,
|
||||
_globalSettings,
|
||||
_requestCache);
|
||||
_requestCache,
|
||||
_linkGenerator);
|
||||
// _explicitPaths); TODO: Implement this once we do OAuth somehow
|
||||
|
||||
|
||||
@@ -111,7 +119,7 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
// It would be possible to re-use the default behavior if any of these need to be set but that must be taken into account else
|
||||
// our back office requests will not function correctly. For now we don't need to set/configure any of these callbacks because
|
||||
// the defaults work fine with our setup.
|
||||
|
||||
|
||||
OnValidatePrincipal = async ctx =>
|
||||
{
|
||||
// We need to resolve the BackOfficeSecurityStampValidator per request as a requirement (even in aspnetcore they do this)
|
||||
@@ -131,6 +139,7 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
|
||||
await EnsureValidSessionId(ctx);
|
||||
await securityStampValidator.ValidateAsync(ctx);
|
||||
EnsureTicketRenewalIfKeepUserLoggedIn(ctx);
|
||||
|
||||
// add a claim to track when the cookie expires, we use this to track time remaining
|
||||
backOfficeIdentity.AddClaim(new Claim(
|
||||
@@ -140,7 +149,7 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
UmbracoBackOfficeIdentity.Issuer,
|
||||
UmbracoBackOfficeIdentity.Issuer,
|
||||
backOfficeIdentity));
|
||||
|
||||
|
||||
},
|
||||
OnSigningIn = ctx =>
|
||||
{
|
||||
@@ -175,7 +184,6 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
OnSigningOut = ctx =>
|
||||
{
|
||||
//Clear the user's session on sign out
|
||||
// TODO: We need to test this once we have signout functionality, not sure if the httpcontext.user.identity will still be set here
|
||||
if (ctx.HttpContext?.User?.Identity != null)
|
||||
{
|
||||
var claimsIdentity = ctx.HttpContext.User.Identity as ClaimsIdentity;
|
||||
@@ -192,7 +200,9 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
BackOfficeSessionIdValidator.CookieName,
|
||||
_securitySettings.AuthCookieName,
|
||||
Constants.Web.PreviewCookieName,
|
||||
Constants.Security.BackOfficeExternalCookieName
|
||||
Constants.Security.BackOfficeExternalCookieName,
|
||||
Constants.Web.AngularCookieName,
|
||||
Constants.Web.CsrfValidationCookieName,
|
||||
};
|
||||
foreach (var cookie in cookies)
|
||||
{
|
||||
@@ -218,5 +228,31 @@ namespace Umbraco.Web.BackOffice.Security
|
||||
if (_runtimeState.Level == RuntimeLevel.Run)
|
||||
await _sessionIdValidator.ValidateSessionAsync(TimeSpan.FromMinutes(1), context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the ticket is renewed if the <see cref="ISecuritySettings.KeepUserLoggedIn"/> is set to true
|
||||
/// and the current request is for the get user seconds endpoint
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
private void EnsureTicketRenewalIfKeepUserLoggedIn(CookieValidatePrincipalContext context)
|
||||
{
|
||||
if (!_securitySettings.KeepUserLoggedIn) return;
|
||||
|
||||
var currentUtc = _systemClock.UtcNow;
|
||||
var issuedUtc = context.Properties.IssuedUtc;
|
||||
var expiresUtc = context.Properties.ExpiresUtc;
|
||||
|
||||
if (expiresUtc.HasValue && issuedUtc.HasValue)
|
||||
{
|
||||
var timeElapsed = currentUtc.Subtract(issuedUtc.Value);
|
||||
var timeRemaining = expiresUtc.Value.Subtract(currentUtc);
|
||||
|
||||
//if it's time to renew, then do it
|
||||
if (timeRemaining < timeElapsed)
|
||||
{
|
||||
context.ShouldRenew = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
using Umbraco.Web.Common.Filters;
|
||||
using Umbraco.Web.Features;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
|
||||
namespace Umbraco.Web.Common.Controllers
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ using Umbraco.Web.Common.Install;
|
||||
using Umbraco.Core.Hosting;
|
||||
using System.Linq.Expressions;
|
||||
using Umbraco.Web.Common.Controllers;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
@@ -58,6 +59,23 @@ namespace Umbraco.Extensions
|
||||
return linkGenerator.GetUmbracoApiService(actionName, typeof(T), id);
|
||||
}
|
||||
|
||||
public static string GetUmbracoApiService<T>(this LinkGenerator url, Expression<Func<T, object>> methodSelector)
|
||||
where T : UmbracoApiControllerBase
|
||||
{
|
||||
var method = ExpressionHelper.GetMethodInfo(methodSelector);
|
||||
var methodParams = ExpressionHelper.GetMethodParams(methodSelector);
|
||||
if (method == null)
|
||||
{
|
||||
throw new MissingMethodException("Could not find the method " + methodSelector + " on type " + typeof(T) + " or the result ");
|
||||
}
|
||||
|
||||
if (methodParams.Any() == false)
|
||||
{
|
||||
return url.GetUmbracoApiService<T>(method.Name);
|
||||
}
|
||||
return url.GetUmbracoApiService<T>(method.Name, methodParams.Values.First());
|
||||
}
|
||||
|
||||
public static string GetUmbracoApiServiceBaseUrl<T>(this LinkGenerator linkGenerator, Expression<Func<T, object>> methodSelector)
|
||||
where T : UmbracoApiControllerBase
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
using System.Buffers;
|
||||
using System.Buffers;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -16,7 +15,7 @@ namespace Umbraco.Web.Common.Filters
|
||||
{
|
||||
}
|
||||
|
||||
private class AngularJsonOnlyConfigurationFilter : IResultFilter
|
||||
private class AngularJsonOnlyConfigurationFilter : IResultFilter
|
||||
{
|
||||
private readonly IOptions<MvcNewtonsoftJsonOptions> _mvcNewtonsoftJsonOptions;
|
||||
private readonly ArrayPool<char> _arrayPool;
|
||||
@@ -31,7 +30,6 @@ namespace Umbraco.Web.Common.Filters
|
||||
|
||||
public void OnResultExecuted(ResultExecutedContext context)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void OnResultExecuting(ResultExecutingContext context)
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
@@ -13,7 +12,6 @@ using Umbraco.Net;
|
||||
using Umbraco.Web.Common.Attributes;
|
||||
using Umbraco.Web.Common.Exceptions;
|
||||
using Umbraco.Web.Common.Filters;
|
||||
using Umbraco.Web.Common.ModelBinding;
|
||||
using Umbraco.Web.Common.Security;
|
||||
using Umbraco.Web.Install;
|
||||
using Umbraco.Web.Install.Models;
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace Umbraco.Web.Common.Routing
|
||||
object constraints = null)
|
||||
=> endpoints.MapUmbracoRoute(controllerType, rootSegment, areaName,
|
||||
isBackOffice
|
||||
? (areaName.IsNullOrWhiteSpace() ? "BackOffice/Api" : $"BackOffice/{areaName}")
|
||||
? (areaName.IsNullOrWhiteSpace() ? $"{Core.Constants.Web.Mvc.BackOfficePathSegment}/Api" : $"{Core.Constants.Web.Mvc.BackOfficePathSegment}/{areaName}")
|
||||
: (areaName.IsNullOrWhiteSpace() ? "Api" : areaName),
|
||||
defaultAction, true, constraints);
|
||||
}
|
||||
|
||||
@@ -1,766 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Security;
|
||||
namespace Umbraco.Web.Common.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the default <see cref="IPublishedRouter"/> implementation.
|
||||
/// </summary>
|
||||
public class PublishedRouter : IPublishedRouter
|
||||
{
|
||||
private readonly IWebRoutingSettings _webRoutingSettings;
|
||||
private readonly ContentFinderCollection _contentFinders;
|
||||
private readonly IContentLastChanceFinder _contentLastChanceFinder;
|
||||
private readonly IProfilingLogger _profilingLogger;
|
||||
private readonly IVariationContextAccessor _variationContextAccessor;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IPublishedUrlProvider _publishedUrlProvider;
|
||||
private readonly IRequestAccessor _requestAccessor;
|
||||
private readonly IPublishedValueFallback _publishedValueFallback;
|
||||
private readonly IPublicAccessChecker _publicAccessChecker;
|
||||
private readonly IFileService _fileService;
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
private readonly IPublicAccessService _publicAccessService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PublishedRouter"/> class.
|
||||
/// </summary>
|
||||
public PublishedRouter(
|
||||
IWebRoutingSettings webRoutingSettings,
|
||||
ContentFinderCollection contentFinders,
|
||||
IContentLastChanceFinder contentLastChanceFinder,
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
IProfilingLogger proflog,
|
||||
IPublishedUrlProvider publishedUrlProvider,
|
||||
IRequestAccessor requestAccessor,
|
||||
IPublishedValueFallback publishedValueFallback,
|
||||
IPublicAccessChecker publicAccessChecker,
|
||||
IFileService fileService,
|
||||
IContentTypeService contentTypeService,
|
||||
IPublicAccessService publicAccessService)
|
||||
{
|
||||
_webRoutingSettings = webRoutingSettings ?? throw new ArgumentNullException(nameof(webRoutingSettings));
|
||||
_contentFinders = contentFinders ?? throw new ArgumentNullException(nameof(contentFinders));
|
||||
_contentLastChanceFinder = contentLastChanceFinder ?? throw new ArgumentNullException(nameof(contentLastChanceFinder));
|
||||
_profilingLogger = proflog ?? throw new ArgumentNullException(nameof(proflog));
|
||||
_variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor));
|
||||
_logger = proflog;
|
||||
_publishedUrlProvider = publishedUrlProvider;
|
||||
_requestAccessor = requestAccessor;
|
||||
_publishedValueFallback = publishedValueFallback;
|
||||
_publicAccessChecker = publicAccessChecker;
|
||||
_fileService = fileService;
|
||||
_contentTypeService = contentTypeService;
|
||||
_publicAccessService = publicAccessService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPublishedRequest CreateRequest(IUmbracoContext umbracoContext, Uri uri = null)
|
||||
{
|
||||
return new PublishedRequest(this, umbracoContext, _webRoutingSettings, uri ?? umbracoContext.CleanedUmbracoUrl);
|
||||
}
|
||||
|
||||
#region Request
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryRouteRequest(IPublishedRequest request)
|
||||
{
|
||||
// disabled - is it going to change the routing?
|
||||
//_pcr.OnPreparing();
|
||||
|
||||
FindDomain(request);
|
||||
if (request.IsRedirect) return false;
|
||||
if (request.HasPublishedContent) return true;
|
||||
FindPublishedContent(request);
|
||||
if (request.IsRedirect) return false;
|
||||
|
||||
// don't handle anything - we just want to ensure that we find the content
|
||||
//HandlePublishedContent();
|
||||
//FindTemplate();
|
||||
//FollowExternalRedirect();
|
||||
//HandleWildcardDomains();
|
||||
|
||||
// disabled - we just want to ensure that we find the content
|
||||
//_pcr.OnPrepared();
|
||||
|
||||
return request.HasPublishedContent;
|
||||
}
|
||||
|
||||
private void SetVariationContext(string culture)
|
||||
{
|
||||
var variationContext = _variationContextAccessor.VariationContext;
|
||||
if (variationContext != null && variationContext.Culture == culture) return;
|
||||
_variationContextAccessor.VariationContext = new VariationContext(culture);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool PrepareRequest(IPublishedRequest request)
|
||||
{
|
||||
// note - at that point the original legacy module did something do handle IIS custom 404 errors
|
||||
// ie pages looking like /anything.aspx?404;/path/to/document - I guess the reason was to support
|
||||
// "directory urls" without having to do wildcard mapping to ASP.NET on old IIS. This is a pain
|
||||
// to maintain and probably not used anymore - removed as of 06/2012. @zpqrtbnk.
|
||||
//
|
||||
// to trigger Umbraco's not-found, one should configure IIS and/or ASP.NET custom 404 errors
|
||||
// so that they point to a non-existing page eg /redirect-404.aspx
|
||||
// TODO: SD: We need more information on this for when we release 4.10.0 as I'm not sure what this means.
|
||||
|
||||
// trigger the Preparing event - at that point anything can still be changed
|
||||
// the idea is that it is possible to change the uri
|
||||
//
|
||||
request.OnPreparing();
|
||||
|
||||
//find domain
|
||||
FindDomain(request);
|
||||
|
||||
// if request has been flagged to redirect then return
|
||||
// whoever called us is in charge of actually redirecting
|
||||
if (request.IsRedirect)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// set the culture on the thread - once, so it's set when running document lookups
|
||||
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture;
|
||||
SetVariationContext(request.Culture.Name);
|
||||
|
||||
//find the published content if it's not assigned. This could be manually assigned with a custom route handler, or
|
||||
// with something like EnsurePublishedContentRequestAttribute or UmbracoVirtualNodeRouteHandler. Those in turn call this method
|
||||
// to setup the rest of the pipeline but we don't want to run the finders since there's one assigned.
|
||||
if (request.PublishedContent == null)
|
||||
{
|
||||
// find the document & template
|
||||
FindPublishedContentAndTemplate(request);
|
||||
}
|
||||
|
||||
// handle wildcard domains
|
||||
HandleWildcardDomains(request);
|
||||
|
||||
// set the culture on the thread -- again, 'cos it might have changed due to a finder or wildcard domain
|
||||
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = request.Culture;
|
||||
SetVariationContext(request.Culture.Name);
|
||||
|
||||
// trigger the Prepared event - at that point it is still possible to change about anything
|
||||
// even though the request might be flagged for redirection - we'll redirect _after_ the event
|
||||
//
|
||||
// also, OnPrepared() will make the PublishedRequest readonly, so nothing can change
|
||||
//
|
||||
request.OnPrepared();
|
||||
|
||||
// we don't take care of anything so if the content has changed, it's up to the user
|
||||
// to find out the appropriate template
|
||||
|
||||
//complete the PCR and assign the remaining values
|
||||
return ConfigureRequest(request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by PrepareRequest once everything has been discovered, resolved and assigned to the PCR. This method
|
||||
/// finalizes the PCR with the values assigned.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns false if the request was not successfully configured
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This method logic has been put into it's own method in case developers have created a custom PCR or are assigning their own values
|
||||
/// but need to finalize it themselves.
|
||||
/// </remarks>
|
||||
public bool ConfigureRequest(IPublishedRequest frequest)
|
||||
{
|
||||
if (frequest.HasPublishedContent == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// set the culture on the thread -- again, 'cos it might have changed in the event handler
|
||||
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = frequest.Culture;
|
||||
SetVariationContext(frequest.Culture.Name);
|
||||
|
||||
// if request has been flagged to redirect, or has no content to display,
|
||||
// then return - whoever called us is in charge of actually redirecting
|
||||
if (frequest.IsRedirect || frequest.HasPublishedContent == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// we may be 404 _and_ have a content
|
||||
|
||||
// can't go beyond that point without a PublishedContent to render
|
||||
// it's ok not to have a template, in order to give MVC a chance to hijack routes
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateRequestToNotFound(IPublishedRequest request)
|
||||
{
|
||||
// clear content
|
||||
var content = request.PublishedContent;
|
||||
request.PublishedContent = null;
|
||||
|
||||
HandlePublishedContent(request); // will go 404
|
||||
FindTemplate(request);
|
||||
|
||||
// if request has been flagged to redirect then return
|
||||
// whoever called us is in charge of redirecting
|
||||
if (request.IsRedirect)
|
||||
return;
|
||||
|
||||
if (request.HasPublishedContent == false)
|
||||
{
|
||||
// means the engine could not find a proper document to handle 404
|
||||
// restore the saved content so we know it exists
|
||||
request.PublishedContent = content;
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.HasTemplate == false)
|
||||
{
|
||||
// means we may have a document, but we have no template
|
||||
// at that point there isn't much we can do and there is no point returning
|
||||
// to Mvc since Mvc can't do much either
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Domain
|
||||
|
||||
/// <summary>
|
||||
/// Finds the site root (if any) matching the http request, and updates the PublishedRequest accordingly.
|
||||
/// </summary>
|
||||
/// <returns>A value indicating whether a domain was found.</returns>
|
||||
internal bool FindDomain(IPublishedRequest request)
|
||||
{
|
||||
const string tracePrefix = "FindDomain: ";
|
||||
|
||||
// note - we are not handling schemes nor ports here.
|
||||
|
||||
_logger.Debug<PublishedRouter>("{TracePrefix}Uri={RequestUri}", tracePrefix, request.Uri);
|
||||
|
||||
var domainsCache = request.UmbracoContext.PublishedSnapshot.Domains;
|
||||
var domains = domainsCache.GetAll(includeWildcards: false).ToList();
|
||||
|
||||
// determines whether a domain corresponds to a published document, since some
|
||||
// domains may exist but on a document that has been unpublished - as a whole - or
|
||||
// that is not published for the domain's culture - in which case the domain does
|
||||
// not apply
|
||||
bool IsPublishedContentDomain(Domain domain)
|
||||
{
|
||||
// just get it from content cache - optimize there, not here
|
||||
var domainDocument = request.UmbracoContext.PublishedSnapshot.Content.GetById(domain.ContentId);
|
||||
|
||||
// not published - at all
|
||||
if (domainDocument == null)
|
||||
return false;
|
||||
|
||||
// invariant - always published
|
||||
if (!domainDocument.ContentType.VariesByCulture())
|
||||
return true;
|
||||
|
||||
// variant, ensure that the culture corresponding to the domain's language is published
|
||||
return domainDocument.Cultures.ContainsKey(domain.Culture.Name);
|
||||
}
|
||||
|
||||
domains = domains.Where(IsPublishedContentDomain).ToList();
|
||||
|
||||
var defaultCulture = domainsCache.DefaultCulture;
|
||||
|
||||
// try to find a domain matching the current request
|
||||
var domainAndUri = DomainUtilities.SelectDomain(domains, request.Uri, defaultCulture: defaultCulture);
|
||||
|
||||
// handle domain - always has a contentId and a culture
|
||||
if (domainAndUri != null)
|
||||
{
|
||||
// matching an existing domain
|
||||
_logger.Debug<PublishedRouter>("{TracePrefix}Matches domain={Domain}, rootId={RootContentId}, culture={Culture}", tracePrefix, domainAndUri.Name, domainAndUri.ContentId, domainAndUri.Culture);
|
||||
|
||||
request.Domain = domainAndUri;
|
||||
request.Culture = domainAndUri.Culture;
|
||||
|
||||
// canonical? not implemented at the moment
|
||||
// if (...)
|
||||
// {
|
||||
// _pcr.RedirectUrl = "...";
|
||||
// return true;
|
||||
// }
|
||||
}
|
||||
else
|
||||
{
|
||||
// not matching any existing domain
|
||||
_logger.Debug<PublishedRouter>("{TracePrefix}Matches no domain", tracePrefix);
|
||||
|
||||
request.Culture = defaultCulture == null ? CultureInfo.CurrentUICulture : new CultureInfo(defaultCulture);
|
||||
}
|
||||
|
||||
_logger.Debug<PublishedRouter>("{TracePrefix}Culture={CultureName}", tracePrefix, request.Culture.Name);
|
||||
|
||||
return request.Domain != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks for wildcard domains in the path and updates <c>Culture</c> accordingly.
|
||||
/// </summary>
|
||||
internal void HandleWildcardDomains(IPublishedRequest request)
|
||||
{
|
||||
const string tracePrefix = "HandleWildcardDomains: ";
|
||||
|
||||
if (request.HasPublishedContent == false)
|
||||
return;
|
||||
|
||||
var nodePath = request.PublishedContent.Path;
|
||||
_logger.Debug<PublishedRouter>("{TracePrefix}Path={NodePath}", tracePrefix, nodePath);
|
||||
var rootNodeId = request.HasDomain ? request.Domain.ContentId : (int?)null;
|
||||
var domain = DomainUtilities.FindWildcardDomainInPath(request.UmbracoContext.PublishedSnapshot.Domains.GetAll(true), nodePath, rootNodeId);
|
||||
|
||||
// always has a contentId and a culture
|
||||
if (domain != null)
|
||||
{
|
||||
request.Culture = domain.Culture;
|
||||
_logger.Debug<PublishedRouter>("{TracePrefix}Got domain on node {DomainContentId}, set culture to {CultureName}", tracePrefix, domain.ContentId, request.Culture.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("{TracePrefix}No match.", tracePrefix);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Rendering engine
|
||||
|
||||
internal bool FindTemplateRenderingEngineInDirectory(DirectoryInfo directory, string alias, string[] extensions)
|
||||
{
|
||||
if (directory == null || directory.Exists == false)
|
||||
return false;
|
||||
|
||||
var pos = alias.IndexOf('/');
|
||||
if (pos > 0)
|
||||
{
|
||||
// recurse
|
||||
var subdir = directory.GetDirectories(alias.Substring(0, pos)).FirstOrDefault();
|
||||
alias = alias.Substring(pos + 1);
|
||||
return subdir != null && FindTemplateRenderingEngineInDirectory(subdir, alias, extensions);
|
||||
}
|
||||
|
||||
// look here
|
||||
return directory.GetFiles().Any(f => extensions.Any(e => f.Name.InvariantEquals(alias + e)));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Document and template
|
||||
|
||||
/// <inheritdoc />
|
||||
public ITemplate GetTemplate(string alias)
|
||||
{
|
||||
return _fileService.GetTemplate(alias);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the Umbraco document (if any) matching the request, and updates the PublishedRequest accordingly.
|
||||
/// </summary>
|
||||
/// <returns>A value indicating whether a document and template were found.</returns>
|
||||
private void FindPublishedContentAndTemplate(IPublishedRequest request)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("FindPublishedContentAndTemplate: Path={UriAbsolutePath}", request.Uri.AbsolutePath);
|
||||
|
||||
// run the document finders
|
||||
FindPublishedContent(request);
|
||||
|
||||
// if request has been flagged to redirect then return
|
||||
// whoever called us is in charge of actually redirecting
|
||||
// -- do not process anything any further --
|
||||
if (request.IsRedirect)
|
||||
return;
|
||||
|
||||
// not handling umbracoRedirect here but after LookupDocument2
|
||||
// so internal redirect, 404, etc has precedence over redirect
|
||||
|
||||
// handle not-found, redirects, access...
|
||||
HandlePublishedContent(request);
|
||||
|
||||
// find a template
|
||||
FindTemplate(request);
|
||||
|
||||
// handle umbracoRedirect
|
||||
FollowExternalRedirect(request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find the document matching the request, by running the IPublishedContentFinder instances.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">There is no finder collection.</exception>
|
||||
internal void FindPublishedContent(IPublishedRequest request)
|
||||
{
|
||||
const string tracePrefix = "FindPublishedContent: ";
|
||||
|
||||
// look for the document
|
||||
// the first successful finder, if any, will set this.PublishedContent, and may also set this.Template
|
||||
// some finders may implement caching
|
||||
|
||||
using (_profilingLogger.DebugDuration<PublishedRouter>(
|
||||
$"{tracePrefix}Begin finders",
|
||||
$"{tracePrefix}End finders, {(request.HasPublishedContent ? "a document was found" : "no document was found")}"))
|
||||
{
|
||||
//iterate but return on first one that finds it
|
||||
var found = _contentFinders.Any(finder =>
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("Finder {ContentFinderType}", finder.GetType().FullName);
|
||||
return finder.TryFindContent(request);
|
||||
});
|
||||
}
|
||||
|
||||
// indicate that the published content (if any) we have at the moment is the
|
||||
// one that was found by the standard finders before anything else took place.
|
||||
request.SetIsInitialPublishedContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the published content (if any).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Handles "not found", internal redirects, access validation...
|
||||
/// things that must be handled in one place because they can create loops
|
||||
/// </remarks>
|
||||
private void HandlePublishedContent(IPublishedRequest request)
|
||||
{
|
||||
// because these might loop, we have to have some sort of infinite loop detection
|
||||
int i = 0, j = 0;
|
||||
const int maxLoop = 8;
|
||||
do
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("HandlePublishedContent: Loop {LoopCounter}", i);
|
||||
|
||||
// handle not found
|
||||
if (request.HasPublishedContent == false)
|
||||
{
|
||||
request.Is404 = true;
|
||||
_logger.Debug<PublishedRouter>("HandlePublishedContent: No document, try last chance lookup");
|
||||
|
||||
// if it fails then give up, there isn't much more that we can do
|
||||
if (_contentLastChanceFinder.TryFindContent(request) == false)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("HandlePublishedContent: Failed to find a document, give up");
|
||||
break;
|
||||
}
|
||||
|
||||
_logger.Debug<PublishedRouter>("HandlePublishedContent: Found a document");
|
||||
}
|
||||
|
||||
// follow internal redirects as long as it's not running out of control ie infinite loop of some sort
|
||||
j = 0;
|
||||
while (FollowInternalRedirects(request) && j++ < maxLoop)
|
||||
{ }
|
||||
if (j == maxLoop) // we're running out of control
|
||||
break;
|
||||
|
||||
// ensure access
|
||||
if (request.HasPublishedContent)
|
||||
EnsurePublishedContentAccess(request);
|
||||
|
||||
// loop while we don't have page, ie the redirect or access
|
||||
// got us to nowhere and now we need to run the notFoundLookup again
|
||||
// as long as it's not running out of control ie infinite loop of some sort
|
||||
|
||||
} while (request.HasPublishedContent == false && i++ < maxLoop);
|
||||
|
||||
if (i == maxLoop || j == maxLoop)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("HandlePublishedContent: Looks like we are running into an infinite loop, abort");
|
||||
request.PublishedContent = null;
|
||||
}
|
||||
|
||||
_logger.Debug<PublishedRouter>("HandlePublishedContent: End");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Follows internal redirections through the <c>umbracoInternalRedirectId</c> document property.
|
||||
/// </summary>
|
||||
/// <returns>A value indicating whether redirection took place and led to a new published document.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Redirecting to a different site root and/or culture will not pick the new site root nor the new culture.</para>
|
||||
/// <para>As per legacy, if the redirect does not work, we just ignore it.</para>
|
||||
/// </remarks>
|
||||
private bool FollowInternalRedirects(IPublishedRequest request)
|
||||
{
|
||||
if (request.PublishedContent == null)
|
||||
throw new InvalidOperationException("There is no PublishedContent.");
|
||||
|
||||
// don't try to find a redirect if the property doesn't exist
|
||||
if (request.PublishedContent.HasProperty(Core.Constants.Conventions.Content.InternalRedirectId) == false)
|
||||
return false;
|
||||
|
||||
var redirect = false;
|
||||
var valid = false;
|
||||
IPublishedContent internalRedirectNode = null;
|
||||
var internalRedirectId = request.PublishedContent.Value(_publishedValueFallback, Core.Constants.Conventions.Content.InternalRedirectId, defaultValue: -1);
|
||||
|
||||
if (internalRedirectId > 0)
|
||||
{
|
||||
// try and get the redirect node from a legacy integer ID
|
||||
valid = true;
|
||||
internalRedirectNode = request.UmbracoContext.Content.GetById(internalRedirectId);
|
||||
}
|
||||
else
|
||||
{
|
||||
var udiInternalRedirectId = request.PublishedContent.Value<GuidUdi>(_publishedValueFallback, Core.Constants.Conventions.Content.InternalRedirectId);
|
||||
if (udiInternalRedirectId != null)
|
||||
{
|
||||
// try and get the redirect node from a UDI Guid
|
||||
valid = true;
|
||||
internalRedirectNode = request.UmbracoContext.Content.GetById(udiInternalRedirectId.Guid);
|
||||
}
|
||||
}
|
||||
|
||||
if (valid == false)
|
||||
{
|
||||
// bad redirect - log and display the current page (legacy behavior)
|
||||
_logger.Debug<PublishedRouter>("FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: value is not an int nor a GuidUdi.",
|
||||
request.PublishedContent.GetProperty(Core.Constants.Conventions.Content.InternalRedirectId).GetSourceValue());
|
||||
}
|
||||
|
||||
if (internalRedirectNode == null)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("FollowInternalRedirects: Failed to redirect to id={InternalRedirectId}: no such published document.",
|
||||
request.PublishedContent.GetProperty(Core.Constants.Conventions.Content.InternalRedirectId).GetSourceValue());
|
||||
}
|
||||
else if (internalRedirectId == request.PublishedContent.Id)
|
||||
{
|
||||
// redirect to self
|
||||
_logger.Debug<PublishedRouter>("FollowInternalRedirects: Redirecting to self, ignore");
|
||||
}
|
||||
else
|
||||
{
|
||||
request.SetInternalRedirectPublishedContent(internalRedirectNode); // don't use .PublishedContent here
|
||||
redirect = true;
|
||||
_logger.Debug<PublishedRouter>("FollowInternalRedirects: Redirecting to id={InternalRedirectId}", internalRedirectId);
|
||||
}
|
||||
|
||||
return redirect;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that access to current node is permitted.
|
||||
/// </summary>
|
||||
/// <remarks>Redirecting to a different site root and/or culture will not pick the new site root nor the new culture.</remarks>
|
||||
private void EnsurePublishedContentAccess(IPublishedRequest request)
|
||||
{
|
||||
if (request.PublishedContent == null)
|
||||
throw new InvalidOperationException("There is no PublishedContent.");
|
||||
|
||||
var path = request.PublishedContent.Path;
|
||||
|
||||
var publicAccessAttempt = _publicAccessService.IsProtected(path);
|
||||
|
||||
if (publicAccessAttempt)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("EnsurePublishedContentAccess: Page is protected, check for access");
|
||||
|
||||
var status = _publicAccessChecker.HasMemberAccessToContent(request.PublishedContent.Id);
|
||||
switch (status)
|
||||
{
|
||||
case PublicAccessStatus.NotLoggedIn:
|
||||
_logger.Debug<PublishedRouter>("EnsurePublishedContentAccess: Not logged in, redirect to login page");
|
||||
SetPublishedContentAsOtherPage(request, publicAccessAttempt.Result.LoginNodeId);
|
||||
break;
|
||||
case PublicAccessStatus.AccessDenied:
|
||||
_logger.Debug<PublishedRouter>("EnsurePublishedContentAccess: Current member has not access, redirect to error page");
|
||||
SetPublishedContentAsOtherPage(request, publicAccessAttempt.Result.NoAccessNodeId);
|
||||
break;
|
||||
case PublicAccessStatus.LockedOut:
|
||||
_logger.Debug<PublishedRouter>("Current member is locked out, redirect to error page");
|
||||
SetPublishedContentAsOtherPage(request, publicAccessAttempt.Result.NoAccessNodeId);
|
||||
break;
|
||||
case PublicAccessStatus.NotApproved:
|
||||
_logger.Debug<PublishedRouter>("Current member is unapproved, redirect to error page");
|
||||
SetPublishedContentAsOtherPage(request, publicAccessAttempt.Result.NoAccessNodeId);
|
||||
break;
|
||||
case PublicAccessStatus.AccessAccepted:
|
||||
_logger.Debug<PublishedRouter>("Current member has access");
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("EnsurePublishedContentAccess: Page is not protected");
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetPublishedContentAsOtherPage(IPublishedRequest request, int errorPageId)
|
||||
{
|
||||
if (errorPageId != request.PublishedContent.Id)
|
||||
request.PublishedContent = request.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a template for the current node, if any.
|
||||
/// </summary>
|
||||
private void FindTemplate(IPublishedRequest request)
|
||||
{
|
||||
// NOTE: at the moment there is only 1 way to find a template, and then ppl must
|
||||
// use the Prepared event to change the template if they wish. Should we also
|
||||
// implement an ITemplateFinder logic?
|
||||
|
||||
if (request.PublishedContent == null)
|
||||
{
|
||||
request.TemplateModel = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// read the alternate template alias, from querystring, form, cookie or server vars,
|
||||
// only if the published content is the initial once, else the alternate template
|
||||
// does not apply
|
||||
// + optionally, apply the alternate template on internal redirects
|
||||
var useAltTemplate = request.IsInitialPublishedContent
|
||||
|| (_webRoutingSettings.InternalRedirectPreservesTemplate && request.IsInternalRedirectPublishedContent);
|
||||
var altTemplate = useAltTemplate
|
||||
? _requestAccessor.GetRequestValue(Core.Constants.Conventions.Url.AltTemplate)
|
||||
: null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(altTemplate))
|
||||
{
|
||||
// we don't have an alternate template specified. use the current one if there's one already,
|
||||
// which can happen if a content lookup also set the template (LookupByNiceUrlAndTemplate...),
|
||||
// else lookup the template id on the document then lookup the template with that id.
|
||||
|
||||
if (request.HasTemplate)
|
||||
{
|
||||
_logger.Debug<IPublishedRequest>("FindTemplate: Has a template already, and no alternate template.");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: When we remove the need for a database for templates, then this id should be irrelevant,
|
||||
// not sure how were going to do this nicely.
|
||||
|
||||
// TODO: We need to limit altTemplate to only allow templates that are assigned to the current document type!
|
||||
// if the template isn't assigned to the document type we should log a warning and return 404
|
||||
|
||||
var templateId = request.PublishedContent.TemplateId;
|
||||
request.TemplateModel = GetTemplateModel(templateId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// we have an alternate template specified. lookup the template with that alias
|
||||
// this means the we override any template that a content lookup might have set
|
||||
// so /path/to/page/template1?altTemplate=template2 will use template2
|
||||
|
||||
// ignore if the alias does not match - just trace
|
||||
|
||||
if (request.HasTemplate)
|
||||
_logger.Debug<PublishedRouter>("FindTemplate: Has a template already, but also an alternative template.");
|
||||
_logger.Debug<PublishedRouter>("FindTemplate: Look for alternative template alias={AltTemplate}", altTemplate);
|
||||
|
||||
// IsAllowedTemplate deals both with DisableAlternativeTemplates and ValidateAlternativeTemplates settings
|
||||
if (request.PublishedContent.IsAllowedTemplate(
|
||||
_fileService,
|
||||
_contentTypeService,
|
||||
_webRoutingSettings.DisableAlternativeTemplates,
|
||||
_webRoutingSettings.ValidateAlternativeTemplates,
|
||||
altTemplate))
|
||||
{
|
||||
// allowed, use
|
||||
var template = _fileService.GetTemplate(altTemplate);
|
||||
|
||||
if (template != null)
|
||||
{
|
||||
request.TemplateModel = template;
|
||||
_logger.Debug<PublishedRouter>("FindTemplate: Got alternative template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("FindTemplate: The alternative template with alias={AltTemplate} does not exist, ignoring.", altTemplate);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn<PublishedRouter>("FindTemplate: Alternative template {TemplateAlias} is not allowed on node {NodeId}, ignoring.", altTemplate, request.PublishedContent.Id);
|
||||
|
||||
// no allowed, back to default
|
||||
var templateId = request.PublishedContent.TemplateId;
|
||||
request.TemplateModel = GetTemplateModel(templateId);
|
||||
}
|
||||
}
|
||||
|
||||
if (request.HasTemplate == false)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("FindTemplate: No template was found.");
|
||||
|
||||
// initial idea was: if we're not already 404 and UmbracoSettings.HandleMissingTemplateAs404 is true
|
||||
// then reset _pcr.Document to null to force a 404.
|
||||
//
|
||||
// but: because we want to let MVC hijack routes even though no template is defined, we decide that
|
||||
// a missing template is OK but the request will then be forwarded to MVC, which will need to take
|
||||
// care of everything.
|
||||
//
|
||||
// so, don't set _pcr.Document to null here
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("FindTemplate: Running with template id={TemplateId} alias={TemplateAlias}", request.TemplateModel.Id, request.TemplateModel.Alias);
|
||||
}
|
||||
}
|
||||
|
||||
private ITemplate GetTemplateModel(int? templateId)
|
||||
{
|
||||
if (templateId.HasValue == false || templateId.Value == default)
|
||||
{
|
||||
_logger.Debug<PublishedRouter>("GetTemplateModel: No template.");
|
||||
return null;
|
||||
}
|
||||
|
||||
_logger.Debug<PublishedRouter>("GetTemplateModel: Get template id={TemplateId}", templateId);
|
||||
|
||||
if (templateId == null)
|
||||
throw new InvalidOperationException("The template is not set, the page cannot render.");
|
||||
|
||||
var template = _fileService.GetTemplate(templateId.Value);
|
||||
if (template == null)
|
||||
throw new InvalidOperationException("The template with Id " + templateId + " does not exist, the page cannot render.");
|
||||
_logger.Debug<PublishedRouter>("GetTemplateModel: Got template id={TemplateId} alias={TemplateAlias}", template.Id, template.Alias);
|
||||
return template;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Follows external redirection through <c>umbracoRedirect</c> document property.
|
||||
/// </summary>
|
||||
/// <remarks>As per legacy, if the redirect does not work, we just ignore it.</remarks>
|
||||
private void FollowExternalRedirect(IPublishedRequest request)
|
||||
{
|
||||
if (request.HasPublishedContent == false) return;
|
||||
|
||||
// don't try to find a redirect if the property doesn't exist
|
||||
if (request.PublishedContent.HasProperty(Core.Constants.Conventions.Content.Redirect) == false)
|
||||
return;
|
||||
|
||||
var redirectId = request.PublishedContent.Value(_publishedValueFallback, Core.Constants.Conventions.Content.Redirect, defaultValue: -1);
|
||||
var redirectUrl = "#";
|
||||
if (redirectId > 0)
|
||||
{
|
||||
redirectUrl = _publishedUrlProvider.GetUrl(redirectId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// might be a UDI instead of an int Id
|
||||
var redirectUdi = request.PublishedContent.Value<GuidUdi>(_publishedValueFallback, Core.Constants.Conventions.Content.Redirect);
|
||||
if (redirectUdi != null)
|
||||
redirectUrl = _publishedUrlProvider.GetUrl(redirectUdi.Guid);
|
||||
}
|
||||
if (redirectUrl != "#")
|
||||
request.SetRedirect(redirectUrl);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,14 @@
|
||||
@scope
|
||||
|
||||
@description
|
||||
<b>Added in Umbraco 7.8</b>. The tour component is a global component and is already added to the umbraco markup.
|
||||
In the Umbraco UI the tours live in the "Help drawer" which opens when you click the Help-icon in the bottom left corner of Umbraco.
|
||||
You can easily add you own tours to the Help-drawer or show and start tours from
|
||||
<b>Added in Umbraco 7.8</b>. The tour component is a global component and is already added to the umbraco markup.
|
||||
In the Umbraco UI the tours live in the "Help drawer" which opens when you click the Help-icon in the bottom left corner of Umbraco.
|
||||
You can easily add you own tours to the Help-drawer or show and start tours from
|
||||
anywhere in the Umbraco backoffice. To see a real world example of a custom tour implementation, install <a href="https://our.umbraco.com/projects/starter-kits/the-starter-kit/">The Starter Kit</a> in Umbraco 7.8
|
||||
|
||||
<h1><b>Extending the help drawer with custom tours</b></h1>
|
||||
The easiet way to add new tours to Umbraco is through the Help-drawer. All it requires is a my-tour.json file.
|
||||
Place the file in <i>App_Plugins/{MyPackage}/backoffice/tours/{my-tour}.json</i> and it will automatically be
|
||||
The easiet way to add new tours to Umbraco is through the Help-drawer. All it requires is a my-tour.json file.
|
||||
Place the file in <i>App_Plugins/{MyPackage}/backoffice/tours/{my-tour}.json</i> and it will automatically be
|
||||
picked up by Umbraco and shown in the Help-drawer.
|
||||
|
||||
<h3><b>The tour object</b></h3>
|
||||
@@ -26,7 +26,7 @@ The tour object consist of two parts - The overall tour configuration and a list
|
||||
"groupOrder": 200 // Control the order of tour groups
|
||||
"allowDisable": // Adds a "Don't" show this tour again"-button to the intro step
|
||||
"culture" : // From v7.11+. Specifies the culture of the tour (eg. en-US), if set the tour will only be shown to users with this culture set on their profile. If omitted or left empty the tour will be visible to all users
|
||||
"requiredSections":["content", "media", "mySection"] // Sections that the tour will access while running, if the user does not have access to the required tour sections, the tour will not load.
|
||||
"requiredSections":["content", "media", "mySection"] // Sections that the tour will access while running, if the user does not have access to the required tour sections, the tour will not load.
|
||||
"steps": [] // tour steps - see next example
|
||||
}
|
||||
</pre>
|
||||
@@ -43,11 +43,12 @@ The tour object consist of two parts - The overall tour configuration and a list
|
||||
"backdropOpacity": 0.4 // the backdrop opacity
|
||||
"view": "" // add a custom view
|
||||
"customProperties" : {} // add any custom properties needed for the custom view
|
||||
"skipStepIfVisible": ".dashboard div [data-element='my-tour-button']" // if we can find this DOM element on the page then we will skip this step
|
||||
}
|
||||
</pre>
|
||||
|
||||
<h1><b>Adding tours to other parts of the Umbraco backoffice</b></h1>
|
||||
It is also possible to add a list of custom tours to other parts of the Umbraco backoffice,
|
||||
It is also possible to add a list of custom tours to other parts of the Umbraco backoffice,
|
||||
as an example on a Dashboard in a Custom section. You can then use the {@link umbraco.services.tourService tourService} to start and stop tours but you don't have to register them as part of the tour service.
|
||||
|
||||
<h1><b>Using the tour service</b></h1>
|
||||
@@ -86,7 +87,8 @@ as an example on a Dashboard in a Custom section. You can then use the {@link um
|
||||
"element": "[data-element='my-tour-button']",
|
||||
"title": "Click the button",
|
||||
"content": "Click the button",
|
||||
"event": "click"
|
||||
"event": "click",
|
||||
"skipStepIfVisible": "[data-element='my-other-tour-button']"
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -257,9 +259,26 @@ In the following example you see how to run some custom logic before a step goes
|
||||
|
||||
// make sure we don't go too far
|
||||
if (scope.model.currentStepIndex !== scope.model.steps.length) {
|
||||
|
||||
var upcomingStep = scope.model.steps[scope.model.currentStepIndex];
|
||||
|
||||
// If the currentStep JSON object has 'skipStepIfVisible'
|
||||
// It's a DOM selector - if we find it then we ship over this step
|
||||
if(upcomingStep.skipStepIfVisible) {
|
||||
let tryFindDomEl = document.querySelector(upcomingStep.element);
|
||||
if(tryFindDomEl) {
|
||||
// check if element is visible:
|
||||
if( tryFindDomEl.offsetWidth || tryFindDomEl.offsetHeight || tryFindDomEl.getClientRects().length ) {
|
||||
// if it was visible then we skip the step.
|
||||
nextStep();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startStep();
|
||||
// tour completed - final step
|
||||
|
||||
} else {
|
||||
// tour completed - final step
|
||||
scope.loadingStep = true;
|
||||
|
||||
waitForPendingRerequests().then(function () {
|
||||
|
||||
@@ -48,6 +48,9 @@ label.umb-form-check--checkbox{
|
||||
&:checked ~ .umb-form-check__state .umb-form-check__check {
|
||||
border-color: @ui-option-type;
|
||||
}
|
||||
&[type='checkbox']:checked ~ .umb-form-check__state .umb-form-check__check {
|
||||
background-color: @ui-option-type;
|
||||
}
|
||||
&:checked:hover ~ .umb-form-check__state .umb-form-check__check {
|
||||
&::before {
|
||||
background: @ui-option-type-hover;
|
||||
|
||||
@@ -52,6 +52,11 @@ namespace Umbraco.Web.UI.BackOffice
|
||||
options.ShouldProfile = request => false; // WebProfiler determine and start profiling. We should not use the MiniProfilerMiddleware to also profile
|
||||
});
|
||||
|
||||
//We need to have runtime compilation of views when using umbraco. We could consider having only this when a specific config is set.
|
||||
//But as far as I can see, there are still precompiled views, even when this is activated, so maybe it is okay.
|
||||
services.AddControllersWithViews().AddRazorRuntimeCompilation();
|
||||
|
||||
|
||||
// If using Kestrel: https://stackoverflow.com/a/55196057
|
||||
services.Configure<KestrelServerOptions>(options =>
|
||||
{
|
||||
|
||||
@@ -188,27 +188,21 @@
|
||||
"event": "click"
|
||||
},
|
||||
{
|
||||
"element": "[data-element~='editor-data-type-picker']",
|
||||
"element": "[ng-controller*='Umbraco.Editors.DataTypePickerController'] [data-element='editor-data-type-picker']",
|
||||
"elementPreventClick": true,
|
||||
"title": "Editor picker",
|
||||
"content": "<p>In the editor picker dialog we can pick one of the many built-in editors.</p><p><em>You can choose from preconfigured data types (Reuse) or create a new configuration (Available editors)</em>.</p>"
|
||||
"content": "<p>In the editor picker dialog we can pick one of the many built-in editors.</p>"
|
||||
},
|
||||
{
|
||||
"element": "[data-element~='editor-data-type-picker'] [data-element='editor-Textarea']",
|
||||
"element": "[data-element~='editor-data-type-picker'] [data-element='datatype-Textarea']",
|
||||
"title": "Select editor",
|
||||
"content": "Select the <b>Textarea</b> editor. This will add a textarea to the Welcome Text property.",
|
||||
"event": "click"
|
||||
},
|
||||
{
|
||||
"element": "[data-element~='editor-data-type-settings']",
|
||||
"elementPreventClick": true,
|
||||
"element": "[data-element='editor-data-type-picker'] [data-element='datatypeconfig-Textarea'] > a",
|
||||
"title": "Editor settings",
|
||||
"content": "Each property editor can have individual settings. For the textarea editor you can set a character limit but in this case it is not needed."
|
||||
},
|
||||
{
|
||||
"element": "[data-element~='editor-data-type-settings'] [data-element='button-submit']",
|
||||
"title": "Save editor",
|
||||
"content": "Click <b>Submit</b> to save the changes.",
|
||||
"content": "Each property editor can have individual settings. For the textarea editor you can set a character limit but in this case it is not needed.",
|
||||
"event": "click"
|
||||
},
|
||||
{
|
||||
@@ -317,7 +311,8 @@
|
||||
"content": "<p>To see all our templates click the <b>small triangle</b> to the left of the templates node.</p>",
|
||||
"event": "click",
|
||||
"eventElement": "#tree [data-element='tree-item-templates'] [data-element='tree-item-expand']",
|
||||
"view": "templatetree"
|
||||
"view": "templatetree",
|
||||
"skipStepIfVisible": "#tree [data-element='tree-item-templates'] > div > button[data-element=tree-item-expand].icon-navigation-down"
|
||||
},
|
||||
{
|
||||
"element": "#tree [data-element='tree-item-templates'] [data-element='tree-item-Home Page']",
|
||||
|
||||
@@ -11,7 +11,7 @@ using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.Mapping;
|
||||
using Umbraco.Core.Net;
|
||||
using Umbraco.Core.Templates;
|
||||
using Umbraco.Net;
|
||||
using Umbraco.Core.PackageActions;
|
||||
using Umbraco.Core.Packaging;
|
||||
|
||||
@@ -386,30 +386,7 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Logs the current user out
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[ClearAngularAntiForgeryToken]
|
||||
[ValidateAngularAntiForgeryToken]
|
||||
public HttpResponseMessage PostLogout()
|
||||
{
|
||||
var owinContext = Request.TryGetOwinContext().Result;
|
||||
|
||||
owinContext.Authentication.SignOut(
|
||||
Core.Constants.Security.BackOfficeAuthenticationType,
|
||||
Core.Constants.Security.BackOfficeExternalAuthenticationType);
|
||||
|
||||
Logger.Info<AuthenticationController>("User {UserName} from IP address {RemoteIpAddress} has logged out", User.Identity == null ? "UNKNOWN" : User.Identity.Name, owinContext.Request.RemoteIpAddress);
|
||||
|
||||
if (UserManager != null)
|
||||
{
|
||||
int.TryParse(User.Identity.GetUserId(), out var userId);
|
||||
UserManager.RaiseLogoutSuccessEvent(User, userId);
|
||||
}
|
||||
|
||||
return Request.CreateResponse(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
|
||||
// NOTE: This has been migrated to netcore, but in netcore we don't explicitly set the principal in this method, that's done in ConfigureUmbracoBackOfficeCookieOptions so don't worry about that
|
||||
private HttpResponseMessage SetPrincipalAndReturnUserDetail(IUser user, IPrincipal principal)
|
||||
|
||||
@@ -4,7 +4,7 @@ using Microsoft.AspNet.SignalR;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Dictionary;
|
||||
using Umbraco.Core.Net;
|
||||
using Umbraco.Core.Templates;
|
||||
using Umbraco.Core.Runtime;
|
||||
using Umbraco.Core.Security;
|
||||
using Umbraco.Core.Services;
|
||||
@@ -41,8 +41,6 @@ namespace Umbraco.Web.Runtime
|
||||
composition.RegisterUnique<IMemberUserKeyProvider, MemberUserKeyProvider>();
|
||||
composition.RegisterUnique<IPublicAccessChecker, PublicAccessChecker>();
|
||||
|
||||
composition.RegisterUnique<ITemplateRenderer, TemplateRenderer>();
|
||||
|
||||
|
||||
// register the umbraco helper - this is Transient! very important!
|
||||
// also, if not level.Run, we cannot really use the helper (during upgrade...)
|
||||
|
||||
@@ -29,228 +29,6 @@ namespace Umbraco.Web.Security
|
||||
/// </summary>
|
||||
public static class AppBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configure Default Identity User Manager for Umbraco
|
||||
/// </summary>
|
||||
public static void ConfigureUserManagerForUmbracoBackOffice(this IAppBuilder app,
|
||||
ServiceContext services,
|
||||
IGlobalSettings globalSettings,
|
||||
UmbracoMapper mapper,
|
||||
// TODO: This could probably be optional?
|
||||
IPasswordConfiguration passwordConfiguration,
|
||||
IIpResolver ipResolver)
|
||||
{
|
||||
if (services == null) throw new ArgumentNullException(nameof(services));
|
||||
|
||||
//Configure Umbraco user manager to be created per request
|
||||
app.CreatePerOwinContext<BackOfficeOwinUserManager>(
|
||||
(options, owinContext) => BackOfficeOwinUserManager.Create(
|
||||
services.UserService,
|
||||
services.EntityService,
|
||||
services.ExternalLoginService,
|
||||
globalSettings,
|
||||
mapper,
|
||||
passwordConfiguration,
|
||||
ipResolver,
|
||||
new BackOfficeIdentityErrorDescriber(),
|
||||
app.GetDataProtectionProvider(),
|
||||
new NullLogger<BackOfficeUserManager<BackOfficeIdentityUser>>()));
|
||||
|
||||
app.SetBackOfficeUserManagerType<BackOfficeOwinUserManager, BackOfficeIdentityUser>();
|
||||
|
||||
//Create a sign in manager per request
|
||||
app.CreatePerOwinContext<BackOfficeSignInManager>((options, context) => BackOfficeSignInManager.Create(context, globalSettings, app.CreateLogger<BackOfficeSignInManager>()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure a custom UserStore with the Identity User Manager for Umbraco
|
||||
/// </summary>
|
||||
public static void ConfigureUserManagerForUmbracoBackOffice(this IAppBuilder app,
|
||||
IRuntimeState runtimeState,
|
||||
IGlobalSettings globalSettings,
|
||||
BackOfficeUserStore customUserStore,
|
||||
// TODO: This could probably be optional?
|
||||
IPasswordConfiguration passwordConfiguration,
|
||||
IIpResolver ipResolver)
|
||||
{
|
||||
if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState));
|
||||
if (customUserStore == null) throw new ArgumentNullException(nameof(customUserStore));
|
||||
|
||||
//Configure Umbraco user manager to be created per request
|
||||
app.CreatePerOwinContext<BackOfficeOwinUserManager>(
|
||||
(options, owinContext) => BackOfficeOwinUserManager.Create(
|
||||
passwordConfiguration,
|
||||
ipResolver,
|
||||
customUserStore,
|
||||
new BackOfficeIdentityErrorDescriber(),
|
||||
app.GetDataProtectionProvider(),
|
||||
new NullLogger<BackOfficeUserManager<BackOfficeIdentityUser>>()));
|
||||
|
||||
app.SetBackOfficeUserManagerType<BackOfficeOwinUserManager, BackOfficeIdentityUser>();
|
||||
|
||||
//Create a sign in manager per request
|
||||
app.CreatePerOwinContext<BackOfficeSignInManager>((options, context) => BackOfficeSignInManager.Create(context, globalSettings, app.CreateLogger(typeof(BackOfficeSignInManager).FullName)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the UmbracoBackOfficeAuthenticationMiddleware is assigned to the pipeline
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <param name="umbracoContextAccessor"></param>
|
||||
/// <param name="runtimeState"></param>
|
||||
/// <param name="userService"></param>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="securitySettings"></param>
|
||||
/// <param name="hostingEnvironment"></param>
|
||||
/// <param name="requestCache"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// By default this will be configured to execute on PipelineStage.Authenticate
|
||||
/// </remarks>
|
||||
public static IAppBuilder UseUmbracoBackOfficeCookieAuthentication(this IAppBuilder app,
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IRuntimeState runtimeState,
|
||||
IUserService userService,
|
||||
IGlobalSettings globalSettings,
|
||||
ISecuritySettings securitySettings,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IRequestCache requestCache)
|
||||
{
|
||||
return app.UseUmbracoBackOfficeCookieAuthentication(umbracoContextAccessor, runtimeState, userService, globalSettings, securitySettings, hostingEnvironment, requestCache, PipelineStage.Authenticate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the UmbracoBackOfficeAuthenticationMiddleware is assigned to the pipeline
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <param name="umbracoContextAccessor"></param>
|
||||
/// <param name="runtimeState"></param>
|
||||
/// <param name="userService"></param>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="securitySettings"></param>
|
||||
/// <param name="hostingEnvironment"></param>
|
||||
/// <param name="requestCache"></param>
|
||||
/// <param name="stage">
|
||||
/// Configurable pipeline stage
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public static IAppBuilder UseUmbracoBackOfficeCookieAuthentication(this IAppBuilder app,
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IRuntimeState runtimeState,
|
||||
IUserService userService,
|
||||
IGlobalSettings globalSettings,
|
||||
ISecuritySettings securitySettings,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IRequestCache requestCache,
|
||||
PipelineStage stage)
|
||||
{
|
||||
//Create the default options and provider
|
||||
var authOptions = app.CreateUmbracoCookieAuthOptions(umbracoContextAccessor, globalSettings, runtimeState, securitySettings, hostingEnvironment, requestCache);
|
||||
|
||||
return app.UseUmbracoBackOfficeCookieAuthentication(umbracoContextAccessor, runtimeState, globalSettings, securitySettings, hostingEnvironment, requestCache, authOptions, stage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the UmbracoBackOfficeAuthenticationMiddleware is assigned to the pipeline
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <param name="umbracoContextAccessor"></param>
|
||||
/// <param name="runtimeState"></param>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="securitySettings"></param>
|
||||
/// <param name="hostingEnvironment"></param>
|
||||
/// <param name="requestCache"></param>
|
||||
/// <param name="cookieOptions">Custom auth cookie options can be specified to have more control over the cookie authentication logic</param>
|
||||
/// <param name="stage">
|
||||
/// Configurable pipeline stage
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public static IAppBuilder UseUmbracoBackOfficeCookieAuthentication(this IAppBuilder app, IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtimeState, IGlobalSettings globalSettings,
|
||||
ISecuritySettings securitySettings, IHostingEnvironment hostingEnvironment, IRequestCache requestCache, CookieAuthenticationOptions cookieOptions, PipelineStage stage)
|
||||
{
|
||||
if (app == null) throw new ArgumentNullException(nameof(app));
|
||||
if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState));
|
||||
if (cookieOptions == null) throw new ArgumentNullException(nameof(cookieOptions));
|
||||
if (cookieOptions.Provider == null)
|
||||
throw new ArgumentNullException("cookieOptions.Provider cannot be null.", nameof(cookieOptions));
|
||||
if (cookieOptions.Provider is BackOfficeCookieAuthenticationProvider == false)
|
||||
throw new ArgumentException($"cookieOptions.Provider must be of type {typeof(BackOfficeCookieAuthenticationProvider)}.", nameof(cookieOptions));
|
||||
|
||||
app.UseUmbracoBackOfficeCookieAuthenticationInternal(cookieOptions, runtimeState, requestCache, stage);
|
||||
|
||||
//don't apply if app is not ready
|
||||
if (runtimeState.Level != RuntimeLevel.Upgrade && runtimeState.Level != RuntimeLevel.Run) return app;
|
||||
|
||||
var backOfficePath = globalSettings.GetBackOfficePath(hostingEnvironment);
|
||||
var cookieAuthOptions = app.CreateUmbracoCookieAuthOptions(
|
||||
umbracoContextAccessor, globalSettings, runtimeState, securitySettings,
|
||||
//This defines the explicit path read cookies from for this middleware
|
||||
hostingEnvironment, requestCache, new[] {$"{backOfficePath}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds"});
|
||||
cookieAuthOptions.Provider = cookieOptions.Provider;
|
||||
|
||||
//This is a custom middleware, we need to return the user's remaining logged in seconds
|
||||
app.Use<GetUserSecondsMiddleWare>(
|
||||
cookieAuthOptions,
|
||||
Current.Configs.Global(),
|
||||
Current.Configs.Security(),
|
||||
app.CreateLogger<GetUserSecondsMiddleWare>(),
|
||||
Current.HostingEnvironment);
|
||||
|
||||
//This is required so that we can read the auth ticket format outside of this pipeline
|
||||
app.CreatePerOwinContext<UmbracoAuthTicketDataProtector>(
|
||||
(options, context) => new UmbracoAuthTicketDataProtector(cookieOptions.TicketDataFormat));
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
private static bool _markerSet = false;
|
||||
|
||||
/// <summary>
|
||||
/// This registers the exact type of the user manager in owin so we can extract it
|
||||
/// when required in order to extract the user manager instance
|
||||
/// </summary>
|
||||
/// <typeparam name="TManager"></typeparam>
|
||||
/// <typeparam name="TUser"></typeparam>
|
||||
/// <param name="app"></param>
|
||||
/// <remarks>
|
||||
/// This is required because a developer can specify a custom user manager and due to generic types the key name will registered
|
||||
/// differently in the owin context
|
||||
/// </remarks>
|
||||
private static void SetBackOfficeUserManagerType<TManager, TUser>(this IAppBuilder app)
|
||||
where TManager : BackOfficeUserManager<TUser>
|
||||
where TUser : BackOfficeIdentityUser
|
||||
{
|
||||
if (_markerSet) throw new InvalidOperationException("The back office user manager marker has already been set, only one back office user manager can be configured");
|
||||
|
||||
//on each request set the user manager getter -
|
||||
// this is required purely because Microsoft.Owin.IOwinContext is super inflexible with it's Get since it can only be
|
||||
// a generic strongly typed instance
|
||||
app.Use((context, func) =>
|
||||
{
|
||||
context.Set(BackOfficeOwinUserManager.OwinMarkerKey, new BackOfficeUserManagerMarker<TManager, TUser>());
|
||||
return func();
|
||||
});
|
||||
}
|
||||
|
||||
private static void UseUmbracoBackOfficeCookieAuthenticationInternal(this IAppBuilder app, CookieAuthenticationOptions options, IRuntimeState runtimeState, IRequestCache requestCache, PipelineStage stage)
|
||||
{
|
||||
if (app == null) throw new ArgumentNullException(nameof(app));
|
||||
if (runtimeState == null) throw new ArgumentNullException(nameof(runtimeState));
|
||||
|
||||
//First the normal cookie middleware
|
||||
app.Use(typeof(CookieAuthenticationMiddleware), app, options);
|
||||
//don't apply if app is not ready
|
||||
if (runtimeState.Level == RuntimeLevel.Upgrade || runtimeState.Level == RuntimeLevel.Run)
|
||||
{
|
||||
//Then our custom middlewares
|
||||
app.Use(typeof(ForceRenewalCookieAuthenticationMiddleware), app, options, Current.UmbracoContextAccessor, requestCache);
|
||||
app.Use(typeof(FixWindowsAuthMiddlware));
|
||||
}
|
||||
|
||||
//Marks all of the above middlewares to execute on Authenticate
|
||||
app.UseStageMarker(stage);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the cookie middleware for validating external logins is assigned to the pipeline with the correct
|
||||
@@ -297,8 +75,6 @@ namespace Umbraco.Web.Security
|
||||
AuthenticationMode = AuthenticationMode.Passive,
|
||||
CookieName = Constants.Security.BackOfficeExternalCookieName,
|
||||
ExpireTimeSpan = TimeSpan.FromMinutes(5),
|
||||
//Custom cookie manager so we can filter requests
|
||||
CookieManager = new BackOfficeCookieManager(umbracoContextAccessor, runtimeState, hostingEnvironment, globalSettings, requestCache),
|
||||
CookiePath = "/",
|
||||
CookieSecure = globalSettings.UseHttps ? CookieSecureOption.Always : CookieSecureOption.SameAsRequest,
|
||||
CookieHttpOnly = true,
|
||||
@@ -353,8 +129,8 @@ namespace Umbraco.Web.Security
|
||||
{
|
||||
if (runtimeState.Level != RuntimeLevel.Run) return app;
|
||||
|
||||
var authOptions = app.CreateUmbracoCookieAuthOptions(umbracoContextAccessor, globalSettings, runtimeState, securitySettings, hostingEnvironment, requestCache);
|
||||
app.Use(typeof(PreviewAuthenticationMiddleware), authOptions, globalSettings, hostingEnvironment);
|
||||
//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,
|
||||
@@ -372,40 +148,6 @@ namespace Umbraco.Web.Security
|
||||
Thread.CurrentThread.SanitizeThreadCulture();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the default umb cookie auth options
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
/// <param name="umbracoContextAccessor"></param>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="runtimeState"></param>
|
||||
/// <param name="securitySettings"></param>
|
||||
/// <param name="hostingEnvironment"></param>
|
||||
/// <param name="requestCache"></param>
|
||||
/// <param name="explicitPaths"></param>
|
||||
/// <returns></returns>
|
||||
public static UmbracoBackOfficeCookieAuthOptions CreateUmbracoCookieAuthOptions(this IAppBuilder app,
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
IGlobalSettings globalSettings, IRuntimeState runtimeState, ISecuritySettings securitySettings, IHostingEnvironment hostingEnvironment, IRequestCache requestCache, string[] explicitPaths = null)
|
||||
{
|
||||
//this is how aspnet wires up the default AuthenticationTicket protector so we'll use the same code
|
||||
var ticketDataFormat = new TicketDataFormat(
|
||||
app.CreateDataProtector(typeof (CookieAuthenticationMiddleware).FullName,
|
||||
Constants.Security.BackOfficeAuthenticationType,
|
||||
"v1"));
|
||||
|
||||
var authOptions = new UmbracoBackOfficeCookieAuthOptions(
|
||||
explicitPaths,
|
||||
umbracoContextAccessor,
|
||||
securitySettings,
|
||||
globalSettings,
|
||||
hostingEnvironment,
|
||||
runtimeState,
|
||||
ticketDataFormat,
|
||||
requestCache);
|
||||
|
||||
return authOptions;
|
||||
}
|
||||
public static IAppBuilder CreatePerOwinContext<T>(this IAppBuilder app, Func<T> createCallback)
|
||||
where T : class, IDisposable
|
||||
{
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using Microsoft.Owin;
|
||||
using Microsoft.Owin.Infrastructure;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Security;
|
||||
|
||||
namespace Umbraco.Web.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// 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,
|
||||
/// instead we'll specify our own cookie manager and return null if the request isn't for an acceptable path.
|
||||
/// </remarks>
|
||||
internal class BackOfficeCookieManager : ChunkingCookieManager, Microsoft.Owin.Infrastructure.ICookieManager
|
||||
{
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly IRuntimeState _runtime;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IRequestCache _requestCache;
|
||||
private readonly string[] _explicitPaths;
|
||||
private readonly string _getRemainingSecondsPath;
|
||||
|
||||
public BackOfficeCookieManager(IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, IHostingEnvironment hostingEnvironment, IGlobalSettings globalSettings, IRequestCache requestCache)
|
||||
: this(umbracoContextAccessor, runtime, hostingEnvironment, globalSettings, requestCache, null)
|
||||
{ }
|
||||
|
||||
public BackOfficeCookieManager(IUmbracoContextAccessor umbracoContextAccessor, IRuntimeState runtime, IHostingEnvironment hostingEnvironment, IGlobalSettings globalSettings, IRequestCache requestCache, IEnumerable<string> explicitPaths)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor;
|
||||
_runtime = runtime;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_globalSettings = globalSettings;
|
||||
_requestCache = requestCache;
|
||||
_explicitPaths = explicitPaths?.ToArray();
|
||||
var backOfficePath = _globalSettings.GetBackOfficePath(_hostingEnvironment);
|
||||
_getRemainingSecondsPath = $"{backOfficePath}/backoffice/UmbracoApi/Authentication/GetRemainingTimeoutSeconds";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explicitly implement this so that we filter the request
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
string Microsoft.Owin.Infrastructure.ICookieManager.GetRequestCookie(IOwinContext context, string key)
|
||||
{
|
||||
if (_umbracoContextAccessor.UmbracoContext == null || context.Request.Uri.IsClientSideRequest())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ShouldAuthenticateRequest(
|
||||
context,
|
||||
_umbracoContextAccessor.UmbracoContext.OriginalRequestUrl) == false
|
||||
//Don't auth request, don't return a cookie
|
||||
? null
|
||||
//Return the default implementation
|
||||
: GetRequestCookie(context, key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if we should authenticate the request
|
||||
/// </summary>
|
||||
/// <param name="owinContext"></param>
|
||||
/// <param name="originalRequestUrl"></param>
|
||||
/// <param name="checkForceAuthTokens"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// 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>
|
||||
internal bool ShouldAuthenticateRequest(IOwinContext owinContext, Uri originalRequestUrl, bool checkForceAuthTokens = true)
|
||||
{
|
||||
// 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
|
||||
// when people have older invalid cookies on the same domain since our user managers might attempt to lookup a user
|
||||
// and we don't even have a db.
|
||||
// was: app.IsConfigured == false (equiv to !Run) && dbContext.IsDbConfigured == false (equiv to Install)
|
||||
// so, we handle .Install here and NOT .Upgrade
|
||||
if (_runtime.Level == RuntimeLevel.Install)
|
||||
return false;
|
||||
|
||||
var request = owinContext.Request;
|
||||
//check the explicit paths
|
||||
if (_explicitPaths != null)
|
||||
{
|
||||
return _explicitPaths.Any(x => x.InvariantEquals(request.Uri.AbsolutePath));
|
||||
}
|
||||
|
||||
//check user seconds path
|
||||
if (request.Uri.AbsolutePath.InvariantEquals(_getRemainingSecondsPath)) return false;
|
||||
|
||||
if (//check the explicit flag
|
||||
(checkForceAuthTokens && owinContext.Get<bool?>(Constants.Security.ForceReAuthFlag) != null)
|
||||
|| (checkForceAuthTokens && _requestCache.IsAvailable && _requestCache.Get(Constants.Security.ForceReAuthFlag) != null)
|
||||
//check back office
|
||||
|| request.Uri.IsBackOfficeRequest(_globalSettings, _hostingEnvironment)
|
||||
//check installer
|
||||
|| request.Uri.IsInstallerRequest(_hostingEnvironment))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -57,19 +57,19 @@ namespace Umbraco.Web.Security
|
||||
protected override Task ApplyResponseGrantAsync()
|
||||
{
|
||||
if (_umbracoContextAccessor.UmbracoContext == null || Context.Request.Uri.IsClientSideRequest())
|
||||
{
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
//Now we need to check if we should force renew this based on a flag in the context and whether this is a request that is not normally renewed by OWIN...
|
||||
// which means that it is not a normal URL that is authenticated.
|
||||
|
||||
var normalAuthUrl = ((BackOfficeCookieManager) Options.CookieManager)
|
||||
.ShouldAuthenticateRequest(Context, _umbracoContextAccessor.UmbracoContext.OriginalRequestUrl,
|
||||
//Pass in false, we want to know if this is a normal auth'd page
|
||||
checkForceAuthTokens: false);
|
||||
//This is auth'd normally, so OWIN will naturally take care of the cookie renewal
|
||||
if (normalAuthUrl) return Task.FromResult(0);
|
||||
//var normalAuthUrl = ((BackOfficeCookieManager) Options.CookieManager)
|
||||
// .ShouldAuthenticateRequest(Context, _umbracoContextAccessor.UmbracoContext.OriginalRequestUrl,
|
||||
// //Pass in false, we want to know if this is a normal auth'd page
|
||||
// checkForceAuthTokens: false);
|
||||
////This is auth'd normally, so OWIN will naturally take care of the cookie renewal
|
||||
//if (normalAuthUrl) return Task.FromResult(0);
|
||||
|
||||
//check for the special flag in either the owin or http context
|
||||
var shouldRenew = Context.Get<bool?>(Constants.Security.ForceReAuthFlag) != null || (_requestCache.IsAvailable && _requestCache.Get(Constants.Security.ForceReAuthFlag) != null);
|
||||
|
||||
@@ -18,34 +18,6 @@ namespace Umbraco.Web.Security
|
||||
{
|
||||
public int LoginTimeoutMinutes { get; }
|
||||
|
||||
public UmbracoBackOfficeCookieAuthOptions(
|
||||
string[] explicitPaths,
|
||||
IUmbracoContextAccessor umbracoContextAccessor,
|
||||
ISecuritySettings securitySettings,
|
||||
IGlobalSettings globalSettings,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IRuntimeState runtimeState,
|
||||
ISecureDataFormat<AuthenticationTicket> secureDataFormat,
|
||||
IRequestCache requestCache)
|
||||
{
|
||||
var secureDataFormat1 = secureDataFormat ?? throw new ArgumentNullException(nameof(secureDataFormat));
|
||||
LoginTimeoutMinutes = globalSettings.TimeOutInMinutes;
|
||||
AuthenticationType = Constants.Security.BackOfficeAuthenticationType;
|
||||
|
||||
SlidingExpiration = true;
|
||||
ExpireTimeSpan = TimeSpan.FromMinutes(LoginTimeoutMinutes);
|
||||
CookieDomain = securitySettings.AuthCookieDomain;
|
||||
CookieName = securitySettings.AuthCookieName;
|
||||
CookieHttpOnly = true;
|
||||
CookieSecure = globalSettings.UseHttps ? CookieSecureOption.Always : CookieSecureOption.SameAsRequest;
|
||||
CookiePath = "/";
|
||||
|
||||
TicketDataFormat = new UmbracoSecureDataFormat(LoginTimeoutMinutes, secureDataFormat1);
|
||||
|
||||
//Custom cookie manager so we can filter requests
|
||||
CookieManager = new BackOfficeCookieManager(umbracoContextAccessor, runtimeState, hostingEnvironment, globalSettings, requestCache, explicitPaths);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the cookie options for saving the auth cookie
|
||||
/// </summary>
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Web.Mvc;
|
||||
using System.Web.Routing;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.Routing;
|
||||
|
||||
namespace Umbraco.Web.Templates
|
||||
{
|
||||
/// <summary>
|
||||
/// This is used purely for the RenderTemplate functionality in Umbraco
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This allows you to render an MVC template based purely off of a node id and an optional alttemplate id as string output.
|
||||
/// </remarks>
|
||||
internal class TemplateRenderer : ITemplateRenderer
|
||||
{
|
||||
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
|
||||
private readonly IPublishedRouter _publishedRouter;
|
||||
private readonly IFileService _fileService;
|
||||
private readonly ILocalizationService _languageService;
|
||||
private readonly IWebRoutingSettings _webRoutingSettings;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public TemplateRenderer(IUmbracoContextAccessor umbracoContextAccessor, IPublishedRouter publishedRouter, IFileService fileService, ILocalizationService textService, IWebRoutingSettings webRoutingSettings, IShortStringHelper shortStringHelper, IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
|
||||
_publishedRouter = publishedRouter ?? throw new ArgumentNullException(nameof(publishedRouter));
|
||||
_fileService = fileService ?? throw new ArgumentNullException(nameof(fileService));
|
||||
_languageService = textService ?? throw new ArgumentNullException(nameof(textService));
|
||||
_webRoutingSettings = webRoutingSettings ?? throw new ArgumentNullException(nameof(webRoutingSettings));
|
||||
_shortStringHelper = shortStringHelper ?? throw new ArgumentNullException(nameof(shortStringHelper));
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public void Render(int pageId, int? altTemplateId, StringWriter writer)
|
||||
{
|
||||
if (writer == null) throw new ArgumentNullException(nameof(writer));
|
||||
|
||||
var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext();
|
||||
// instantiate a request and process
|
||||
// important to use CleanedUmbracoUrl - lowercase path-only version of the current url, though this isn't going to matter
|
||||
// terribly much for this implementation since we are just creating a doc content request to modify it's properties manually.
|
||||
var contentRequest = _publishedRouter.CreateRequest(umbracoContext);
|
||||
|
||||
var doc = contentRequest.UmbracoContext.Content.GetById(pageId);
|
||||
|
||||
if (doc == null)
|
||||
{
|
||||
writer.Write("<!-- Could not render template for Id {0}, the document was not found -->", pageId);
|
||||
return;
|
||||
}
|
||||
|
||||
//in some cases the UmbracoContext will not have a PublishedRequest assigned to it if we are not in the
|
||||
//execution of a front-end rendered page. In this case set the culture to the default.
|
||||
//set the culture to the same as is currently rendering
|
||||
if (umbracoContext.PublishedRequest == null)
|
||||
{
|
||||
var defaultLanguage = _languageService.GetAllLanguages().FirstOrDefault();
|
||||
contentRequest.Culture = defaultLanguage == null
|
||||
? CultureInfo.CurrentUICulture
|
||||
: defaultLanguage.CultureInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
contentRequest.Culture = umbracoContext.PublishedRequest.Culture;
|
||||
}
|
||||
|
||||
//set the doc that was found by id
|
||||
contentRequest.PublishedContent = doc;
|
||||
//set the template, either based on the AltTemplate found or the standard template of the doc
|
||||
var templateId = _webRoutingSettings.DisableAlternativeTemplates || !altTemplateId.HasValue
|
||||
? doc.TemplateId
|
||||
: altTemplateId.Value;
|
||||
if (templateId.HasValue)
|
||||
contentRequest.TemplateModel = _fileService.GetTemplate(templateId.Value);
|
||||
|
||||
//if there is not template then exit
|
||||
if (contentRequest.HasTemplate == false)
|
||||
{
|
||||
if (altTemplateId.HasValue == false)
|
||||
{
|
||||
writer.Write("<!-- Could not render template for Id {0}, the document's template was not found with id {0}-->", doc.TemplateId);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.Write("<!-- Could not render template for Id {0}, the altTemplate was not found with id {0}-->", altTemplateId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//First, save all of the items locally that we know are used in the chain of execution, we'll need to restore these
|
||||
//after this page has rendered.
|
||||
SaveExistingItems(out var oldPublishedRequest);
|
||||
|
||||
try
|
||||
{
|
||||
//set the new items on context objects for this templates execution
|
||||
SetNewItemsOnContextObjects(contentRequest);
|
||||
|
||||
//Render the template
|
||||
ExecuteTemplateRendering(writer, contentRequest);
|
||||
}
|
||||
finally
|
||||
{
|
||||
//restore items on context objects to continuing rendering the parent template
|
||||
RestoreItems(oldPublishedRequest);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void ExecuteTemplateRendering(TextWriter sw, IPublishedRequest request)
|
||||
{
|
||||
//NOTE: Before we used to build up the query strings here but this is not necessary because when we do a
|
||||
// Server.Execute in the TemplateRenderer, we pass in a 'true' to 'preserveForm' which automatically preserves all current
|
||||
// query strings so there's no need for this. HOWEVER, once we get MVC involved, we might have to do some fun things,
|
||||
// though this will happen in the TemplateRenderer.
|
||||
|
||||
//var queryString = _umbracoContext.HttpContext.Request.QueryString.AllKeys
|
||||
// .ToDictionary(key => key, key => context.Request.QueryString[key]);
|
||||
|
||||
var requestContext = new RequestContext(_httpContextAccessor.GetRequiredHttpContext(), new RouteData()
|
||||
{
|
||||
Route = RouteTable.Routes["Umbraco_default"]
|
||||
});
|
||||
var routeHandler = new RenderRouteHandler(_umbracoContextAccessor, ControllerBuilder.Current.GetControllerFactory(), _shortStringHelper);
|
||||
var routeDef = routeHandler.GetUmbracoRouteDefinition(requestContext, request);
|
||||
var renderModel = new ContentModel(request.PublishedContent);
|
||||
//manually add the action/controller, this is required by mvc
|
||||
requestContext.RouteData.Values.Add("action", routeDef.ActionName);
|
||||
requestContext.RouteData.Values.Add("controller", routeDef.ControllerName);
|
||||
//add the rest of the required route data
|
||||
routeHandler.SetupRouteDataForRequest(renderModel, requestContext, request);
|
||||
|
||||
var stringOutput = RenderUmbracoRequestToString(requestContext);
|
||||
|
||||
sw.Write(stringOutput);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will execute the UmbracoMvcHandler for the request specified and get the string output.
|
||||
/// </summary>
|
||||
/// <param name="requestContext">
|
||||
/// Assumes the RequestContext is setup specifically to render an Umbraco view.
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// To achieve this we temporarily change the output text writer of the current HttpResponse, then
|
||||
/// execute the controller via the handler which inevitably writes the result to the text writer
|
||||
/// that has been assigned to the response. Then we change the response textwriter back to the original
|
||||
/// before continuing .
|
||||
/// </remarks>
|
||||
private string RenderUmbracoRequestToString(RequestContext requestContext)
|
||||
{
|
||||
var currentWriter = requestContext.HttpContext.Response.Output;
|
||||
var newWriter = new StringWriter();
|
||||
requestContext.HttpContext.Response.Output = newWriter;
|
||||
|
||||
var handler = new UmbracoMvcHandler(requestContext);
|
||||
handler.ExecuteUmbracoRequest();
|
||||
|
||||
//reset it
|
||||
requestContext.HttpContext.Response.Output = currentWriter;
|
||||
return newWriter.ToString();
|
||||
}
|
||||
|
||||
private void SetNewItemsOnContextObjects(IPublishedRequest request)
|
||||
{
|
||||
//now, set the new ones for this page execution
|
||||
_umbracoContextAccessor.UmbracoContext.PublishedRequest = request;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save all items that we know are used for rendering execution to variables so we can restore after rendering
|
||||
/// </summary>
|
||||
private void SaveExistingItems(out IPublishedRequest oldPublishedRequest)
|
||||
{
|
||||
//Many objects require that these legacy items are in the http context items... before we render this template we need to first
|
||||
//save the values in them so that we can re-set them after we render so the rest of the execution works as per normal
|
||||
oldPublishedRequest = _umbracoContextAccessor.UmbracoContext.PublishedRequest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores all items back to their context's to continue normal page rendering execution
|
||||
/// </summary>
|
||||
private void RestoreItems(IPublishedRequest oldPublishedRequest)
|
||||
{
|
||||
_umbracoContextAccessor.UmbracoContext.PublishedRequest = oldPublishedRequest;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -319,7 +319,6 @@
|
||||
<Compile Include="Security\AppBuilderExtensions.cs" />
|
||||
<Compile Include="Security\AuthenticationOptionsExtensions.cs" />
|
||||
<Compile Include="Security\AuthenticationManagerExtensions.cs" />
|
||||
<Compile Include="Security\BackOfficeCookieManager.cs" />
|
||||
<Compile Include="Security\UmbracoBackOfficeCookieAuthOptions.cs" />
|
||||
<Compile Include="Trees\MenuRenderingEventArgs.cs" />
|
||||
<Compile Include="Trees\TreeControllerBase.cs" />
|
||||
@@ -328,7 +327,6 @@
|
||||
<Compile Include="WebApi\AngularJsonOnlyConfigurationAttribute.cs" />
|
||||
<Compile Include="Editors\Binders\MemberBinder.cs" />
|
||||
<Compile Include="WebApi\Filters\AngularAntiForgeryHelper.cs" />
|
||||
<Compile Include="WebApi\Filters\ClearAngularAntiForgeryTokenAttribute.cs" />
|
||||
<Compile Include="WebApi\Filters\DisableBrowserCacheAttribute.cs" />
|
||||
<Compile Include="WebApi\Filters\EnableOverrideAuthorizationAttribute.cs" />
|
||||
<Compile Include="WebApi\Filters\FilterGrouping.cs" />
|
||||
@@ -425,6 +423,8 @@
|
||||
<Compile Include="Mvc\UmbracoPageResult.cs" />
|
||||
<Compile Include="RouteCollectionExtensions.cs" />
|
||||
<Compile Include="Templates\TemplateRenderer.cs" />
|
||||
<Compile Include="Trees\PartialViewMacrosTreeController.cs" />
|
||||
<Compile Include="Trees\PartialViewsTreeController.cs" />
|
||||
<Compile Include="UmbracoHelper.cs" />
|
||||
<Compile Include="Mvc\ViewContextExtensions.cs" />
|
||||
<Compile Include="Mvc\ViewDataContainerExtensions.cs" />
|
||||
|
||||
@@ -63,7 +63,6 @@ namespace Umbraco.Web
|
||||
protected virtual void ConfigureServices(IAppBuilder app, ServiceContext services)
|
||||
{
|
||||
app.SetUmbracoLoggerFactory();
|
||||
ConfigureUmbracoUserManager(app);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -81,21 +80,6 @@ namespace Umbraco.Web
|
||||
.FinalizeMiddlewareConfiguration();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure the Identity user manager for use with Umbraco Back office
|
||||
/// </summary>
|
||||
/// <param name="app"></param>
|
||||
protected virtual void ConfigureUmbracoUserManager(IAppBuilder app)
|
||||
{
|
||||
// (EXPERT: an overload accepts a custom BackOfficeUserStore implementation)
|
||||
app.ConfigureUserManagerForUmbracoBackOffice(
|
||||
Services,
|
||||
GlobalSettings,
|
||||
Mapper,
|
||||
UserPasswordConfig,
|
||||
IpResolver);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure external/OAuth login providers
|
||||
/// </summary>
|
||||
@@ -105,7 +89,8 @@ namespace Umbraco.Web
|
||||
// Ensure owin is configured for Umbraco back office authentication.
|
||||
// Front-end OWIN cookie configuration must be declared after this code.
|
||||
app
|
||||
.UseUmbracoBackOfficeCookieAuthentication(UmbracoContextAccessor, RuntimeState, Services.UserService, GlobalSettings, SecuritySettings, HostingEnvironment, RequestCache, PipelineStage.Authenticate)
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ using Umbraco.Composing;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Dictionary;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Net;
|
||||
using Umbraco.Core.Templates;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Core.Xml;
|
||||
using Umbraco.Web.Mvc;
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Web.Http.Filters;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Clears the angular csrf cookie if the request was successful
|
||||
/// </summary>
|
||||
public sealed class ClearAngularAntiForgeryTokenAttribute : ActionFilterAttribute
|
||||
{
|
||||
public override void OnActionExecuted(HttpActionExecutedContext context)
|
||||
{
|
||||
if (context.Response == null) return;
|
||||
if (context.Response.IsSuccessStatusCode == false) return;
|
||||
|
||||
//remove the cookies
|
||||
var angularCookie = new CookieHeaderValue(Core.Constants.Web.AngularCookieName, "null")
|
||||
{
|
||||
Expires = DateTime.Now.AddYears(-1),
|
||||
//must be js readable
|
||||
HttpOnly = false,
|
||||
Path = "/"
|
||||
};
|
||||
var validationCookie = new CookieHeaderValue(Core.Constants.Web.CsrfValidationCookieName, "null")
|
||||
{
|
||||
Expires = DateTime.Now.AddYears(-1),
|
||||
HttpOnly = true,
|
||||
Path = "/"
|
||||
};
|
||||
context.Response.Headers.AddCookies(new[] { angularCookie, validationCookie });
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user