Merge remote-tracking branch 'origin/netcore/netcore' into netcore/feature/migrate-remaining-trees

This commit is contained in:
Bjarke Berg
2020-06-17 18:05:40 +02:00
100 changed files with 1336 additions and 2141 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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/"))
{

View File

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

View File

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

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

View File

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

View File

@@ -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()
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
using NUnit.Framework;
using Umbraco.Core;
namespace Umbraco.Tests.CoreThings
namespace Umbraco.Tests.UnitTests.Umbraco.Core
{
public class GuidUtilsTests
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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)
{
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 () {

View File

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

View File

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

View File

@@ -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']",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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