From 9818b4a14b5eaae110507c3c050363a545f1e7ee Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 20 May 2020 15:25:42 +1000 Subject: [PATCH] Moves what is possible for identity back office to Core, configures backoffice identity with IOptions and our password settings --- .../BackOffice/BackOfficeIdentityUser.cs | 0 .../BackOfficeUserPasswordCheckerResult.cs | 0 .../BackOffice}/ClaimsPrincipalExtensions.cs | 0 .../IBackOfficeUserPasswordChecker.cs | 0 .../BackOffice/IdentityAuditEventArgs.cs | 2 - .../BackOffice/IdentityMapDefinition.cs | 0 .../BackOffice/UmbracoBackOfficeIdentity.cs | 0 .../{Extensions => }/IdentityExtensions.cs | 0 .../BackOffice/UserLoginInfoWrapper.cs | 2 +- .../CoreMappingProfiles.cs | 11 +- .../Controllers/BackOfficeController.cs | 45 +++++- ...coBackOfficeServiceCollectionExtensions.cs | 64 ++++---- .../WebMappingProfiles.cs | 3 + .../Editors/BackOfficeController.cs | 145 +++++++++--------- 14 files changed, 170 insertions(+), 102 deletions(-) rename src/{Umbraco.Infrastructure => Umbraco.Core}/BackOffice/BackOfficeIdentityUser.cs (100%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/BackOffice/BackOfficeUserPasswordCheckerResult.cs (100%) rename src/{Umbraco.Infrastructure/BackOffice/Extensions => Umbraco.Core/BackOffice}/ClaimsPrincipalExtensions.cs (100%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/BackOffice/IBackOfficeUserPasswordChecker.cs (100%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/BackOffice/IdentityAuditEventArgs.cs (98%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/BackOffice/IdentityMapDefinition.cs (100%) rename src/{Umbraco.Infrastructure => Umbraco.Core}/BackOffice/UmbracoBackOfficeIdentity.cs (100%) rename src/Umbraco.Infrastructure/BackOffice/{Extensions => }/IdentityExtensions.cs (100%) diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityUser.cs b/src/Umbraco.Core/BackOffice/BackOfficeIdentityUser.cs similarity index 100% rename from src/Umbraco.Infrastructure/BackOffice/BackOfficeIdentityUser.cs rename to src/Umbraco.Core/BackOffice/BackOfficeIdentityUser.cs diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserPasswordCheckerResult.cs b/src/Umbraco.Core/BackOffice/BackOfficeUserPasswordCheckerResult.cs similarity index 100% rename from src/Umbraco.Infrastructure/BackOffice/BackOfficeUserPasswordCheckerResult.cs rename to src/Umbraco.Core/BackOffice/BackOfficeUserPasswordCheckerResult.cs diff --git a/src/Umbraco.Infrastructure/BackOffice/Extensions/ClaimsPrincipalExtensions.cs b/src/Umbraco.Core/BackOffice/ClaimsPrincipalExtensions.cs similarity index 100% rename from src/Umbraco.Infrastructure/BackOffice/Extensions/ClaimsPrincipalExtensions.cs rename to src/Umbraco.Core/BackOffice/ClaimsPrincipalExtensions.cs diff --git a/src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserPasswordChecker.cs b/src/Umbraco.Core/BackOffice/IBackOfficeUserPasswordChecker.cs similarity index 100% rename from src/Umbraco.Infrastructure/BackOffice/IBackOfficeUserPasswordChecker.cs rename to src/Umbraco.Core/BackOffice/IBackOfficeUserPasswordChecker.cs diff --git a/src/Umbraco.Infrastructure/BackOffice/IdentityAuditEventArgs.cs b/src/Umbraco.Core/BackOffice/IdentityAuditEventArgs.cs similarity index 98% rename from src/Umbraco.Infrastructure/BackOffice/IdentityAuditEventArgs.cs rename to src/Umbraco.Core/BackOffice/IdentityAuditEventArgs.cs index 1991f248f1..e99d9e51f5 100644 --- a/src/Umbraco.Infrastructure/BackOffice/IdentityAuditEventArgs.cs +++ b/src/Umbraco.Core/BackOffice/IdentityAuditEventArgs.cs @@ -1,6 +1,4 @@ using System; -using System.Threading; -using Umbraco.Extensions; namespace Umbraco.Core.BackOffice diff --git a/src/Umbraco.Infrastructure/BackOffice/IdentityMapDefinition.cs b/src/Umbraco.Core/BackOffice/IdentityMapDefinition.cs similarity index 100% rename from src/Umbraco.Infrastructure/BackOffice/IdentityMapDefinition.cs rename to src/Umbraco.Core/BackOffice/IdentityMapDefinition.cs diff --git a/src/Umbraco.Infrastructure/BackOffice/UmbracoBackOfficeIdentity.cs b/src/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentity.cs similarity index 100% rename from src/Umbraco.Infrastructure/BackOffice/UmbracoBackOfficeIdentity.cs rename to src/Umbraco.Core/BackOffice/UmbracoBackOfficeIdentity.cs diff --git a/src/Umbraco.Infrastructure/BackOffice/Extensions/IdentityExtensions.cs b/src/Umbraco.Infrastructure/BackOffice/IdentityExtensions.cs similarity index 100% rename from src/Umbraco.Infrastructure/BackOffice/Extensions/IdentityExtensions.cs rename to src/Umbraco.Infrastructure/BackOffice/IdentityExtensions.cs diff --git a/src/Umbraco.Infrastructure/BackOffice/UserLoginInfoWrapper.cs b/src/Umbraco.Infrastructure/BackOffice/UserLoginInfoWrapper.cs index a441d0299a..ab6af35519 100644 --- a/src/Umbraco.Infrastructure/BackOffice/UserLoginInfoWrapper.cs +++ b/src/Umbraco.Infrastructure/BackOffice/UserLoginInfoWrapper.cs @@ -3,7 +3,7 @@ using Umbraco.Core.Models.Identity; namespace Umbraco.Core.BackOffice { - public class UserLoginInfoWrapper : IUserLoginInfo + internal class UserLoginInfoWrapper : IUserLoginInfo { private readonly UserLoginInfo _info; diff --git a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/CoreMappingProfiles.cs b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/CoreMappingProfiles.cs index 9835d17fb6..0d713efe46 100644 --- a/src/Umbraco.Infrastructure/Composing/CompositionExtensions/CoreMappingProfiles.cs +++ b/src/Umbraco.Infrastructure/Composing/CompositionExtensions/CoreMappingProfiles.cs @@ -1,14 +1,23 @@ -using Umbraco.Core.Mapping; +using Umbraco.Core.BackOffice; +using Umbraco.Core.Mapping; namespace Umbraco.Core.Composing.CompositionExtensions { public static class CoreMappingProfiles { + /// + /// Registers the core Umbraco mapper definitions + /// + /// + /// public static Composition ComposeCoreMappingProfiles(this Composition composition) { composition.RegisterUnique(); + composition.WithCollectionBuilder() + .Add(); + return composition; } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index c5507f00bf..3055ad72d6 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -6,15 +6,18 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using Umbraco.Core; +using Umbraco.Core.BackOffice; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Grid; using Umbraco.Core.Hosting; using Umbraco.Core.Services; using Umbraco.Core.WebAssets; +using Umbraco.Extensions; using Umbraco.Net; using Umbraco.Web.BackOffice.ActionResults; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.ActionResults; +using Umbraco.Web.Models; using Umbraco.Web.WebAssets; using Constants = Umbraco.Core.Constants; @@ -24,6 +27,7 @@ namespace Umbraco.Web.BackOffice.Controllers [Area(Constants.Web.Mvc.BackOfficeArea)] public class BackOfficeController : Controller { + private readonly BackOfficeUserManager _userManager; private readonly IRuntimeMinifier _runtimeMinifier; private readonly IGlobalSettings _globalSettings; private readonly IHostingEnvironment _hostingEnvironment; @@ -32,8 +36,17 @@ namespace Umbraco.Web.BackOffice.Controllers private readonly ILocalizedTextService _textService; private readonly IGridConfig _gridConfig; - public BackOfficeController(IRuntimeMinifier runtimeMinifier, IGlobalSettings globalSettings, IHostingEnvironment hostingEnvironment, IUmbracoApplicationLifetime umbracoApplicationLifetime, IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService textService, IGridConfig gridConfig) + public BackOfficeController( + BackOfficeUserManager userManager, + IRuntimeMinifier runtimeMinifier, + IGlobalSettings globalSettings, + IHostingEnvironment hostingEnvironment, + IUmbracoApplicationLifetime umbracoApplicationLifetime, + IUmbracoContextAccessor umbracoContextAccessor, + ILocalizedTextService textService, + IGridConfig gridConfig) { + _userManager = userManager; _runtimeMinifier = runtimeMinifier; _globalSettings = globalSettings; _hostingEnvironment = hostingEnvironment; @@ -46,6 +59,7 @@ namespace Umbraco.Web.BackOffice.Controllers [HttpGet] public IActionResult Default() { + // TODO: Migrate this return View(); } @@ -109,5 +123,34 @@ namespace Umbraco.Web.BackOffice.Controllers { return new JsonNetResult { Data = _gridConfig.EditorsConfig.Editors, Formatting = Formatting.None }; } + + [HttpGet] + public async Task ValidatePasswordResetCode([Bind(Prefix = "u")]int userId, [Bind(Prefix = "r")]string resetCode) + { + var user = await _userManager.FindByIdAsync(userId.ToString()); + if (user != null) + { + var result = await _userManager.VerifyUserTokenAsync(user, "ResetPassword", "ResetPassword", resetCode); + if (result) + { + //Add a flag and redirect for it to be displayed + TempData[ViewDataExtensions.TokenPasswordResetCode] = new ValidatePasswordResetCodeModel { UserId = userId, ResetCode = resetCode }; + return RedirectToLocal(Url.Action("Default", "BackOffice")); + } + } + + //Add error and redirect for it to be displayed + TempData[ViewDataExtensions.TokenPasswordResetCode] = new[] { _textService.Localize("login/resetCodeExpired") }; + return RedirectToLocal(Url.Action("Default", "BackOffice")); + } + + private ActionResult RedirectToLocal(string returnUrl) + { + if (Url.IsLocalUrl(returnUrl)) + { + return Redirect(returnUrl); + } + return Redirect("/"); + } } } diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeServiceCollectionExtensions.cs index f3a9a528ae..f03230b00b 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeServiceCollectionExtensions.cs @@ -3,9 +3,10 @@ using System.Security.Claims; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.BackOffice; -using Umbraco.Core.Mapping; +using Umbraco.Core.Configuration; using Umbraco.Net; using Umbraco.Web.Common.AspNetCore; @@ -17,41 +18,52 @@ namespace Umbraco.Extensions { services.AddDataProtection(); - // UmbracoMapper - hack? - services.TryAddSingleton(); - services.TryAddSingleton(s => new MapDefinitionCollection(new[] {s.GetService()})); - services.TryAddSingleton(); - services.TryAddScoped(); - services.AddIdentityCore(options => - { - options.User.RequireUniqueEmail = true; - - // TODO: Configure password configuration - /*options.Password.RequiredLength = passwordConfiguration.RequiredLength; - options.Password.RequireNonAlphanumeric = passwordConfiguration.RequireNonLetterOrDigit; - options.Password.RequireDigit = passwordConfiguration.RequireDigit; - options.Password.RequireLowercase = passwordConfiguration.RequireLowercase; - options.Password.RequireUppercase = passwordConfiguration.RequireUppercase; - options.Lockout.MaxFailedAccessAttempts = passwordConfiguration.MaxFailedAccessAttemptsBeforeLockout;*/ - - options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier; - options.ClaimsIdentity.UserNameClaimType = ClaimTypes.Name; - options.ClaimsIdentity.RoleClaimType = ClaimTypes.Role; - options.ClaimsIdentity.SecurityStampClaimType = Constants.Web.SecurityStampClaimType; - - options.Lockout.AllowedForNewUsers = true; - options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromDays(30); - }) + services + .AddIdentityCore() .AddDefaultTokenProviders() .AddUserStore() .AddUserManager() .AddClaimsPrincipalFactory>(); + services.ConfigureOptions(); services.AddScoped(); services.TryAddScoped>(); } + + /// + /// Used to configure for the Umbraco Back office + /// + private class UmbracoBackOfficeIdentityOptions : IConfigureOptions + { + private readonly IUserPasswordConfiguration _userPasswordConfiguration; + + public UmbracoBackOfficeIdentityOptions(IUserPasswordConfiguration userPasswordConfiguration) + { + _userPasswordConfiguration = userPasswordConfiguration; + } + + public void Configure(IdentityOptions options) + { + options.User.RequireUniqueEmail = true; + options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier; + options.ClaimsIdentity.UserNameClaimType = ClaimTypes.Name; + options.ClaimsIdentity.RoleClaimType = ClaimTypes.Role; + options.ClaimsIdentity.SecurityStampClaimType = Constants.Web.SecurityStampClaimType; + options.Lockout.AllowedForNewUsers = true; + options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromDays(30); + + options.Password.RequiredLength = _userPasswordConfiguration.RequiredLength; + options.Password.RequireNonAlphanumeric = _userPasswordConfiguration.RequireNonLetterOrDigit; + options.Password.RequireDigit = _userPasswordConfiguration.RequireDigit; + options.Password.RequireLowercase = _userPasswordConfiguration.RequireLowercase; + options.Password.RequireUppercase = _userPasswordConfiguration.RequireUppercase; + options.Lockout.MaxFailedAccessAttempts = _userPasswordConfiguration.MaxFailedAccessAttemptsBeforeLockout; + } + } + + } } diff --git a/src/Umbraco.Web/Composing/CompositionExtensions/WebMappingProfiles.cs b/src/Umbraco.Web/Composing/CompositionExtensions/WebMappingProfiles.cs index 44d86df59c..8ef601a290 100644 --- a/src/Umbraco.Web/Composing/CompositionExtensions/WebMappingProfiles.cs +++ b/src/Umbraco.Web/Composing/CompositionExtensions/WebMappingProfiles.cs @@ -10,6 +10,9 @@ namespace Umbraco.Web.Composing.CompositionExtensions { public static Composition ComposeWebMappingProfiles(this Composition composition) { + // TODO: All/Most of these should be moved to ComposeCoreMappingProfiles which requires All/most of the + // definitions to be moved to core + composition.WithCollectionBuilder() .Add() .Add() diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index a8bdba8b40..64b2f70e42 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -183,65 +183,68 @@ namespace Umbraco.Web.Editors () => Redirect("/")); } - /// - /// Get the json localized text for a given culture or the culture for the current user - /// - /// - /// - /// Migrated already to .Net Core - [HttpGet] - public JsonNetResult LocalizedText(string culture = null) - { - var cultureInfo = string.IsNullOrWhiteSpace(culture) - //if the user is logged in, get their culture, otherwise default to 'en' - ? Security.IsAuthenticated() - //current culture is set at the very beginning of each request - ? Thread.CurrentThread.CurrentCulture - : CultureInfo.GetCultureInfo(GlobalSettings.DefaultUILanguage) - : CultureInfo.GetCultureInfo(culture); - var allValues = Services.TextService.GetAllStoredValues(cultureInfo); - var pathedValues = allValues.Select(kv => - { - var slashIndex = kv.Key.IndexOf('/'); - var areaAlias = kv.Key.Substring(0, slashIndex); - var valueAlias = kv.Key.Substring(slashIndex+1); - return new - { - areaAlias, - valueAlias, - value = kv.Value - }; - }); + // NOTE: Migrated + ///// + ///// Get the json localized text for a given culture or the culture for the current user + ///// + ///// + ///// + ///// Migrated already to .Net Core + //[HttpGet] + //public JsonNetResult LocalizedText(string culture = null) + //{ + // var cultureInfo = string.IsNullOrWhiteSpace(culture) + // //if the user is logged in, get their culture, otherwise default to 'en' + // ? Security.IsAuthenticated() + // //current culture is set at the very beginning of each request + // ? Thread.CurrentThread.CurrentCulture + // : CultureInfo.GetCultureInfo(GlobalSettings.DefaultUILanguage) + // : CultureInfo.GetCultureInfo(culture); - Dictionary> nestedDictionary = pathedValues - .GroupBy(pv => pv.areaAlias) - .ToDictionary(pv => pv.Key, pv => - pv.ToDictionary(pve => pve.valueAlias, pve => pve.value)); + // var allValues = Services.TextService.GetAllStoredValues(cultureInfo); + // var pathedValues = allValues.Select(kv => + // { + // var slashIndex = kv.Key.IndexOf('/'); + // var areaAlias = kv.Key.Substring(0, slashIndex); + // var valueAlias = kv.Key.Substring(slashIndex+1); + // return new + // { + // areaAlias, + // valueAlias, + // value = kv.Value + // }; + // }); - return new JsonNetResult { Data = nestedDictionary, Formatting = Formatting.None }; - } + // Dictionary> nestedDictionary = pathedValues + // .GroupBy(pv => pv.areaAlias) + // .ToDictionary(pv => pv.Key, pv => + // pv.ToDictionary(pve => pve.valueAlias, pve => pve.value)); - /// - /// Returns the JavaScript main file including all references found in manifests - /// - /// - [MinifyJavaScriptResult(Order = 0)] - [OutputCache(Order = 1, VaryByParam = "none", Location = OutputCacheLocation.Server, Duration = 5000)] - public async Task Application() - { - var result = await _runtimeMinifier.GetScriptForLoadingBackOfficeAsync(GlobalSettings, _hostingEnvironment); + // return new JsonNetResult { Data = nestedDictionary, Formatting = Formatting.None }; + //} - return JavaScript(result); - } + // NOTE: Migrated + ///// + ///// Returns the JavaScript main file including all references found in manifests + ///// + ///// + //[MinifyJavaScriptResult(Order = 0)] + //[OutputCache(Order = 1, VaryByParam = "none", Location = OutputCacheLocation.Server, Duration = 5000)] + //public async Task Application() + //{ + // var result = await _runtimeMinifier.GetScriptForLoadingBackOfficeAsync(GlobalSettings, _hostingEnvironment); - /// Migrated already to .Net Core - [UmbracoAuthorize(Order = 0)] - [HttpGet] - public JsonNetResult GetGridConfig() - { - return new JsonNetResult { Data = _gridConfig.EditorsConfig.Editors, Formatting = Formatting.None }; - } + // return JavaScript(result); + //} + + // NOTE: Migrated + //[UmbracoAuthorize(Order = 0)] + //[HttpGet] + //public JsonNetResult GetGridConfig() + //{ + // return new JsonNetResult { Data = _gridConfig.EditorsConfig.Editors, Formatting = Formatting.None }; + //} @@ -290,25 +293,25 @@ namespace Umbraco.Web.Editors User.Identity.GetUserId()); } - [HttpGet] - public async Task ValidatePasswordResetCode([Bind(Prefix = "u")]int userId, [Bind(Prefix = "r")]string resetCode) - { - var user = await UserManager.FindByIdAsync(userId.ToString()); - if (user != null) - { - var result = await UserManager.VerifyUserTokenAsync(user, "ResetPassword", "ResetPassword", resetCode); - if (result) - { - //Add a flag and redirect for it to be displayed - TempData[ViewDataExtensions.TokenPasswordResetCode] = new ValidatePasswordResetCodeModel { UserId = userId, ResetCode = resetCode }; - return RedirectToLocal(Url.Action("Default", "BackOffice")); - } - } + //[HttpGet] + //public async Task ValidatePasswordResetCode([Bind(Prefix = "u")]int userId, [Bind(Prefix = "r")]string resetCode) + //{ + // var user = await UserManager.FindByIdAsync(userId.ToString()); + // if (user != null) + // { + // var result = await UserManager.VerifyUserTokenAsync(user, "ResetPassword", "ResetPassword", resetCode); + // if (result) + // { + // //Add a flag and redirect for it to be displayed + // TempData[ViewDataExtensions.TokenPasswordResetCode] = new ValidatePasswordResetCodeModel { UserId = userId, ResetCode = resetCode }; + // return RedirectToLocal(Url.Action("Default", "BackOffice")); + // } + // } - //Add error and redirect for it to be displayed - TempData[ViewDataExtensions.TokenPasswordResetCode] = new[] { Services.TextService.Localize("login/resetCodeExpired") }; - return RedirectToLocal(Url.Action("Default", "BackOffice")); - } + // //Add error and redirect for it to be displayed + // TempData[ViewDataExtensions.TokenPasswordResetCode] = new[] { Services.TextService.Localize("login/resetCodeExpired") }; + // return RedirectToLocal(Url.Action("Default", "BackOffice")); + //} [HttpGet] public async Task ExternalLinkLoginCallback()