From b331d683ecb8d665837d48bffd75e855ad52ce3e Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 24 Nov 2020 11:33:46 +0100 Subject: [PATCH 1/9] Migrated member related partial views along with necessary methods from MembershipHelper into IUmbracoWebsiteSecurity. --- .../Models/Security}/LoginStatusModel.cs | 3 +- .../Security/IUmbracoWebsiteSecurity.cs | 20 ++ .../Security/UmbracoWebsiteSecurityTests.cs | 99 +++++++++ .../Umbraco.Web.UI.NetCore.csproj | 4 + .../Templates/EditProfile.cshtml | 16 +- .../PartialViewMacros/Templates/Login.cshtml | 17 +- .../Templates/LoginStatus.cshtml | 14 +- .../Templates/RegisterMember.cshtml | 15 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 35 +--- .../Extensions/HtmlHelperRenderExtensions.cs | 80 ++++--- .../Extensions/TypeLoaderExtensions.cs | 1 - .../Security/UmbracoWebsiteSecurity.cs | 196 +++++++++++++++++- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 13 files changed, 381 insertions(+), 120 deletions(-) rename src/{Umbraco.Web/Models => Umbraco.Core/Models/Security}/LoginStatusModel.cs (96%) create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Security/UmbracoWebsiteSecurityTests.cs rename src/{Umbraco.Web.UI/Umbraco => Umbraco.Web.UI.NetCore/umbraco}/PartialViewMacros/Templates/EditProfile.cshtml (83%) rename src/{Umbraco.Web.UI/Umbraco => Umbraco.Web.UI.NetCore/umbraco}/PartialViewMacros/Templates/Login.cshtml (75%) rename src/{Umbraco.Web.UI/Umbraco => Umbraco.Web.UI.NetCore/umbraco}/PartialViewMacros/Templates/LoginStatus.cshtml (63%) rename src/{Umbraco.Web.UI/Umbraco => Umbraco.Web.UI.NetCore/umbraco}/PartialViewMacros/Templates/RegisterMember.cshtml (92%) diff --git a/src/Umbraco.Web/Models/LoginStatusModel.cs b/src/Umbraco.Core/Models/Security/LoginStatusModel.cs similarity index 96% rename from src/Umbraco.Web/Models/LoginStatusModel.cs rename to src/Umbraco.Core/Models/Security/LoginStatusModel.cs index 4b699bab38..3978a84334 100644 --- a/src/Umbraco.Web/Models/LoginStatusModel.cs +++ b/src/Umbraco.Core/Models/Security/LoginStatusModel.cs @@ -1,7 +1,6 @@ using System.ComponentModel.DataAnnotations; - -namespace Umbraco.Web.Models +namespace Umbraco.Core.Models.Security { /// /// The model representing the status of a logged in member. diff --git a/src/Umbraco.Core/Security/IUmbracoWebsiteSecurity.cs b/src/Umbraco.Core/Security/IUmbracoWebsiteSecurity.cs index c302d45354..00124c4dce 100644 --- a/src/Umbraco.Core/Security/IUmbracoWebsiteSecurity.cs +++ b/src/Umbraco.Core/Security/IUmbracoWebsiteSecurity.cs @@ -6,6 +6,13 @@ namespace Umbraco.Core.Security { public interface IUmbracoWebsiteSecurity { + /// + /// Creates a model to use for registering new members with custom member properties + /// + /// Alias of member type for created member (default used if not provided). + /// Instance of + RegisterModel CreateRegistrationModel(string memberTypeAlias = null); + /// /// Registers a new member. /// @@ -14,6 +21,13 @@ namespace Umbraco.Core.Security /// Result of registration operation. Task RegisterMemberAsync(RegisterModel model, bool logMemberIn = true); + /// + /// Creates a new profile model filled in with the current members details if they are logged in which allows for editing + /// profile properties. + /// + /// Instance of + Task GetCurrentMemberProfileModelAsync(); + /// /// Updates the currently logged in member's profile. /// @@ -35,6 +49,12 @@ namespace Umbraco.Core.Security /// True if logged in, false if not. bool IsLoggedIn(); + /// + /// Returns the login status model of the currently logged in member. + /// + /// Instance of + Task GetCurrentLoginStatusAsync(); + /// /// Logs out the current member. /// diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Security/UmbracoWebsiteSecurityTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Security/UmbracoWebsiteSecurityTests.cs new file mode 100644 index 0000000000..d021d38c15 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Security/UmbracoWebsiteSecurityTests.cs @@ -0,0 +1,99 @@ +using System.Linq; +using System.Security.Claims; +using System.Security.Principal; +using Microsoft.AspNetCore.Http; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Security; +using Umbraco.Core.Security; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; +using Umbraco.Tests.Common.Builders; +using Umbraco.Web.Website.Security; +using CoreConstants = Umbraco.Core.Constants; + +namespace Umbraco.Tests.UnitTests.Umbraco.Web.Website.Security +{ + [TestFixture] + public class UmbracoWebsiteSecurityTests + { + [Test] + public void Can_Create_Registration_Model_With_Default_Member_Type() + { + var sut = CreateUmbracoWebsiteSecurity(); + + var result = sut.CreateRegistrationModel(); + AssertRegisterModel(result); + } + + [Test] + public void Can_Create_Registration_Model_With_Custom_Member_Type() + { + const string Alias = "testAlias"; + var sut = CreateUmbracoWebsiteSecurity(Alias); + + var result = sut.CreateRegistrationModel(Alias); + AssertRegisterModel(result, Alias); + } + + [Test] + public void Can_Detected_Logged_In_User() + { + var sut = CreateUmbracoWebsiteSecurity(); + + var result = sut.IsLoggedIn(); + Assert.IsTrue(result); + } + + [Test] + public void Can_Detected_Anonymous_User() + { + var sut = CreateUmbracoWebsiteSecurity(isUserAuthenticated: false); + + var result = sut.IsLoggedIn(); + Assert.IsFalse(result); + } + + private static void AssertRegisterModel(RegisterModel result, string memberTypeAlias = CoreConstants.Conventions.MemberTypes.DefaultAlias) + { + Assert.AreEqual(memberTypeAlias, result.MemberTypeAlias); + Assert.AreEqual(1, result.MemberProperties.Count); + + var firstProperty = result.MemberProperties.First(); + Assert.AreEqual("title", firstProperty.Alias); + Assert.AreEqual("Title", firstProperty.Name); + Assert.AreEqual(string.Empty, firstProperty.Value); + } + + private IUmbracoWebsiteSecurity CreateUmbracoWebsiteSecurity(string memberTypeAlias = CoreConstants.Conventions.MemberTypes.DefaultAlias, bool isUserAuthenticated = true) + { + var mockHttpContextAccessor = new Mock(); + var mockHttpContext = new Mock(); + var mockIdentity = new Mock(); + mockIdentity.SetupGet(x => x.IsAuthenticated).Returns(isUserAuthenticated); + var mockPrincipal = new Mock(); + mockPrincipal.Setup(x => x.Identity).Returns(mockIdentity.Object); + mockHttpContext.Setup(m => m.User).Returns(mockPrincipal.Object); + mockHttpContextAccessor.SetupGet(x => x.HttpContext).Returns(mockHttpContext.Object); + + var mockMemberService = new Mock(); + + var mockMemberTypeService = new Mock(); + mockMemberTypeService + .Setup(x => x.Get(It.Is(y => y == memberTypeAlias))) + .Returns(CreateSimpleMemberType(memberTypeAlias)); + + var mockShortStringHelper = new Mock(); + + return new UmbracoWebsiteSecurity(mockHttpContextAccessor.Object, mockMemberService.Object, mockMemberTypeService.Object, mockShortStringHelper.Object); + } + + private IMemberType CreateSimpleMemberType(string alias) + { + var memberType = MemberTypeBuilder.CreateSimpleMemberType(alias); + memberType.SetMemberCanEditProperty("title", true); + return memberType; + } + } +} diff --git a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj index 6cc92f18da..1c199f1e0a 100644 --- a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj +++ b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj @@ -68,6 +68,10 @@ + + + + diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/EditProfile.cshtml b/src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/EditProfile.cshtml similarity index 83% rename from src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/EditProfile.cshtml rename to src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/EditProfile.cshtml index 576541ea4a..d7639ff09f 100644 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/EditProfile.cshtml +++ b/src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/EditProfile.cshtml @@ -1,14 +1,12 @@ -@using System.Web.Mvc.Html -@using Umbraco.Web -@using Umbraco.Web.Composing -@using Umbraco.Web.Controllers -@inherits Umbraco.Web.Macros.PartialViewMacroPage +@using Umbraco.Core.Security +@using Umbraco.Extensions +@using Umbraco.Web.Website.Controllers +@inherits Umbraco.Web.Common.Macros.PartialViewMacroPage +@inject IUmbracoWebsiteSecurityAccessor UmbracoWebsiteSecurityAccessor @{ - var profileModel = Current.MembershipHelper.GetCurrentMemberProfileModel(); + var profileModel = await UmbracoWebsiteSecurityAccessor.WebsiteSecurity.GetCurrentMemberProfileModelAsync(); - Html.EnableClientValidation(); - Html.EnableUnobtrusiveJavaScript(); var success = TempData["ProfileUpdateSuccess"] != null; } @@ -17,7 +15,7 @@ -@if (Current.MembershipHelper.IsLoggedIn() && profileModel != null) +@if (UmbracoWebsiteSecurityAccessor.WebsiteSecurity.IsLoggedIn() && profileModel != null) { if (success) { diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Login.cshtml b/src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/Login.cshtml similarity index 75% rename from src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Login.cshtml rename to src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/Login.cshtml index b50d1ac806..bd4c9e1e59 100644 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Login.cshtml +++ b/src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/Login.cshtml @@ -1,17 +1,12 @@ -@using System.Web.Mvc.Html -@using Umbraco.Web -@using Umbraco.Web.Composing -@using Umbraco.Web.Models -@using Umbraco.Web.Controllers -@inherits Umbraco.Web.Macros.PartialViewMacroPage +@using Microsoft.AspNetCore.Http.Extensions +@using Umbraco.Core.Models.Security +@using Umbraco.Extensions +@using Umbraco.Web.Website.Controllers +@inherits Umbraco.Web.Common.Macros.PartialViewMacroPage @{ var loginModel = new LoginModel(); - loginModel.RedirectUrl = HttpContext.Current.Request.Url.AbsolutePath; - - Html.EnableClientValidation(); - Html.EnableUnobtrusiveJavaScript(); - + loginModel.RedirectUrl = Context.Request.GetDisplayUrl(); } diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/LoginStatus.cshtml b/src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/LoginStatus.cshtml similarity index 63% rename from src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/LoginStatus.cshtml rename to src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/LoginStatus.cshtml index 78b06151af..350f573ade 100644 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/LoginStatus.cshtml +++ b/src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/LoginStatus.cshtml @@ -1,12 +1,12 @@ -@using System.Web.Mvc.Html -@using Umbraco.Web -@using Umbraco.Web.Composing -@using Umbraco.Web.Models -@using Umbraco.Web.Controllers -@inherits Umbraco.Web.Macros.PartialViewMacroPage +@using Umbraco.Core.Security +@using Umbraco.Core.Models.Security +@using Umbraco.Extensions +@using Umbraco.Web.Website.Controllers +@inherits Umbraco.Web.Common.Macros.PartialViewMacroPage +@inject IUmbracoWebsiteSecurityAccessor UmbracoWebsiteSecurityAccessor @{ - var loginStatusModel = Current.MembershipHelper.GetCurrentLoginStatus(); + var loginStatusModel = await UmbracoWebsiteSecurityAccessor.WebsiteSecurity.GetCurrentLoginStatusAsync(); var logoutModel = new PostRedirectModel(); @* diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml b/src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/RegisterMember.cshtml similarity index 92% rename from src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml rename to src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/RegisterMember.cshtml index 5e6230a294..9bbf630a21 100644 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/RegisterMember.cshtml +++ b/src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/RegisterMember.cshtml @@ -1,8 +1,8 @@ -@using System.Web.Mvc.Html -@using Umbraco.Web -@using Umbraco.Web.Composing -@using Umbraco.Web.Controllers -@inherits Umbraco.Web.Macros.PartialViewMacroPage +@using Umbraco.Core.Security +@using Umbraco.Extensions +@using Umbraco.Web.Website.Controllers +@inherits Umbraco.Web.Common.Macros.PartialViewMacroPage +@inject IUmbracoWebsiteSecurityAccessor UmbracoWebsiteSecurityAccessor @{ @* @@ -12,7 +12,7 @@ var registerModel = Members.CreateRegistrationModel("Custom Member"); *@ - var registerModel = Current.MembershipHelper.CreateRegistrationModel(); + var registerModel = UmbracoWebsiteSecurityAccessor.WebsiteSecurity.CreateRegistrationModel(); @* Configurable here: @@ -31,9 +31,6 @@ @Html.ValidationMessageFor(m => registerModel.Username) *@ - Html.EnableClientValidation(); - Html.EnableUnobtrusiveJavaScript(); - var success = TempData["FormSuccess"] != null; } diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 93f13e73e0..c413712b22 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -78,9 +78,7 @@ - - - + @@ -178,22 +176,6 @@ Designer - - true - PreserveNewest - - - true - PreserveNewest - - - true - PreserveNewest - - - true - PreserveNewest - Designer @@ -257,21 +239,8 @@ False True - 9000 - / - http://localhost:9000/ - http://localhost:8700 - 8610 - / - http://localhost:8610 - http://localhost:8700 - 8800 - 8900 8910 / - http://localhost:8800 - http://localhost:8700 - http://localhost:8900 http://localhost:8910 False False @@ -354,4 +323,4 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs index a48ed435bf..b58fbc7f53 100644 --- a/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs @@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; -using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Umbraco.Core; using Umbraco.Core.Cache; @@ -214,7 +213,7 @@ namespace Umbraco.Extensions // /// // /// // /// - // public static IHtmlContent Action(this HtmlHelper htmlHelper, string actionName) + // public static IHtmlContent Action(this IHtmlHelper htmlHelper, string actionName) // where T : SurfaceController // { // return htmlHelper.Action(actionName, typeof(T)); @@ -274,7 +273,6 @@ namespace Umbraco.Extensions /// /// /// - /// /// public UmbracoForm( ViewContext viewContext, @@ -282,19 +280,15 @@ namespace Umbraco.Extensions string controllerName, string controllerAction, string area, - FormMethod method, object additionalRouteVals = null) : base(viewContext, htmlEncoder) { _viewContext = viewContext; - _method = method; _controllerName = controllerName; _encryptedString = EncryptionHelper.CreateEncryptedRouteString(GetRequiredService(viewContext), controllerName, controllerAction, area, additionalRouteVals); } - private readonly ViewContext _viewContext; - private readonly FormMethod _method; private bool _disposed; private readonly string _encryptedString; private readonly string _controllerName; @@ -330,7 +324,7 @@ namespace Umbraco.Extensions /// Name of the controller. /// The method. /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, FormMethod method) + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, string controllerName, FormMethod method) { return html.BeginUmbracoForm(action, controllerName, null, new Dictionary(), method); } @@ -342,7 +336,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName) + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, string controllerName) { return html.BeginUmbracoForm(action, controllerName, null, new Dictionary()); } @@ -356,7 +350,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, object additionalRouteVals, FormMethod method) + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, string controllerName, object additionalRouteVals, FormMethod method) { return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, new Dictionary(), method); } @@ -369,7 +363,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, object additionalRouteVals) + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, string controllerName, object additionalRouteVals) { return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, new Dictionary()); } @@ -384,7 +378,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, string controllerName, object additionalRouteVals, object htmlAttributes, FormMethod method) @@ -401,7 +395,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, string controllerName, object additionalRouteVals, object htmlAttributes) { @@ -418,7 +412,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, string controllerName, object additionalRouteVals, IDictionary htmlAttributes, FormMethod method) @@ -440,7 +434,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, string controllerName, object additionalRouteVals, IDictionary htmlAttributes) { @@ -460,7 +454,7 @@ namespace Umbraco.Extensions /// The surface controller to route to /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, FormMethod method) + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, Type surfaceType, FormMethod method) { return html.BeginUmbracoForm(action, surfaceType, null, new Dictionary(), method); } @@ -472,7 +466,7 @@ namespace Umbraco.Extensions /// /// The surface controller to route to /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType) + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, Type surfaceType) { return html.BeginUmbracoForm(action, surfaceType, null, new Dictionary()); } @@ -485,7 +479,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, FormMethod method) + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, FormMethod method) where T : SurfaceController { return html.BeginUmbracoForm(action, typeof(T), method); @@ -498,7 +492,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action) + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action) where T : SurfaceController { return html.BeginUmbracoForm(action, typeof(T)); @@ -513,7 +507,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, Type surfaceType, object additionalRouteVals, FormMethod method) { return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, new Dictionary(), method); @@ -527,7 +521,7 @@ namespace Umbraco.Extensions /// The surface controller to route to /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, Type surfaceType, object additionalRouteVals) { return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, new Dictionary()); @@ -542,7 +536,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, object additionalRouteVals, FormMethod method) + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, object additionalRouteVals, FormMethod method) where T : SurfaceController { return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, method); @@ -556,7 +550,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, object additionalRouteVals) + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, object additionalRouteVals) where T : SurfaceController { return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals); @@ -572,7 +566,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, Type surfaceType, object additionalRouteVals, object htmlAttributes, FormMethod method) @@ -589,7 +583,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, Type surfaceType, object additionalRouteVals, object htmlAttributes) { @@ -606,7 +600,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, object additionalRouteVals, object htmlAttributes, FormMethod method) @@ -624,7 +618,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, object additionalRouteVals, object htmlAttributes) where T : SurfaceController @@ -642,7 +636,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, Type surfaceType, object additionalRouteVals, IDictionary htmlAttributes, FormMethod method) @@ -652,18 +646,19 @@ namespace Umbraco.Extensions if (string.IsNullOrWhiteSpace(action)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(action)); if (surfaceType == null) throw new ArgumentNullException(nameof(surfaceType)); - var area = ""; - var surfaceControllerTypeCollection = GetRequiredService(html); var surfaceController = surfaceControllerTypeCollection.SingleOrDefault(x => x == surfaceType); if (surfaceController == null) throw new InvalidOperationException("Could not find the surface controller of type " + surfaceType.FullName); var metaData = PluginController.GetMetadata(surfaceController); + + var area = string.Empty; if (metaData.AreaName.IsNullOrWhiteSpace() == false) { - //set the area to the plugin area + // Set the area to the plugin area area = metaData.AreaName; } + return html.BeginUmbracoForm(action, metaData.ControllerName, area, additionalRouteVals, htmlAttributes, method); } @@ -676,7 +671,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, Type surfaceType, object additionalRouteVals, IDictionary htmlAttributes) { @@ -693,7 +688,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, object additionalRouteVals, IDictionary htmlAttributes, FormMethod method) @@ -711,7 +706,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, object additionalRouteVals, IDictionary htmlAttributes) where T : SurfaceController @@ -728,7 +723,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, FormMethod method) + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, string controllerName, string area, FormMethod method) { return html.BeginUmbracoForm(action, controllerName, area, null, new Dictionary(), method); } @@ -741,7 +736,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area) + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, string controllerName, string area) { return html.BeginUmbracoForm(action, controllerName, area, null, new Dictionary()); } @@ -757,7 +752,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, string controllerName, string area, object additionalRouteVals, IDictionary htmlAttributes, FormMethod method) @@ -782,7 +777,7 @@ namespace Umbraco.Extensions /// /// /// - public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, + public static MvcForm BeginUmbracoForm(this IHtmlHelper html, string action, string controllerName, string area, object additionalRouteVals, IDictionary htmlAttributes) { @@ -804,7 +799,7 @@ namespace Umbraco.Extensions /// /// This code is pretty much the same as the underlying MVC code that writes out the form /// - private static MvcForm RenderForm(this HtmlHelper htmlHelper, + private static MvcForm RenderForm(this IHtmlHelper htmlHelper, string formAction, FormMethod method, IDictionary htmlAttributes, @@ -837,12 +832,13 @@ namespace Umbraco.Extensions var htmlEncoder = GetRequiredService(htmlHelper); //new UmbracoForm: - var theForm = new UmbracoForm(htmlHelper.ViewContext, htmlEncoder, surfaceController, surfaceAction, area, method, additionalRouteVals); + var theForm = new UmbracoForm(htmlHelper.ViewContext, htmlEncoder, surfaceController, surfaceAction, area, additionalRouteVals); if (traditionalJavascriptEnabled) { htmlHelper.ViewContext.FormContext.FormData["FormId"] = tagBuilder.Attributes["id"]; } + return theForm; } @@ -859,7 +855,7 @@ namespace Umbraco.Extensions /// /// The HTML encoded value. /// - public static IHtmlContent If(this HtmlHelper html, bool test, string valueIfTrue) + public static IHtmlContent If(this IHtmlHelper html, bool test, string valueIfTrue) { return If(html, test, valueIfTrue, string.Empty); } @@ -874,7 +870,7 @@ namespace Umbraco.Extensions /// /// The HTML encoded value. /// - public static IHtmlContent If(this HtmlHelper html, bool test, string valueIfTrue, string valueIfFalse) + public static IHtmlContent If(this IHtmlHelper html, bool test, string valueIfTrue, string valueIfFalse) { return new HtmlString(HttpUtility.HtmlEncode(test ? valueIfTrue : valueIfFalse)); } diff --git a/src/Umbraco.Web.Website/Extensions/TypeLoaderExtensions.cs b/src/Umbraco.Web.Website/Extensions/TypeLoaderExtensions.cs index c01bdf7804..cdaa40ef6a 100644 --- a/src/Umbraco.Web.Website/Extensions/TypeLoaderExtensions.cs +++ b/src/Umbraco.Web.Website/Extensions/TypeLoaderExtensions.cs @@ -4,7 +4,6 @@ using Umbraco.Core.Composing; using Umbraco.Web.Common.Controllers; using Umbraco.Web.Website.Controllers; - namespace Umbraco.Extensions { /// diff --git a/src/Umbraco.Web.Website/Security/UmbracoWebsiteSecurity.cs b/src/Umbraco.Web.Website/Security/UmbracoWebsiteSecurity.cs index 90e80537ec..d110cf9661 100644 --- a/src/Umbraco.Web.Website/Security/UmbracoWebsiteSecurity.cs +++ b/src/Umbraco.Web.Website/Security/UmbracoWebsiteSecurity.cs @@ -1,32 +1,173 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Http; +using Umbraco.Core; +using Umbraco.Core.Models; using Umbraco.Core.Models.Security; using Umbraco.Core.Security; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; +using Umbraco.Web.Models; namespace Umbraco.Web.Website.Security { public class UmbracoWebsiteSecurity : IUmbracoWebsiteSecurity { private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IMemberService _memberService; + private readonly IMemberTypeService _memberTypeService; + private readonly IShortStringHelper _shortStringHelper; - public UmbracoWebsiteSecurity(IHttpContextAccessor httpContextAccessor) + public UmbracoWebsiteSecurity(IHttpContextAccessor httpContextAccessor, + IMemberService memberService, + IMemberTypeService memberTypeService, + IShortStringHelper shortStringHelper) { _httpContextAccessor = httpContextAccessor; + _memberService = memberService; + _memberTypeService = memberTypeService; + _shortStringHelper = shortStringHelper; } /// + public RegisterModel CreateRegistrationModel(string memberTypeAlias = null) + { + var providedOrDefaultMemberTypeAlias = memberTypeAlias ?? Constants.Conventions.MemberTypes.DefaultAlias; + var memberType = _memberTypeService.Get(providedOrDefaultMemberTypeAlias); + if (memberType == null) + { + throw new InvalidOperationException($"Could not find a member type with alias: {providedOrDefaultMemberTypeAlias}."); + } + + var model = RegisterModel.CreateModel(); + model.MemberTypeAlias = providedOrDefaultMemberTypeAlias; + model.MemberProperties = GetMemberPropertiesViewModel(memberType); + return model; + } + + private List GetMemberPropertiesViewModel(IMemberType memberType, IMember member = null) + { + var viewProperties = new List(); + + var builtIns = ConventionsHelper.GetStandardPropertyTypeStubs(_shortStringHelper).Select(x => x.Key).ToArray(); + + var propertyTypes = memberType.PropertyTypes + .Where(x => builtIns.Contains(x.Alias) == false && memberType.MemberCanEditProperty(x.Alias)) + .OrderBy(p => p.SortOrder); + + foreach (var prop in propertyTypes) + { + var value = string.Empty; + if (member != null) + { + var propValue = member.Properties[prop.Alias]; + if (propValue != null && propValue.GetValue() != null) + { + value = propValue.GetValue().ToString(); + } + } + + var viewProperty = new UmbracoProperty + { + Alias = prop.Alias, + Name = prop.Name, + Value = value + }; + + // TODO: Perhaps one day we'll ship with our own EditorTempates but for now developers + // can just render their own. + + ////This is a rudimentary check to see what data template we should render + //// if developers want to change the template they can do so dynamically in their views or controllers + //// for a given property. + ////These are the default built-in MVC template types: “Boolean”, “Decimal”, “EmailAddress”, “HiddenInput”, “HTML”, “Object”, “String”, “Text”, and “Url” + //// by default we'll render a text box since we've defined that metadata on the UmbracoProperty.Value property directly. + //if (prop.DataTypeId == new Guid(Constants.PropertyEditors.TrueFalse)) + //{ + // viewProperty.EditorTemplate = "UmbracoBoolean"; + //} + //else + //{ + // switch (prop.DataTypeDatabaseType) + // { + // case DataTypeDatabaseType.Integer: + // viewProperty.EditorTemplate = "Decimal"; + // break; + // case DataTypeDatabaseType.Ntext: + // viewProperty.EditorTemplate = "Text"; + // break; + // case DataTypeDatabaseType.Date: + // case DataTypeDatabaseType.Nvarchar: + // break; + // } + //} + + viewProperties.Add(viewProperty); + } + + return viewProperties; + } + public Task RegisterMemberAsync(RegisterModel model, bool logMemberIn = true) { throw new System.NotImplementedException(); } + /// + public async Task GetCurrentMemberProfileModelAsync() + { + if (IsLoggedIn() == false) + { + return null; + } + + var member = GetCurrentPersistedMember(); + + // This shouldn't happen but will if the member is deleted in the back office while the member is trying + // to use the front-end! + if (member == null) + { + // Log them out since they've been removed + await LogOutAsync(); + + return null; + } + + var model = new ProfileModel + { + Name = member.Name, + MemberTypeAlias = member.ContentTypeAlias, + + // TODO: get ASP.NET Core Identity equiavlant of MemberShipUser in order to get common membership properties such as Email + // and UserName (see MembershipProviderExtensions.GetCurrentUserName()for legacy membership provider implementation). + + //Email = membershipUser.Email, + //UserName = membershipUser.UserName, + //Comment = membershipUser.Comment, + //IsApproved = membershipUser.IsApproved, + //IsLockedOut = membershipUser.IsLockedOut, + //LastLockoutDate = membershipUser.LastLockoutDate, + //CreationDate = membershipUser.CreationDate, + //LastLoginDate = membershipUser.LastLoginDate, + //LastActivityDate = membershipUser.LastActivityDate, + //LastPasswordChangedDate = membershipUser.LastPasswordChangedDate + }; + + var memberType = _memberTypeService.Get(member.ContentTypeId); + + model.MemberProperties = GetMemberPropertiesViewModel(memberType, member); + + return model; + } + /// public Task UpdateMemberProfileAsync(ProfileModel model) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } /// @@ -36,10 +177,55 @@ namespace Umbraco.Web.Website.Security return httpContext?.User != null && httpContext.User.Identity.IsAuthenticated; } + /// + public async Task GetCurrentLoginStatusAsync() + { + var model = LoginStatusModel.CreateModel(); + + if (IsLoggedIn() == false) + { + model.IsLoggedIn = false; + return model; + } + + var member = GetCurrentPersistedMember(); + + // This shouldn't happen but will if the member is deleted in the back office while the member is trying + // to use the front-end! + if (member == null) + { + // Log them out since they've been removed. + await LogOutAsync(); + model.IsLoggedIn = false; + return model; + } + + model.Name = member.Name; + model.Username = member.Username; + model.Email = member.Email; + model.IsLoggedIn = true; + + return model; + } + + /// + /// Returns the currently logged in IMember object - this should never be exposed to the front-end since it's returning a business logic entity! + /// + /// + private IMember GetCurrentPersistedMember() + { + // TODO: get user name from ASP.NET Core Identity (see MembershipProviderExtensions.GetCurrentUserName() + // for legacy membership provider implementation). + var username = ""; + + // The result of this is cached by the MemberRepository + return _memberService.GetByUsername(username); + } + /// public Task LoginAsync(string username, string password) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } /// @@ -51,7 +237,7 @@ namespace Umbraco.Web.Website.Security /// public bool IsMemberAuthorized(IEnumerable allowTypes = null, IEnumerable allowGroups = null, IEnumerable allowMembers = null) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 8157a90715..456088c8cd 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -235,7 +235,6 @@ - From 41f3dae9c897cfb891fcecd2ba84c1b217c2f12c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 24 Nov 2020 19:49:46 +0100 Subject: [PATCH 2/9] Fix paths in LogviewerTests --- .../Umbraco.Infrastructure/Logging/LogviewerTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs index 2b78714997..6a0fde5116 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs @@ -42,12 +42,12 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Logging var loggingConfiguration = TestHelper.GetLoggingConfiguration(hostingEnv); - var exampleLogfilePath = Path.Combine(testRoot, @"TestHelpers\Assets\", _logfileName); + var exampleLogfilePath = Path.Combine(testRoot, "TestHelpers","Assets", _logfileName); _newLogfileDirPath = loggingConfiguration.LogDirectory; _newLogfilePath = Path.Combine(_newLogfileDirPath, _logfileName); - var exampleSearchfilePath = Path.Combine(testRoot, @"TestHelpers\Assets\", _searchfileName); - _newSearchfileDirPath = Path.Combine(hostingEnv.ApplicationPhysicalPath, @"Config\"); + var exampleSearchfilePath = Path.Combine(testRoot, "TestHelpers","Assets", _searchfileName); + _newSearchfileDirPath = Path.Combine(hostingEnv.ApplicationPhysicalPath, @"Config"); _newSearchfilePath = Path.Combine(_newSearchfileDirPath, _searchfileName); //Create/ensure Directory exists From 7f8ed7b2e3206b58d767dc3fec5c10847d3b13c2 Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Wed, 25 Nov 2020 05:11:31 +0000 Subject: [PATCH 3/9] Resolve naming issues from #9415 Note that I removed the comment around funky namespace without changing namespace, dead comment from 2016. --- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- ...ensions.cs => UmbracoBuilderExtensions.cs} | 21 +------------------ 2 files changed, 2 insertions(+), 21 deletions(-) rename src/Umbraco.Web/{CompositionExtensions.cs => UmbracoBuilderExtensions.cs} (81%) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index ae2c1b8fb6..34383cf8cf 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -184,7 +184,7 @@ - + diff --git a/src/Umbraco.Web/CompositionExtensions.cs b/src/Umbraco.Web/UmbracoBuilderExtensions.cs similarity index 81% rename from src/Umbraco.Web/CompositionExtensions.cs rename to src/Umbraco.Web/UmbracoBuilderExtensions.cs index 957172ccff..422d0a8b23 100644 --- a/src/Umbraco.Web/CompositionExtensions.cs +++ b/src/Umbraco.Web/UmbracoBuilderExtensions.cs @@ -2,33 +2,14 @@ using Microsoft.Extensions.DependencyInjection; using Umbraco.Core; using Umbraco.Core.Builder; -using Umbraco.Core.Composing; -using Umbraco.Web.Actions; -using Umbraco.Web.ContentApps; -using Umbraco.Web.Dashboards; -using Umbraco.Web.Editors; -using Umbraco.Web.HealthCheck; -using Umbraco.Web.Media.EmbedProviders; -using Umbraco.Web.Mvc; -using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; -using Umbraco.Web.Search; -using Umbraco.Web.Sections; -using Umbraco.Web.Tour; -using Umbraco.Web.Trees; -using Current = Umbraco.Web.Composing.Current; -// the namespace here is intentional - although defined in Umbraco.Web assembly, -// this class should be visible when using Umbraco.Core.Components, alongside -// Umbraco.Core's own IUmbracoBuilderExtensions class - -// ReSharper disable once CheckNamespace namespace Umbraco.Web { /// /// Provides extension methods to the class. /// - public static class WebIUmbracoBuilderExtensions + public static class UmbracoBuilderExtensions { [Obsolete("This extension method exists only to ease migration, please refactor")] public static IServiceProvider CreateServiceProvider(this IUmbracoBuilder builder) From 0b7f7e4f37d3b778d37cb91a28dd7c4744570284 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 25 Nov 2020 07:22:32 +0100 Subject: [PATCH 4/9] Use temp path for test Signed-off-by: Bjarke Berg --- .../HostedServices/TempFileCleanupTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/TempFileCleanupTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/TempFileCleanupTests.cs index 7feda1e9da..2fcc618397 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/TempFileCleanupTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/TempFileCleanupTests.cs @@ -15,7 +15,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices public class TempFileCleanupTests { private Mock _mockIOHelper; - private string _testPath = @"c:\test\temp\path"; + private string _testPath = Path.GetTempPath(); [Test] public async Task Does_Not_Execute_When_Not_Main_Dom() From 1e2ad85718ebe3e06d6d2bc76db0f0f35d864a25 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 25 Nov 2020 07:31:39 +0100 Subject: [PATCH 5/9] Fix test for linux: config folder is lowercase Signed-off-by: Bjarke Berg --- .../Umbraco.Infrastructure/Logging/LogviewerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs index 6a0fde5116..183e87e5ca 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Logging/LogviewerTests.cs @@ -47,7 +47,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Logging _newLogfilePath = Path.Combine(_newLogfileDirPath, _logfileName); var exampleSearchfilePath = Path.Combine(testRoot, "TestHelpers","Assets", _searchfileName); - _newSearchfileDirPath = Path.Combine(hostingEnv.ApplicationPhysicalPath, @"Config"); + _newSearchfileDirPath = Path.Combine(hostingEnv.ApplicationPhysicalPath, @"config"); _newSearchfilePath = Path.Combine(_newSearchfileDirPath, _searchfileName); //Create/ensure Directory exists From a117d6be925a0b090d40ca5ce1fe4223a946270d Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 25 Nov 2020 07:40:56 +0100 Subject: [PATCH 6/9] Fix test for linux: use temp folder inside content root Signed-off-by: Bjarke Berg --- .../HostedServices/TempFileCleanupTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/TempFileCleanupTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/TempFileCleanupTests.cs index 2fcc618397..98666ece2f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/TempFileCleanupTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/HostedServices/TempFileCleanupTests.cs @@ -15,7 +15,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.HostedServices public class TempFileCleanupTests { private Mock _mockIOHelper; - private string _testPath = Path.GetTempPath(); + private string _testPath = Path.Combine(TestContext.CurrentContext.TestDirectory.Split("bin")[0], "App_Data", "TEMP"); [Test] public async Task Does_Not_Execute_When_Not_Main_Dom() From 667c00ccd4d3373650b28af9c072f4335b3c924a Mon Sep 17 00:00:00 2001 From: Paul Johnson Date: Wed, 25 Nov 2020 10:42:09 +0000 Subject: [PATCH 7/9] Hotfix for PublishedSnapshotService initialization --- .../PublishedSnapshotService.cs | 57 ++++++++++++------- .../Services/EntityServiceTests.cs | 5 +- .../PublishedContent/NuCacheChildrenTests.cs | 4 ++ .../PublishedContent/NuCacheTests.cs | 5 ++ .../Scoping/ScopedNuCacheTests.cs | 10 +++- 5 files changed, 58 insertions(+), 23 deletions(-) diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs index 688515ad33..2b80b32b71 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs @@ -3,19 +3,14 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using CSharpTest.Net.Collections; using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Cache; -using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Hosting; -using Umbraco.Core.Install; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -30,8 +25,8 @@ using Umbraco.Core.Services; using Umbraco.Core.Services.Changes; using Umbraco.Core.Services.Implement; using Umbraco.Core.Strings; +using Umbraco.Net; using Umbraco.Web.Cache; -using Umbraco.Web.Install; using Umbraco.Web.PublishedCache.NuCache.DataSource; using Umbraco.Web.Routing; using File = System.IO.File; @@ -40,6 +35,10 @@ namespace Umbraco.Web.PublishedCache.NuCache { internal class PublishedSnapshotService : PublishedSnapshotServiceBase { + private readonly PublishedSnapshotServiceOptions _options; + private readonly IMainDom _mainDom; + private readonly IUmbracoApplicationLifetime _lifeTime; + private readonly IRuntimeState _runtime; private readonly ServiceContext _serviceContext; private readonly IPublishedContentTypeFactory _publishedContentTypeFactory; private readonly IProfilingLogger _profilingLogger; @@ -63,9 +62,9 @@ namespace Umbraco.Web.PublishedCache.NuCache // volatile because we read it with no lock private volatile bool _isReady; - private readonly ContentStore _contentStore; - private readonly ContentStore _mediaStore; - private readonly SnapDictionary _domainStore; + private ContentStore _contentStore; + private ContentStore _mediaStore; + private SnapDictionary _domainStore; private readonly object _storesLock = new object(); private readonly object _elementsLock = new object(); @@ -84,13 +83,21 @@ namespace Umbraco.Web.PublishedCache.NuCache //private static int _singletonCheck; - public PublishedSnapshotService(PublishedSnapshotServiceOptions options, IMainDom mainDom, IRuntimeState runtime, - ServiceContext serviceContext, IPublishedContentTypeFactory publishedContentTypeFactory, - IPublishedSnapshotAccessor publishedSnapshotAccessor, IVariationContextAccessor variationContextAccessor, + public PublishedSnapshotService( + PublishedSnapshotServiceOptions options, + IMainDom mainDom, + IUmbracoApplicationLifetime lifeTime, + IRuntimeState runtime, + ServiceContext serviceContext, + IPublishedContentTypeFactory publishedContentTypeFactory, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IVariationContextAccessor variationContextAccessor, IProfilingLogger profilingLogger, ILoggerFactory loggerFactory, IScopeProvider scopeProvider, - IDocumentRepository documentRepository, IMediaRepository mediaRepository, IMemberRepository memberRepository, + IDocumentRepository documentRepository, + IMediaRepository mediaRepository, + IMemberRepository memberRepository, IDefaultCultureAccessor defaultCultureAccessor, IDataSource dataSource, IOptions globalSettings, @@ -106,6 +113,10 @@ namespace Umbraco.Web.PublishedCache.NuCache //if (Interlocked.Increment(ref _singletonCheck) > 1) // throw new Exception("Singleton must be instantiated only once!"); + _options = options; + _mainDom = mainDom; + _lifeTime = lifeTime; + _runtime = runtime; _serviceContext = serviceContext; _publishedContentTypeFactory = publishedContentTypeFactory; _profilingLogger = profilingLogger; @@ -134,12 +145,17 @@ namespace Umbraco.Web.PublishedCache.NuCache // (ideally we'd have Upgrading.App vs Upgrading.Data application states...) InitializeRepositoryEvents(); + _lifeTime.ApplicationInit += OnApplicationInit; + } + + internal void OnApplicationInit(object sender, EventArgs e) + { // however, the cache is NOT available until we are configured, because loading // content (and content types) from database cannot be consistent (see notes in "Handle // Notifications" region), so // - notifications will be ignored // - trying to obtain a published snapshot from the service will throw - if (runtime.Level != RuntimeLevel.Run) + if (_runtime.Level != RuntimeLevel.Run) return; // lock this entire call, we only want a single thread to be accessing the stores at once and within @@ -148,25 +164,25 @@ namespace Umbraco.Web.PublishedCache.NuCache // it will not be able to close the stores until we are done populating (if the store is empty) lock (_storesLock) { - if (options.IgnoreLocalDb == false) + if (!_options.IgnoreLocalDb) { - var registered = mainDom.Register(MainDomRegister, MainDomRelease); + _mainDom.Register(MainDomRegister, MainDomRelease); // stores are created with a db so they can write to it, but they do not read from it, // stores need to be populated, happens in OnResolutionFrozen which uses _localDbExists to // figure out whether it can read the databases or it should populate them from sql _logger.LogInformation("Creating the content store, localContentDbExists? {LocalContentDbExists}", _localContentDbExists); - _contentStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, _loggerFactory.CreateLogger("ContentStore"), _loggerFactory, publishedModelFactory, _localContentDb); + _contentStore = new ContentStore(PublishedSnapshotAccessor, VariationContextAccessor, _loggerFactory.CreateLogger("ContentStore"), _loggerFactory, _publishedModelFactory, _localContentDb); _logger.LogInformation("Creating the media store, localMediaDbExists? {LocalMediaDbExists}", _localMediaDbExists); - _mediaStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, _loggerFactory.CreateLogger("ContentStore"), _loggerFactory, publishedModelFactory, _localMediaDb); + _mediaStore = new ContentStore(PublishedSnapshotAccessor, VariationContextAccessor, _loggerFactory.CreateLogger("ContentStore"), _loggerFactory, _publishedModelFactory, _localMediaDb); } else { _logger.LogInformation("Creating the content store (local db ignored)"); - _contentStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, _loggerFactory.CreateLogger("ContentStore"), _loggerFactory, publishedModelFactory); + _contentStore = new ContentStore(PublishedSnapshotAccessor, VariationContextAccessor, _loggerFactory.CreateLogger("ContentStore"), _loggerFactory, _publishedModelFactory); _logger.LogInformation("Creating the media store (local db ignored)"); - _mediaStore = new ContentStore(publishedSnapshotAccessor, variationContextAccessor, _loggerFactory.CreateLogger("ContentStore"), _loggerFactory, publishedModelFactory); + _mediaStore = new ContentStore(PublishedSnapshotAccessor, VariationContextAccessor, _loggerFactory.CreateLogger("ContentStore"), _loggerFactory, _publishedModelFactory); } _domainStore = new SnapDictionary(); @@ -330,6 +346,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override void Dispose() { TearDownRepositoryEvents(); + _lifeTime.ApplicationInit -= OnApplicationInit; base.Dispose(); } diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs index ecb3292727..b7ab3ceea1 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/EntityServiceTests.cs @@ -9,10 +9,12 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence; using Umbraco.Core.Services; +using Umbraco.Net; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Integration.Testing; using Umbraco.Tests.Testing; using Umbraco.Web.PublishedCache; +using Umbraco.Web.PublishedCache.NuCache; namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services { @@ -42,7 +44,8 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services { //This is super nasty, but this lets us initialize the cache while it is empty. - _ = GetRequiredService(); + var publishedSnapshotService = GetRequiredService() as PublishedSnapshotService; + publishedSnapshotService?.OnApplicationInit(null, EventArgs.Empty); if (_langFr == null && _langEs == null) { diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index 78cae13b13..a17eb71ab0 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -32,6 +32,7 @@ using Umbraco.Web.PublishedCache.NuCache; using Umbraco.Web.PublishedCache.NuCache.DataSource; using Current = Umbraco.Web.Composing.Current; using Umbraco.Core.Serialization; +using Umbraco.Net; namespace Umbraco.Tests.PublishedContent { @@ -147,8 +148,10 @@ namespace Umbraco.Tests.PublishedContent // at last, create the complete NuCache snapshot service! var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; + var lifetime = new Mock(); _snapshotService = new PublishedSnapshotService(options, null, + lifetime.Object, runtime, serviceContext, contentTypeFactory, @@ -173,6 +176,7 @@ namespace Umbraco.Tests.PublishedContent // invariant is the current default _variationAccesor.VariationContext = new VariationContext(); + lifetime.Raise(e => e.ApplicationInit += null, EventArgs.Empty); Mock.Get(factory).Setup(x => x.GetService(typeof(IVariationContextAccessor))).Returns(_variationAccesor); } diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs index af2f738cf7..d689215081 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheTests.cs @@ -21,6 +21,7 @@ using Umbraco.Core.Serialization; using Umbraco.Core.Services; using Umbraco.Core.Services.Changes; using Umbraco.Core.Strings; +using Umbraco.Net; using Umbraco.Tests.Common; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing.Objects; @@ -188,8 +189,10 @@ namespace Umbraco.Tests.PublishedContent // at last, create the complete NuCache snapshot service! var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true }; + var lifetime = new Mock(); _snapshotService = new PublishedSnapshotService(options, null, + lifetime.Object, runtime, serviceContext, contentTypeFactory, @@ -212,6 +215,8 @@ namespace Umbraco.Tests.PublishedContent TestHelper.IOHelper, Microsoft.Extensions.Options.Options.Create(nuCacheSettings)); + lifetime.Raise(e => e.ApplicationInit += null, EventArgs.Empty); + // invariant is the current default _variationAccesor.VariationContext = new VariationContext(); diff --git a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs index f32ce9d9e1..1a8e485634 100644 --- a/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs @@ -17,6 +17,7 @@ using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; using Umbraco.Core.Strings; using Umbraco.Core.Sync; +using Umbraco.Net; using Umbraco.Tests.Common; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; @@ -83,10 +84,11 @@ namespace Umbraco.Tests.Scoping var typeFinder = TestHelper.GetTypeFinder(); var nuCacheSettings = new NuCacheSettings(); - - return new PublishedSnapshotService( + var lifetime = new Mock(); + var snapshotService = new PublishedSnapshotService( options, null, + lifetime.Object, runtimeStateMock.Object, ServiceContext, contentTypeFactory, @@ -106,6 +108,10 @@ namespace Umbraco.Tests.Scoping Mock.Of(), IOHelper, Microsoft.Extensions.Options.Options.Create(nuCacheSettings)); + + lifetime.Raise(e => e.ApplicationInit += null, EventArgs.Empty); + + return snapshotService; } protected IUmbracoContext GetUmbracoContextNu(string url, RouteData routeData = null, bool setSingleton = false) From 642c4550249b0c02bc43fe71f2aea9903537c3d2 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 25 Nov 2020 13:45:31 +0100 Subject: [PATCH 8/9] Reuse UmbracoWebsiteSecurityAccessor.WebsiteSecurity and align with other templates, by saving in variable even if it is just used once. + Signed-off-by: Bjarke Berg --- .../umbraco/PartialViewMacros/Templates/EditProfile.cshtml | 5 +++-- .../umbraco/PartialViewMacros/Templates/LoginStatus.cshtml | 3 ++- .../PartialViewMacros/Templates/RegisterMember.cshtml | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/EditProfile.cshtml b/src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/EditProfile.cshtml index d7639ff09f..de5f3167b0 100644 --- a/src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/EditProfile.cshtml +++ b/src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/EditProfile.cshtml @@ -5,7 +5,8 @@ @inject IUmbracoWebsiteSecurityAccessor UmbracoWebsiteSecurityAccessor @{ - var profileModel = await UmbracoWebsiteSecurityAccessor.WebsiteSecurity.GetCurrentMemberProfileModelAsync(); + var websiteSecurity = UmbracoWebsiteSecurityAccessor.WebsiteSecurity; + var profileModel = await websiteSecurity.GetCurrentMemberProfileModelAsync(); var success = TempData["ProfileUpdateSuccess"] != null; @@ -15,7 +16,7 @@ -@if (UmbracoWebsiteSecurityAccessor.WebsiteSecurity.IsLoggedIn() && profileModel != null) +@if (websiteSecurity.IsLoggedIn() && profileModel != null) { if (success) { diff --git a/src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/LoginStatus.cshtml b/src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/LoginStatus.cshtml index 350f573ade..72f5e814c8 100644 --- a/src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/LoginStatus.cshtml +++ b/src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/LoginStatus.cshtml @@ -6,7 +6,8 @@ @inject IUmbracoWebsiteSecurityAccessor UmbracoWebsiteSecurityAccessor @{ - var loginStatusModel = await UmbracoWebsiteSecurityAccessor.WebsiteSecurity.GetCurrentLoginStatusAsync(); + var websiteSecurity = UmbracoWebsiteSecurityAccessor.WebsiteSecurity; + var loginStatusModel = await websiteSecurity.GetCurrentLoginStatusAsync(); var logoutModel = new PostRedirectModel(); @* diff --git a/src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/RegisterMember.cshtml b/src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/RegisterMember.cshtml index 9bbf630a21..0876e64b08 100644 --- a/src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/RegisterMember.cshtml +++ b/src/Umbraco.Web.UI.NetCore/umbraco/PartialViewMacros/Templates/RegisterMember.cshtml @@ -12,7 +12,8 @@ var registerModel = Members.CreateRegistrationModel("Custom Member"); *@ - var registerModel = UmbracoWebsiteSecurityAccessor.WebsiteSecurity.CreateRegistrationModel(); + var websiteSecurity = UmbracoWebsiteSecurityAccessor.WebsiteSecurity; + var registerModel = websiteSecurity.CreateRegistrationModel(); @* Configurable here: From 2534a32b9f6e7ebbe34f06d51881271445880f46 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 25 Nov 2020 13:47:51 +0100 Subject: [PATCH 9/9] Handle all PartialViewMacros Templates as content + Aligned directory seperators Signed-off-by: Bjarke Berg --- .../Umbraco.Web.UI.NetCore.csproj | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj index 1c199f1e0a..9bd49b2999 100644 --- a/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj +++ b/src/Umbraco.Web.UI.NetCore/Umbraco.Web.UI.NetCore.csproj @@ -59,19 +59,15 @@ - - - - - - - - - - - - - + + + + + + + + +