From 0a77775bb1e07f505ef3b745f12155c4acc38d3c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 7 Sep 2022 11:24:41 +0200 Subject: [PATCH] Fixed InstallAuthorizeAttribute and simplified other things (cherry picked from commit da24ae9180f2198af80cca889d97bf1a7fee8b7d) --- .../Controllers/BackOfficeController.cs | 7 +++ .../Install/InstallApiController.cs | 47 +++++++-------- .../Install/InstallAreaRoutes.cs | 15 ++--- .../Install/InstallAuthorizeAttribute.cs | 56 ++++++++++-------- .../Install/InstallController.cs | 59 +------------------ 5 files changed, 65 insertions(+), 119 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index beee83cbb4..c5567d1796 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -24,6 +24,7 @@ using Umbraco.Cms.Core.WebAssets; using Umbraco.Cms.Infrastructure.WebAssets; using Umbraco.Cms.Web.BackOffice.ActionResults; using Umbraco.Cms.Web.BackOffice.Filters; +using Umbraco.Cms.Web.BackOffice.Install; using Umbraco.Cms.Web.BackOffice.Security; using Umbraco.Cms.Web.Common.ActionsResults; using Umbraco.Cms.Web.Common.Attributes; @@ -211,6 +212,12 @@ public class BackOfficeController : UmbracoController { // force authentication to occur since this is not an authorized endpoint AuthenticateResult result = await this.AuthenticateBackOfficeAsync(); + if (result.Succeeded) + { + // Redirect to installer if we're already authorized + var installerUrl = Url.Action(nameof(InstallController.Index), ControllerExtensions.GetControllerName(), new { area = Cms.Core.Constants.Web.Mvc.InstallArea }) ?? "/"; + return new LocalRedirectResult(installerUrl); + } var viewPath = Path.Combine(Constants.SystemDirectories.Umbraco, Constants.Web.Mvc.BackOfficeArea, nameof(AuthorizeUpgrade) + ".cshtml"); diff --git a/src/Umbraco.Web.BackOffice/Install/InstallApiController.cs b/src/Umbraco.Web.BackOffice/Install/InstallApiController.cs index 02538ea528..555c14ff96 100644 --- a/src/Umbraco.Web.BackOffice/Install/InstallApiController.cs +++ b/src/Umbraco.Web.BackOffice/Install/InstallApiController.cs @@ -60,26 +60,27 @@ public class InstallApiController : ControllerBase internal InstallHelper InstallHelper { get; } public bool PostValidateDatabaseConnection(DatabaseModel databaseSettings) - => _databaseBuilder.ConfigureDatabaseConnection(databaseSettings, true); + { + if (_runtime.State.Level != RuntimeLevel.Install) + { + return false; + } + + return _databaseBuilder.ConfigureDatabaseConnection(databaseSettings, true); + } /// - /// Gets the install setup. + /// Gets the install setup. /// public InstallSetup GetSetup() { - var setup = new InstallSetup(); + // Only get the steps that are targeting the current install type + var setup = new InstallSetup + { + Steps = _installSteps.GetStepsForCurrentInstallType().ToList() + }; - // TODO: Check for user/site token - - var steps = new List(); - - InstallSetupStep[] installSteps = _installSteps.GetStepsForCurrentInstallType().ToArray(); - - //only get the steps that are targeting the current install type - steps.AddRange(installSteps); - setup.Steps = steps; - - _installStatusTracker.Initialize(setup.InstallId, installSteps); + _installStatusTracker.Initialize(setup.InstallId, setup.Steps); return setup; } @@ -100,9 +101,6 @@ public class InstallApiController : ControllerBase return NoContent(); } - /// - /// Installs. - /// public async Task> PostPerformInstall(InstallInstructions installModel) { if (installModel == null) @@ -110,14 +108,14 @@ public class InstallApiController : ControllerBase throw new ArgumentNullException(nameof(installModel)); } + // There won't be any statuses returned if the app pool has restarted so we need to re-read from file InstallTrackingItem[] status = InstallStatusTracker.GetStatus().ToArray(); - //there won't be any statuses returned if the app pool has restarted so we need to re-read from file. if (status.Any() == false) { status = _installStatusTracker.InitializeFromFile(installModel.InstallId).ToArray(); } - //create a new queue of the non-finished ones + // Create a new queue of the non-finished ones var queue = new Queue(status.Where(x => x.IsComplete == false)); while (queue.Count > 0) { @@ -144,14 +142,15 @@ public class InstallApiController : ControllerBase // determine's the next step in the queue and dequeue's any items that don't need to execute var nextStep = IterateSteps(step, queue, installModel.InstallId, installModel); + bool processComplete = string.IsNullOrEmpty(nextStep) && InstallStatusTracker.GetStatus().All(x => x.IsComplete); // check if there's a custom view to return for this step if (setupData != null && setupData.View.IsNullOrWhiteSpace() == false) { - return new InstallProgressResultModel(false, step.Name, nextStep, setupData.View, setupData.ViewModel); + return new InstallProgressResultModel(processComplete, step.Name, nextStep, setupData.View, setupData.ViewModel); } - return new InstallProgressResultModel(false, step.Name, nextStep); + return new InstallProgressResultModel(processComplete, step.Name, nextStep); } catch (Exception ex) { @@ -252,8 +251,7 @@ public class InstallApiController : ControllerBase Attempt modelAttempt = instruction.TryConvertTo(step.StepType); if (!modelAttempt.Success) { - throw new InvalidCastException( - $"Cannot cast/convert {step.GetType().FullName} into {step.StepType.FullName}"); + throw new InvalidCastException($"Cannot cast/convert {step.GetType().FullName} into {step.StepType.FullName}"); } var model = modelAttempt.Result; @@ -281,8 +279,7 @@ public class InstallApiController : ControllerBase Attempt modelAttempt = instruction.TryConvertTo(step.StepType); if (!modelAttempt.Success) { - throw new InvalidCastException( - $"Cannot cast/convert {step.GetType().FullName} into {step.StepType.FullName}"); + throw new InvalidCastException($"Cannot cast/convert {step.GetType().FullName} into {step.StepType.FullName}"); } var model = modelAttempt.Result; diff --git a/src/Umbraco.Web.BackOffice/Install/InstallAreaRoutes.cs b/src/Umbraco.Web.BackOffice/Install/InstallAreaRoutes.cs index 590fb73e0e..0ea55d861d 100644 --- a/src/Umbraco.Web.BackOffice/Install/InstallAreaRoutes.cs +++ b/src/Umbraco.Web.BackOffice/Install/InstallAreaRoutes.cs @@ -29,21 +29,14 @@ public class InstallAreaRoutes : IAreaRoutes switch (_runtime.Level) { case var _ when _runtime.EnableInstaller(): + endpoints.MapUmbracoRoute(installPathSegment, Constants.Web.Mvc.InstallArea, "api", includeControllerNameInRoute: false); + endpoints.MapUmbracoRoute(installPathSegment, Constants.Web.Mvc.InstallArea, string.Empty, includeControllerNameInRoute: false); - endpoints.MapUmbracoRoute(installPathSegment, Constants.Web.Mvc.InstallArea, - "api", includeControllerNameInRoute: false); - endpoints.MapUmbracoRoute(installPathSegment, Constants.Web.Mvc.InstallArea, - string.Empty, includeControllerNameInRoute: false); - - // register catch all because if we are in install/upgrade mode then we'll catch everything and redirect - endpoints.MapFallbackToAreaController( - "Redirect", - ControllerExtensions.GetControllerName(), - Constants.Web.Mvc.InstallArea); + // register catch all because if we are in install/upgrade mode then we'll catch everything + endpoints.MapFallbackToAreaController(nameof(InstallController.Index), ControllerExtensions.GetControllerName(), Constants.Web.Mvc.InstallArea); break; case RuntimeLevel.Run: - // when we are in run mode redirect to the back office if the installer endpoint is hit endpoints.MapGet($"{installPathSegment}/{{controller?}}/{{action?}}", context => { diff --git a/src/Umbraco.Web.BackOffice/Install/InstallAuthorizeAttribute.cs b/src/Umbraco.Web.BackOffice/Install/InstallAuthorizeAttribute.cs index 428f21932c..2c6d5102e8 100644 --- a/src/Umbraco.Web.BackOffice/Install/InstallAuthorizeAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Install/InstallAuthorizeAttribute.cs @@ -1,55 +1,59 @@ +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.BackOffice.Controllers; using Umbraco.Extensions; namespace Umbraco.Cms.Web.BackOffice.Install; /// -/// Ensures authorization occurs for the installer if it has already completed. -/// If install has not yet occurred then the authorization is successful. +/// Specifies the authorization filter that verifies whether the runtime level is , or and a user is logged in. /// public class InstallAuthorizeAttribute : TypeFilterAttribute { - public InstallAuthorizeAttribute() : base(typeof(InstallAuthorizeFilter)) - { - } + public InstallAuthorizeAttribute() + : base(typeof(InstallAuthorizeFilter)) + { } - private class InstallAuthorizeFilter : IAuthorizationFilter + private class InstallAuthorizeFilter : IAsyncAuthorizationFilter { private readonly ILogger _logger; private readonly IRuntimeState _runtimeState; + private readonly LinkGenerator _linkGenerator; + private readonly IHostingEnvironment _hostingEnvironment; - public InstallAuthorizeFilter( - IRuntimeState runtimeState, - ILogger logger) + public InstallAuthorizeFilter(IRuntimeState runtimeState, ILogger logger, LinkGenerator linkGenerator, IHostingEnvironment hostingEnvironment) { _runtimeState = runtimeState; _logger = logger; + _linkGenerator = linkGenerator; + _hostingEnvironment = hostingEnvironment; } - public void OnAuthorization(AuthorizationFilterContext authorizationFilterContext) + public async Task OnAuthorizationAsync(AuthorizationFilterContext context) { - if (!IsAllowed(authorizationFilterContext)) + if (_runtimeState.EnableInstaller() == false) { - authorizationFilterContext.Result = new ForbidResult(); + // Only authorize when the installer is enabled + context.Result = new ForbidResult(new AuthenticationProperties() + { + RedirectUri = _linkGenerator.GetBackOfficeUrl(_hostingEnvironment) + }); } - } - - private bool IsAllowed(AuthorizationFilterContext authorizationFilterContext) - { - try + else if (_runtimeState.Level == RuntimeLevel.Upgrade && (await context.HttpContext.AuthenticateBackOfficeAsync()).Succeeded == false) { - // if not configured (install or upgrade) then we can continue - // otherwise we need to ensure that a user is logged in - return _runtimeState.EnableInstaller() - || (authorizationFilterContext.HttpContext.User?.Identity?.IsAuthenticated ?? false); - } - catch (Exception ex) - { - _logger.LogError(ex, "An error occurred determining authorization"); - return false; + // Redirect to authorize upgrade + var authorizeUpgradePath = _linkGenerator.GetPathByAction(nameof(BackOfficeController.AuthorizeUpgrade), ControllerExtensions.GetControllerName(), new + { + area = Constants.Web.Mvc.BackOfficeArea, + redir = _linkGenerator.GetInstallerUrl() + }); + context.Result = new LocalRedirectResult(authorizeUpgradePath ?? "/"); } } } diff --git a/src/Umbraco.Web.BackOffice/Install/InstallController.cs b/src/Umbraco.Web.BackOffice/Install/InstallController.cs index c8af0d8ba8..7de93d4296 100644 --- a/src/Umbraco.Web.BackOffice/Install/InstallController.cs +++ b/src/Umbraco.Web.BackOffice/Install/InstallController.cs @@ -1,6 +1,4 @@ using System.Net; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; @@ -13,12 +11,10 @@ using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.WebAssets; using Umbraco.Cms.Infrastructure.Install; -using Umbraco.Cms.Web.Common.Attributes; using Umbraco.Cms.Web.Common.Filters; using Umbraco.Extensions; namespace Umbraco.Cms.Web.BackOffice.Install; - /// /// The Installation controller /// @@ -27,8 +23,6 @@ namespace Umbraco.Cms.Web.BackOffice.Install; [Area(Constants.Web.Mvc.InstallArea)] public class InstallController : Controller { - private static bool _reported; - private static RuntimeLevel _reportedLevel; private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor; private readonly GlobalSettings _globalSettings; private readonly IHostingEnvironment _hostingEnvironment; @@ -63,31 +57,12 @@ public class InstallController : Controller [HttpGet] [StatusCodeResult(HttpStatusCode.ServiceUnavailable)] - [TypeFilter(typeof(StatusCodeResultAttribute), Arguments = new object[] { HttpStatusCode.ServiceUnavailable })] public async Task Index() { - var umbracoPath = Url.GetBackOfficeUrl(); - - if (_runtime.Level == RuntimeLevel.Run) - { - return Redirect(umbracoPath!); - } - - // TODO: Update for package migrations - if (_runtime.Level == RuntimeLevel.Upgrade) - { - AuthenticateResult authResult = await this.AuthenticateBackOfficeAsync(); - - if (!authResult.Succeeded) - { - return Redirect(_globalSettings.UmbracoPath + "/AuthorizeUpgrade?redir=" + Request.GetEncodedUrl()); - } - } - - // gen the install base URL + // Get the install base URL ViewData.SetInstallApiBaseUrl(_linkGenerator.GetInstallerApiUrl()); - // get the base umbraco folder + // Get the base umbraco folder var baseFolder = _hostingEnvironment.ToAbsolute(_globalSettings.UmbracoPath); ViewData.SetUmbracoBaseFolder(baseFolder); @@ -97,34 +72,4 @@ public class InstallController : Controller return View(Path.Combine(Constants.SystemDirectories.Umbraco.TrimStart("~"), Constants.Web.Mvc.InstallArea, nameof(Index) + ".cshtml")); } - - /// - /// Used to perform the redirect to the installer when the runtime level is or - /// - /// - /// - [HttpGet] - [IgnoreFromNotFoundSelectorPolicy] - public ActionResult Redirect() - { - var uri = HttpContext.Request.GetEncodedUrl(); - - // redirect to install - ReportRuntime(_logger, _runtime.Level, "Umbraco must install or upgrade."); - - var installUrl = $"{_linkGenerator.GetInstallerUrl()}?redir=true&url={uri}"; - return Redirect(installUrl); - } - - private static void ReportRuntime(ILogger logger, RuntimeLevel level, string message) - { - if (_reported && _reportedLevel == level) - { - return; - } - - _reported = true; - _reportedLevel = level; - logger.LogWarning(message); - } }