Merge pull request #8572 from umbraco/netcore/bugfix/6970-backoffice-controller

Migrates remaining non-oauth actions on back office controller to netcore
This commit is contained in:
Bjarke Berg
2020-08-17 08:29:59 +02:00
committed by GitHub
5 changed files with 163 additions and 95 deletions

View File

@@ -321,7 +321,6 @@ namespace Umbraco.Core.Migrations.Install
var database = scope.Database;
var dbSchema = new DatabaseSchemaCreator(database, _logger, _umbracoVersion, _globalSettings);
_databaseSchemaValidationResult = dbSchema.ValidateSchema();
scope.Complete();
return _databaseSchemaValidationResult;
}

View File

@@ -19,7 +19,7 @@ namespace Umbraco.Core.Services.Implement
/// <inheritdoc />
public string GetValue(string key)
{
using (var scope = _scopeProvider.CreateScope())
using (var scope = _scopeProvider.CreateScope(autoComplete: true))
{
return _repository.Get(key)?.Value;
}

View File

@@ -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]
@@ -76,6 +86,84 @@ namespace Umbraco.Web.BackOffice.Controllers
() => View(viewPath));
}
[HttpGet]
public async Task<IActionResult> 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(nameof(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<BackOfficeController>("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<BackOfficeController>("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<BackOfficeController>("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<BackOfficeController>("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<BackOfficeController>("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 BackOfficeSignInManager would be a breaking change
identityUser.LastLoginDateUtc = previousLastLoginDate;
await _userManager.UpdateAsync(identityUser);
return new RedirectResult(Url.Action(nameof(Default)) + "#/login/false?invite=1");
}
/// <summary>
/// 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.
/// </summary>
/// <returns></returns>
[HttpGet]
[StatusCodeResult(System.Net.HttpStatusCode.ServiceUnavailable)]
public async Task<IActionResult> 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 IActionResult to perform if external login is successful
() => Redirect("/"));
}
/// <summary>
/// Returns the JavaScript main file including all references found in manifests
/// </summary>
@@ -97,8 +185,7 @@ namespace Umbraco.Web.BackOffice.Controllers
[HttpGet]
public Dictionary<string, Dictionary<string, string>> 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'

View File

@@ -0,0 +1,72 @@
@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
@{
var backOfficePath = globalSettings.GetBackOfficePath(hostingEnvironment);
}
<!DOCTYPE html>
<html lang="en">
<head>
<base href="@backOfficePath.EnsureEndsWith('/')" />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Umbraco</title>
@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*@
<style>
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none !important;
}
</style>
</head>
<body id="umbracoMainPageBody" ng-controller="Umbraco.AuthorizeUpgradeController" class="login-only">
<umb-login on-login="loginAndRedirect()"></umb-login>
<umb-notifications></umb-notifications>
@{
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)
<script type="text/javascript">
document.angularReady = function (app) {
@await Html.AngularValueExternalLoginInfoScriptAsync(signInManager, ViewData.GetExternalSignInError())
@Html.AngularValueResetPasswordCodeInfoScript(ViewData["PasswordResetCode"])
}
</script>
@*And finally we can load in our angular app*@
<script type="text/javascript" src="lib/lazyload-js/lazyload.min.js"></script>
<script src="@Url.GetUrlWithCacheBust("Application", "BackOffice", null, hostingEnvironment, umbracoVersion, runtimeMinifier)"></script>
</body>
</html>

View File

@@ -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
@@ -77,85 +66,6 @@ namespace Umbraco.Web.Editors
protected IAuthenticationManager AuthenticationManager => OwinContext.Authentication;
[HttpGet]
public async Task<ActionResult> 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<BackOfficeController>("VerifyUser endpoint reached with invalid token: NULL");
return RedirectToAction("Default");
}
var parts = Server.UrlDecode(invite).Split('|');
if (parts.Length != 2)
{
Logger.Warn<BackOfficeController>("VerifyUser endpoint reached with invalid token: {Invite}", invite);
return RedirectToAction("Default");
}
var token = parts[1];
var decoded = token.FromUrlBase64();
if (decoded.IsNullOrWhiteSpace())
{
Logger.Warn<BackOfficeController>("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<BackOfficeController>("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<BackOfficeController>("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");
}
/// <summary>
/// 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.
/// </summary>
/// <returns></returns>
[HttpGet]
[StatusCodeResult(System.Net.HttpStatusCode.ServiceUnavailable)]
public async Task<ActionResult> 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