From d9c2e5f55c3295d91729130fa5c4d6d8a2340b47 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 4 Aug 2020 14:30:42 +1000 Subject: [PATCH] Migrates remaining non-oauth actions on back office controller to netcore --- .../Controllers/BackOfficeController.cs | 93 +++++++++++++++- .../UmbracoBackOffice/AuthorizeUpgrade.cshtml | 73 ++++++++++++ .../Editors/BackOfficeController.cs | 105 +----------------- 3 files changed, 164 insertions(+), 107 deletions(-) create mode 100644 src/Umbraco.Web.UI.NetCore/Umbraco/UmbracoBackOffice/AuthorizeUpgrade.cshtml diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index c17e977951..867cff2f56 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -13,14 +13,17 @@ using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Grid; using Umbraco.Core.Hosting; +using Umbraco.Core.Logging; using Umbraco.Core.Services; using Umbraco.Core.WebAssets; using Umbraco.Extensions; using Umbraco.Web.BackOffice.Filters; using Umbraco.Web.Common.ActionResults; using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.Filters; using Umbraco.Web.Common.Security; using Umbraco.Web.Models; +using Umbraco.Web.Security; using Umbraco.Web.WebAssets; using Constants = Umbraco.Core.Constants; @@ -40,6 +43,8 @@ namespace Umbraco.Web.BackOffice.Controllers private readonly BackOfficeServerVariables _backOfficeServerVariables; private readonly AppCaches _appCaches; private readonly BackOfficeSignInManager _signInManager; + private readonly IWebSecurity _webSecurity; + private readonly ILogger _logger; public BackOfficeController( BackOfficeUserManager userManager, @@ -51,7 +56,10 @@ namespace Umbraco.Web.BackOffice.Controllers IGridConfig gridConfig, BackOfficeServerVariables backOfficeServerVariables, AppCaches appCaches, - BackOfficeSignInManager signInManager) + BackOfficeSignInManager signInManager, + IWebSecurity webSecurity, + ILogger logger) + { _userManager = userManager; _runtimeMinifier = runtimeMinifier; @@ -63,6 +71,8 @@ namespace Umbraco.Web.BackOffice.Controllers _backOfficeServerVariables = backOfficeServerVariables; _appCaches = appCaches; _signInManager = signInManager; + _webSecurity = webSecurity; + _logger = logger; } [HttpGet] @@ -74,6 +84,84 @@ namespace Umbraco.Web.BackOffice.Controllers () => View(viewPath)); } + [HttpGet] + public async Task VerifyInvite(string invite) + { + //if you are hitting VerifyInvite, you're already signed in as a different user, and the token is invalid + //you'll exit on one of the return RedirectToAction("Default") but you're still logged in so you just get + //dumped at the default admin view with no detail + if (_webSecurity.IsAuthenticated()) + { + await _signInManager.SignOutAsync(); + } + + if (invite == null) + { + _logger.Warn("VerifyUser endpoint reached with invalid token: NULL"); + return RedirectToAction(nameof(Default)); + } + + var parts = System.Net.WebUtility.UrlDecode(invite).Split('|'); + + if (parts.Length != 2) + { + _logger.Warn("VerifyUser endpoint reached with invalid token: {Invite}", invite); + return RedirectToAction(nameof(Default)); + } + + var token = parts[1]; + + var decoded = token.FromUrlBase64(); + if (decoded.IsNullOrWhiteSpace()) + { + _logger.Warn("VerifyUser endpoint reached with invalid token: {Invite}", invite); + return RedirectToAction(nameof(Default)); + } + + var id = parts[0]; + + var identityUser = await _userManager.FindByIdAsync(id); + if (identityUser == null) + { + _logger.Warn("VerifyUser endpoint reached with non existing user: {UserId}", id); + return RedirectToAction(nameof(Default)); + } + + var result = await _userManager.ConfirmEmailAsync(identityUser, decoded); + + if (result.Succeeded == false) + { + _logger.Warn("Could not verify email, Error: {Errors}, Token: {Invite}", result.Errors.ToErrorMessage(), invite); + return new RedirectResult(Url.Action(nameof(Default)) + "#/login/false?invite=3"); + } + + //sign the user in + DateTime? previousLastLoginDate = identityUser.LastLoginDateUtc; + await _signInManager.SignInAsync(identityUser, false); + //reset the lastlogindate back to previous as the user hasn't actually logged in, to add a flag or similar to SignInManager would be a breaking change + identityUser.LastLoginDateUtc = previousLastLoginDate; + await _userManager.UpdateAsync(identityUser); + + return new RedirectResult(Url.Action(nameof(Default)) + "#/login/false?invite=1"); + } + + /// + /// This Action is used by the installer when an upgrade is detected but the admin user is not logged in. We need to + /// ensure the user is authenticated before the install takes place so we redirect here to show the standard login screen. + /// + /// + [HttpGet] + [StatusCodeResult(System.Net.HttpStatusCode.ServiceUnavailable)] + public async Task AuthorizeUpgrade() + { + var viewPath = Path.Combine(_globalSettings.UmbracoPath, Umbraco.Core.Constants.Web.Mvc.BackOfficeArea, nameof(AuthorizeUpgrade) + ".cshtml"); + return await RenderDefaultOrProcessExternalLoginAsync( + //The default view to render when there is no external login info or errors + () => View(viewPath), + //The ActionResult to perform if external login is successful + () => Redirect("/")); + } + /// /// Returns the JavaScript main file including all references found in manifests /// @@ -95,8 +183,7 @@ namespace Umbraco.Web.BackOffice.Controllers [HttpGet] public Dictionary> LocalizedText(string culture = null) { - var securityHelper = _umbracoContextAccessor.GetRequiredUmbracoContext().Security; - var isAuthenticated = securityHelper.IsAuthenticated(); + var isAuthenticated = _webSecurity.IsAuthenticated(); var cultureInfo = string.IsNullOrWhiteSpace(culture) //if the user is logged in, get their culture, otherwise default to 'en' diff --git a/src/Umbraco.Web.UI.NetCore/Umbraco/UmbracoBackOffice/AuthorizeUpgrade.cshtml b/src/Umbraco.Web.UI.NetCore/Umbraco/UmbracoBackOffice/AuthorizeUpgrade.cshtml new file mode 100644 index 0000000000..a3cb5e4c01 --- /dev/null +++ b/src/Umbraco.Web.UI.NetCore/Umbraco/UmbracoBackOffice/AuthorizeUpgrade.cshtml @@ -0,0 +1,73 @@ +@using Umbraco.Core +@using Umbraco.Web.WebAssets +@using Umbraco.Web.Common.Security +@using Umbraco.Core.WebAssets +@using Umbraco.Core.Configuration +@using Umbraco.Core.Hosting +@using Umbraco.Extensions +@using Umbraco.Core.Logging +@using Umbraco.Web.BackOffice.Controllers +@inject BackOfficeSignInManager signInManager +@inject BackOfficeServerVariables backOfficeServerVariables +@inject IUmbracoVersion umbracoVersion +@inject IHostingEnvironment hostingEnvironment +@inject IGlobalSettings globalSettings +@inject IRuntimeMinifier runtimeMinifier +@inject IProfilerHtml profilerHtml + +@{ + var backOfficePath = globalSettings.GetBackOfficePath(hostingEnvironment); +} + + + + + + + + + + Umbraco + + @Html.Raw(await runtimeMinifier.RenderCssHereAsync(BackOfficeWebAssets.UmbracoUpgradeCssBundleName)) + + @*Because we're lazy loading angular js, the embedded cloak style will not be loaded initially, but we need it*@ + + + + + + + + + + @{ + var externalLoginUrl = Url.Action("ExternalLogin", "BackOffice", new + { + area = ViewData.GetUmbracoPath(), + //Custom redirect URL since we don't want to just redirect to the back office since this is for authing upgrades + redirectUrl = Url.Action("AuthorizeUpgrade", "BackOffice") + }); + } + + @await Html.BareMinimumServerVariablesScriptAsync(backOfficeServerVariables) + + + + @*And finally we can load in our angular app*@ + + + + + diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 328ce92052..b963871a58 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -1,32 +1,21 @@ using System; -using System.Collections.Generic; -using System.Globalization; using System.Linq; -using System.Threading; using System.Threading.Tasks; using System.Web; using System.Web.Mvc; -using System.Web.UI; using Microsoft.AspNetCore.Identity; using Microsoft.Owin.Security; -using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; -using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Core.Services; using Umbraco.Web.Features; using Umbraco.Web.Security; using Constants = Umbraco.Core.Constants; -using Umbraco.Core.Configuration.Grid; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Hosting; -using Umbraco.Core.WebAssets; -using Umbraco.Extensions; -using Umbraco.Web.Trees; -using Umbraco.Web.WebAssets; using BackOfficeIdentityUser = Umbraco.Core.BackOffice.BackOfficeIdentityUser; namespace Umbraco.Web.Editors @@ -40,17 +29,13 @@ namespace Umbraco.Web.Editors public class BackOfficeController : UmbracoController { private readonly UmbracoFeatures _features; - private readonly IRuntimeState _runtimeState; private BackOfficeOwinUserManager _userManager; private BackOfficeSignInManager _signInManager; private readonly IUmbracoVersion _umbracoVersion; - private readonly IGridConfig _gridConfig; private readonly IContentSettings _contentSettings; - private readonly TreeCollection _treeCollection; private readonly IHostingEnvironment _hostingEnvironment; private readonly IRuntimeSettings _runtimeSettings; private readonly ISecuritySettings _securitySettings; - private readonly IRuntimeMinifier _runtimeMinifier; public BackOfficeController( UmbracoFeatures features, @@ -59,29 +44,20 @@ namespace Umbraco.Web.Editors ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger, - IRuntimeState runtimeState, IUmbracoVersion umbracoVersion, - IGridConfig gridConfig, IContentSettings contentSettings, - TreeCollection treeCollection, IHostingEnvironment hostingEnvironment, - IHttpContextAccessor httpContextAccessor, IRuntimeSettings settings, - ISecuritySettings securitySettings, - IRuntimeMinifier runtimeMinifier) + ISecuritySettings securitySettings) : base(globalSettings, umbracoContextAccessor, services, appCaches, profilingLogger) { _features = features; - _runtimeState = runtimeState; _umbracoVersion = umbracoVersion; - _gridConfig = gridConfig ?? throw new ArgumentNullException(nameof(gridConfig)); _contentSettings = contentSettings ?? throw new ArgumentNullException(nameof(contentSettings)); - _treeCollection = treeCollection ?? throw new ArgumentNullException(nameof(treeCollection)); _hostingEnvironment = hostingEnvironment; _runtimeSettings = settings; _securitySettings = securitySettings; - _runtimeMinifier = runtimeMinifier; } protected BackOfficeSignInManager SignInManager => _signInManager ?? (_signInManager = OwinContext.GetBackOfficeSignInManager()); @@ -90,85 +66,6 @@ namespace Umbraco.Web.Editors protected IAuthenticationManager AuthenticationManager => OwinContext.Authentication; - [HttpGet] - public async Task VerifyInvite(string invite) - { - //if you are hitting VerifyInvite, you're already signed in as a different user, and the token is invalid - //you'll exit on one of the return RedirectToAction("Default") but you're still logged in so you just get - //dumped at the default admin view with no detail - if(Security.IsAuthenticated()) - { - AuthenticationManager.SignOut( - Core.Constants.Security.BackOfficeAuthenticationType, - Core.Constants.Security.BackOfficeExternalAuthenticationType); - } - - if (invite == null) - { - Logger.Warn("VerifyUser endpoint reached with invalid token: NULL"); - return RedirectToAction("Default"); - } - - var parts = Server.UrlDecode(invite).Split('|'); - - if (parts.Length != 2) - { - Logger.Warn("VerifyUser endpoint reached with invalid token: {Invite}", invite); - return RedirectToAction("Default"); - } - - var token = parts[1]; - - var decoded = token.FromUrlBase64(); - if (decoded.IsNullOrWhiteSpace()) - { - Logger.Warn("VerifyUser endpoint reached with invalid token: {Invite}", invite); - return RedirectToAction("Default"); - } - - var id = parts[0]; - - var identityUser = await UserManager.FindByIdAsync(id); - if (identityUser == null) - { - Logger.Warn("VerifyUser endpoint reached with non existing user: {UserId}", id); - return RedirectToAction("Default"); - } - - var result = await UserManager.ConfirmEmailAsync(identityUser, decoded); - - if (result.Succeeded == false) - { - Logger.Warn("Could not verify email, Error: {Errors}, Token: {Invite}", result.Errors.ToErrorMessage(), invite); - return new RedirectResult(Url.Action("Default") + "#/login/false?invite=3"); - } - - //sign the user in - DateTime? previousLastLoginDate = identityUser.LastLoginDateUtc; - await SignInManager.SignInAsync(identityUser, false, false); - //reset the lastlogindate back to previous as the user hasn't actually logged in, to add a flag or similar to SignInManager would be a breaking change - identityUser.LastLoginDateUtc = previousLastLoginDate; - await UserManager.UpdateAsync(identityUser); - - return new RedirectResult(Url.Action("Default") + "#/login/false?invite=1"); - } - - /// - /// This Action is used by the installer when an upgrade is detected but the admin user is not logged in. We need to - /// ensure the user is authenticated before the install takes place so we redirect here to show the standard login screen. - /// - /// - [HttpGet] - [StatusCodeResult(System.Net.HttpStatusCode.ServiceUnavailable)] - public async Task AuthorizeUpgrade() - { - return await RenderDefaultOrProcessExternalLoginAsync( - //The default view to render when there is no external login info or errors - () => View(GlobalSettings.GetBackOfficePath(_hostingEnvironment).EnsureEndsWith('/') + "Views/AuthorizeUpgrade.cshtml", new BackOfficeModel(_features, GlobalSettings, _umbracoVersion, _contentSettings, _hostingEnvironment, _runtimeSettings, _securitySettings)), - //The ActionResult to perform if external login is successful - () => Redirect("/")); - } - // TODO: for converting to netcore, some examples: // * https://github.com/dotnet/aspnetcore/blob/master/src/Identity/samples/IdentitySample.Mvc/Controllers/AccountController.cs