Files
Umbraco-CMS/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs

546 lines
25 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration.Grid;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.Manifest;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
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.Security;
2021-02-10 11:42:04 +01:00
using Umbraco.Cms.Web.Common.ActionsResults;
using Umbraco.Cms.Web.Common.Attributes;
using Umbraco.Cms.Web.Common.Authorization;
using Umbraco.Cms.Web.Common.Controllers;
using Umbraco.Cms.Web.Common.Filters;
using Umbraco.Extensions;
using Constants = Umbraco.Cms.Core.Constants;
using SignInResult = Microsoft.AspNetCore.Identity.SignInResult;
namespace Umbraco.Cms.Web.BackOffice.Controllers
{
[DisableBrowserCache]
[UmbracoRequireHttps]
[PluginController(Constants.Web.Mvc.BackOfficeArea)]
[IsBackOffice]
public class BackOfficeController : UmbracoController
{
// See here for examples of what a lot of this is doing: https://github.com/dotnet/aspnetcore/blob/main/src/Identity/samples/IdentitySample.Mvc/Controllers/AccountController.cs
// along with our AuthenticationController
// NOTE: Each action must either be explicitly authorized or explicitly [AllowAnonymous], the latter is optional because
// this controller itself doesn't require authz but it's more clear what the intention is.
private readonly IBackOfficeUserManager _userManager;
private readonly IRuntimeState _runtimeState;
private readonly IRuntimeMinifier _runtimeMinifier;
private readonly GlobalSettings _globalSettings;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly ILocalizedTextService _textService;
private readonly IGridConfig _gridConfig;
private readonly BackOfficeServerVariables _backOfficeServerVariables;
private readonly AppCaches _appCaches;
private readonly IBackOfficeSignInManager _signInManager;
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
private readonly ILogger<BackOfficeController> _logger;
2020-08-31 13:39:29 +02:00
private readonly IJsonSerializer _jsonSerializer;
2020-10-23 14:18:53 +11:00
private readonly IBackOfficeExternalLoginProviders _externalLogins;
private readonly IHttpContextAccessor _httpContextAccessor;
2020-12-02 12:22:08 +11:00
private readonly IBackOfficeTwoFactorOptions _backOfficeTwoFactorOptions;
private readonly IManifestParser _manifestParser;
2021-01-13 12:48:41 +11:00
private readonly ServerVariablesParser _serverVariables;
public BackOfficeController(
IBackOfficeUserManager userManager,
IRuntimeState runtimeState,
IRuntimeMinifier runtimeMinifier,
IOptions<GlobalSettings> globalSettings,
IHostingEnvironment hostingEnvironment,
ILocalizedTextService textService,
IGridConfig gridConfig,
BackOfficeServerVariables backOfficeServerVariables,
AppCaches appCaches,
IBackOfficeSignInManager signInManager,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
ILogger<BackOfficeController> logger,
IJsonSerializer jsonSerializer,
IBackOfficeExternalLoginProviders externalLogins,
2020-12-02 12:22:08 +11:00
IHttpContextAccessor httpContextAccessor,
2021-01-13 12:48:41 +11:00
IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions,
IManifestParser manifestParser,
2021-01-13 12:48:41 +11:00
ServerVariablesParser serverVariables)
{
_userManager = userManager;
_runtimeState = runtimeState;
_runtimeMinifier = runtimeMinifier;
_globalSettings = globalSettings.Value;
_hostingEnvironment = hostingEnvironment;
_textService = textService;
_gridConfig = gridConfig ?? throw new ArgumentNullException(nameof(gridConfig));
_backOfficeServerVariables = backOfficeServerVariables;
_appCaches = appCaches;
_signInManager = signInManager;
_backofficeSecurityAccessor = backofficeSecurityAccessor;
_logger = logger;
2020-08-31 13:39:29 +02:00
_jsonSerializer = jsonSerializer;
2020-10-23 14:18:53 +11:00
_externalLogins = externalLogins;
_httpContextAccessor = httpContextAccessor;
2020-12-02 12:22:08 +11:00
_backOfficeTwoFactorOptions = backOfficeTwoFactorOptions;
_manifestParser = manifestParser;
2021-01-13 12:48:41 +11:00
_serverVariables = serverVariables;
}
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> Default()
{
// force authentication to occur since this is not an authorized endpoint
var result = await this.AuthenticateBackOfficeAsync();
var viewPath = Path.Combine(_globalSettings.UmbracoPath , Constants.Web.Mvc.BackOfficeArea, nameof(Default) + ".cshtml")
.Replace("\\", "/"); // convert to forward slashes since it's a virtual path
return await RenderDefaultOrProcessExternalLoginAsync(
result,
() => View(viewPath),
() => View(viewPath));
}
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> VerifyInvite(string invite)
{
var authenticate = await this.AuthenticateBackOfficeAsync();
//if you are hitting VerifyInvite, you're already signed in as a different user, and the token is invalid
2020-08-04 20:29:48 +02:00
//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 (authenticate.Succeeded)
{
await _signInManager.SignOutAsync();
}
if (invite == null)
{
_logger.LogWarning("VerifyUser endpoint reached with invalid token: NULL");
return RedirectToAction(nameof(Default));
}
var parts = System.Net.WebUtility.UrlDecode(invite).Split('|');
if (parts.Length != 2)
{
_logger.LogWarning("VerifyUser endpoint reached with invalid token: {Invite}", invite);
return RedirectToAction(nameof(Default));
}
var token = parts[1];
var decoded = token.FromUrlBase64();
if (decoded.IsNullOrWhiteSpace())
{
_logger.LogWarning("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.LogWarning("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.LogWarning("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);
2020-08-04 20:29:48 +02:00
//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)]
[AllowAnonymous]
public async Task<IActionResult> AuthorizeUpgrade()
{
// force authentication to occur since this is not an authorized endpoint
var result = await this.AuthenticateBackOfficeAsync();
var viewPath = Path.Combine(_globalSettings.UmbracoPath, Constants.Web.Mvc.BackOfficeArea, nameof(AuthorizeUpgrade) + ".cshtml");
return await RenderDefaultOrProcessExternalLoginAsync(
result,
//The default view to render when there is no external login info or errors
() => View(viewPath),
2020-08-04 20:29:48 +02:00
//The IActionResult to perform if external login is successful
() => Redirect("/"));
}
/// <summary>
/// Returns the JavaScript main file including all references found in manifests
/// </summary>
/// <returns></returns>
[MinifyJavaScriptResult(Order = 0)]
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> Application()
{
var result = await _runtimeMinifier.GetScriptForLoadingBackOfficeAsync(
_globalSettings,
_hostingEnvironment,
_manifestParser);
return new JavaScriptResult(result);
}
/// <summary>
/// Get the json localized text for a given culture or the culture for the current user
/// </summary>
/// <param name="culture"></param>
/// <returns></returns>
[HttpGet]
[AllowAnonymous]
public Dictionary<string, Dictionary<string, string>> LocalizedText(string culture = null)
{
var isAuthenticated = _backofficeSecurityAccessor.BackOfficeSecurity.IsAuthenticated();
var cultureInfo = string.IsNullOrWhiteSpace(culture)
//if the user is logged in, get their culture, otherwise default to 'en'
? isAuthenticated
//current culture is set at the very beginning of each request
? Thread.CurrentThread.CurrentCulture
: CultureInfo.GetCultureInfo(_globalSettings.DefaultUILanguage)
: CultureInfo.GetCultureInfo(culture);
var allValues = _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
};
});
var nestedDictionary = pathedValues
.GroupBy(pv => pv.areaAlias)
.ToDictionary(pv => pv.Key, pv =>
pv.ToDictionary(pve => pve.valueAlias, pve => pve.value));
return nestedDictionary;
}
[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)]
[HttpGet]
public IEnumerable<IGridEditorConfig> GetGridConfig()
{
return _gridConfig.EditorsConfig.Editors;
}
/// <summary>
/// Returns the JavaScript object representing the static server variables javascript object
/// </summary>
[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)]
[MinifyJavaScriptResult(Order = 1)]
public async Task<JavaScriptResult> ServerVariables()
{
2021-01-13 12:48:41 +11:00
// cache the result if debugging is disabled
var serverVars = await _serverVariables.ParseAsync(await _backOfficeServerVariables.GetServerVariablesAsync());
var result = _hostingEnvironment.IsDebugMode
? serverVars
: _appCaches.RuntimeCache.GetCacheItem<string>(
typeof(BackOfficeController) + "ServerVariables",
() => serverVars,
new TimeSpan(0, 10, 0));
return new JavaScriptResult(result);
}
[HttpPost]
[AllowAnonymous]
public ActionResult ExternalLogin(string provider, string redirectUrl = null)
{
if (redirectUrl == null)
{
redirectUrl = Url.Action(nameof(Default), this.GetControllerName());
}
// Configures the redirect URL and user identifier for the specified external login
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return Challenge(properties, provider);
}
/// <summary>
/// Called when a user links an external login provider in the back office
/// </summary>
/// <param name="provider"></param>
/// <returns></returns>
[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)]
[HttpPost]
public ActionResult LinkLogin(string provider)
{
// Request a redirect to the external login provider to link a login for the current user
var redirectUrl = Url.Action(nameof(ExternalLinkLoginCallback), this.GetControllerName());
// Configures the redirect URL and user identifier for the specified external login including xsrf data
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User));
return Challenge(properties, provider);
}
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> ValidatePasswordResetCode([Bind(Prefix = "u")]int userId, [Bind(Prefix = "r")]string resetCode)
{
var user = await _userManager.FindByIdAsync(userId.ToString());
if (user != null)
{
2020-08-31 13:39:29 +02:00
var result = await _userManager.VerifyUserTokenAsync(user, "Default", "ResetPassword", resetCode);
if (result)
{
//Add a flag and redirect for it to be displayed
2020-08-31 13:39:29 +02:00
TempData[ViewDataExtensions.TokenPasswordResetCode] = _jsonSerializer.Serialize(new ValidatePasswordResetCodeModel { UserId = userId, ResetCode = resetCode });
return RedirectToLocal(Url.Action(nameof(Default), this.GetControllerName()));
}
}
//Add error and redirect for it to be displayed
Merge remote-tracking branch 'origin/v8/dev' into v9/feature/merge-v8-05072021 # Conflicts: # build/NuSpecs/UmbracoCms.Web.nuspec # src/SolutionInfo.cs # src/Umbraco.Core/Compose/RelateOnTrashComponent.cs # src/Umbraco.Core/Composing/Current.cs # src/Umbraco.Core/Constants-AppSettings.cs # src/Umbraco.Core/Constants-SqlTemplates.cs # src/Umbraco.Core/Dashboards/ContentDashboardSettings.cs # src/Umbraco.Core/Dashboards/IContentDashboardSettings.cs # src/Umbraco.Core/Extensions/PublishedPropertyExtension.cs # src/Umbraco.Core/HealthChecks/Checks/Services/SmtpCheck.cs # src/Umbraco.Core/Models/IReadOnlyContentBase.cs # src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs # src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs # src/Umbraco.Core/Models/ReadOnlyContentBaseAdapter.cs # src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs # src/Umbraco.Core/PropertyEditors/IPropertyCacheCompression.cs # src/Umbraco.Core/PropertyEditors/IPropertyCacheCompressionOptions.cs # src/Umbraco.Core/PropertyEditors/MediaPicker3Configuration.cs # src/Umbraco.Core/PropertyEditors/NoopPropertyCacheCompressionOptions.cs # src/Umbraco.Core/PropertyEditors/PropertyCacheCompression.cs # src/Umbraco.Core/Routing/UrlProviderExtensions.cs # src/Umbraco.Core/Runtime/CoreRuntime.cs # src/Umbraco.Core/Services/ILocalizedTextService.cs # src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs # src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs # src/Umbraco.Examine/UmbracoContentIndex.cs # src/Umbraco.Infrastructure/Examine/GenericIndexDiagnostics.cs # src/Umbraco.Infrastructure/IPublishedContentQuery.cs # src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs # src/Umbraco.Infrastructure/Models/MediaWithCrops.cs # src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions.cs # src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs # src/Umbraco.Infrastructure/PropertyEditors/ImageCropperConfiguration.cs # src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs # src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs # src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs # src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs # src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MediaPickerWithCropsValueConverter.cs # src/Umbraco.Infrastructure/PublishedContentQuery.cs # src/Umbraco.Infrastructure/Search/UmbracoTreeSearcherFields.cs # src/Umbraco.Infrastructure/Services/Implement/LocalizedTextService.cs # src/Umbraco.Persistence.SqlCe/SqlCeSyntaxProvider.cs # src/Umbraco.PublishedCache.NuCache/DataSource/BTree.DictionaryOfPropertyDataSerializer.cs # src/Umbraco.PublishedCache.NuCache/DataSource/BTree.cs # src/Umbraco.PublishedCache.NuCache/DataSource/ContentData.cs # src/Umbraco.PublishedCache.NuCache/DataSource/ContentSourceDto.cs # src/Umbraco.PublishedCache.NuCache/DataSource/PropertyData.cs # src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs # src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageCropperTest.cs # src/Umbraco.Tests/App.config # src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs # src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs # src/Umbraco.Tests/PublishedContent/NuCacheTests.cs # src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs # src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs # src/Umbraco.Web.BackOffice/Controllers/ContentController.cs # src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs # src/Umbraco.Web.BackOffice/Controllers/DashboardController.cs # src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs # src/Umbraco.Web.BackOffice/Controllers/MediaController.cs # src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs # src/Umbraco.Web.BackOffice/Controllers/TemplateQueryController.cs # src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs # src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs # src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs # src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs # src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs # src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs # src/Umbraco.Web.Common/Macros/MacroRenderer.cs # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml # src/Umbraco.Web.UI/Umbraco/config/lang/it.xml # src/Umbraco.Web.UI/web.Template.Debug.config # src/Umbraco.Web.UI/web.Template.config # src/Umbraco.Web/Compose/NotificationsComponent.cs # src/Umbraco.Web/Composing/ModuleInjector.cs # src/Umbraco.Web/Editors/AuthenticationController.cs # src/Umbraco.Web/Editors/BackOfficeController.cs # src/Umbraco.Web/Editors/ContentTypeController.cs # src/Umbraco.Web/Editors/CurrentUserController.cs # src/Umbraco.Web/Editors/DictionaryController.cs # src/Umbraco.Web/Editors/MediaTypeController.cs # src/Umbraco.Web/Editors/MemberController.cs # src/Umbraco.Web/Editors/MemberGroupController.cs # src/Umbraco.Web/Editors/MemberTypeController.cs # src/Umbraco.Web/Editors/NuCacheStatusController.cs # src/Umbraco.Web/Editors/UserGroupsController.cs # src/Umbraco.Web/Editors/UsersController.cs # src/Umbraco.Web/HealthCheck/Checks/Config/AbstractConfigCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Config/CompilationDebugCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationService.cs # src/Umbraco.Web/HealthCheck/Checks/Config/CustomErrorsCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Config/MacroErrorsCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Config/NotificationEmailCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Config/TraceCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Config/TrySkipIisCustomErrorsCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Security/BaseHttpHeaderCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Security/HttpsCheck.cs # src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs # src/Umbraco.Web/Models/Trees/MenuItemList.cs # src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentCacheDataModel.cs # src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs # src/Umbraco.Web/PublishedCache/NuCache/DataSource/SerializerBase.cs # src/Umbraco.Web/PublishedCache/NuCache/NuCacheComposer.cs # src/Umbraco.Web/Runtime/WebRuntime.cs # src/Umbraco.Web/Search/ExamineComponent.cs # src/Umbraco.Web/Trees/ApplicationTreeController.cs # src/Umbraco.Web/Trees/MemberTreeController.cs # src/Umbraco.Web/UrlHelperRenderExtensions.cs
2021-07-05 20:58:04 +02:00
TempData[ViewDataExtensions.TokenPasswordResetCode] = new[] { _textService.Localize("login","resetCodeExpired") };
return RedirectToLocal(Url.Action(nameof(Default), this.GetControllerName()));
}
/// <summary>
/// Callback path when the user initiates a link login request from the back office to the external provider from the <see cref="LinkLogin(string)"/> action
/// </summary>
/// <remarks>
/// An example of this is here https://github.com/dotnet/aspnetcore/blob/main/src/Identity/samples/IdentitySample.Mvc/Controllers/AccountController.cs#L155
/// which this is based on
/// </remarks>
[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)]
[HttpGet]
public async Task<IActionResult> ExternalLinkLoginCallback()
{
BackOfficeIdentityUser user = await _userManager.GetUserAsync(User);
if (user == null)
{
// ... this should really not happen
TempData[ViewDataExtensions.TokenExternalSignInError] = new[] { "Local user does not exist" };
return RedirectToLocal(Url.Action(nameof(Default), this.GetControllerName()));
}
ExternalLoginInfo info = await _signInManager.GetExternalLoginInfoAsync(await _userManager.GetUserIdAsync(user));
if (info == null)
{
//Add error and redirect for it to be displayed
TempData[ViewDataExtensions.TokenExternalSignInError] = new[] { "An error occurred, could not get external login info" };
return RedirectToLocal(Url.Action(nameof(Default), this.GetControllerName()));
}
IdentityResult addLoginResult = await _userManager.AddLoginAsync(user, info);
2020-10-23 14:18:53 +11:00
if (addLoginResult.Succeeded)
{
// Update any authentication tokens if succeeded
await _signInManager.UpdateExternalAuthenticationTokensAsync(info);
return RedirectToLocal(Url.Action(nameof(Default), this.GetControllerName()));
}
//Add errors and redirect for it to be displayed
2020-10-23 14:18:53 +11:00
TempData[ViewDataExtensions.TokenExternalSignInError] = addLoginResult.Errors;
return RedirectToLocal(Url.Action(nameof(Default), this.GetControllerName()));
}
/// <summary>
/// Used by Default and AuthorizeUpgrade to render as per normal if there's no external login info,
/// otherwise process the external login info.
/// </summary>
/// <returns></returns>
private async Task<IActionResult> RenderDefaultOrProcessExternalLoginAsync(
AuthenticateResult authenticateResult,
Func<IActionResult> defaultResponse,
Func<IActionResult> externalSignInResponse)
{
if (defaultResponse is null) throw new ArgumentNullException(nameof(defaultResponse));
if (externalSignInResponse is null) throw new ArgumentNullException(nameof(externalSignInResponse));
ViewData.SetUmbracoPath(_globalSettings.GetUmbracoMvcArea(_hostingEnvironment));
2020-10-23 14:18:53 +11:00
//check if there is the TempData or cookies with the any token name specified, if so, assign to view bag and render the view
if (ViewData.FromBase64CookieData<BackOfficeExternalLoginProviderErrors>(_httpContextAccessor.HttpContext, ViewDataExtensions.TokenExternalSignInError, _jsonSerializer) ||
2020-10-23 14:18:53 +11:00
ViewData.FromTempData(TempData, ViewDataExtensions.TokenExternalSignInError) ||
ViewData.FromTempData(TempData, ViewDataExtensions.TokenPasswordResetCode))
{
return defaultResponse();
}
//First check if there's external login info, if there's not proceed as normal
var loginInfo = await _signInManager.GetExternalLoginInfoAsync();
2020-10-23 14:18:53 +11:00
if (loginInfo == null || loginInfo.Principal == null)
{
2020-10-23 14:18:53 +11:00
// if the user is not logged in, check if there's any auto login redirects specified
if (!authenticateResult.Succeeded)
2020-10-23 14:18:53 +11:00
{
var oauthRedirectAuthProvider = _externalLogins.GetAutoLoginProvider();
if (!oauthRedirectAuthProvider.IsNullOrWhiteSpace())
{
return ExternalLogin(oauthRedirectAuthProvider);
}
}
return defaultResponse();
}
//we're just logging in with an external source, not linking accounts
return await ExternalSignInAsync(loginInfo, externalSignInResponse);
}
private async Task<IActionResult> ExternalSignInAsync(ExternalLoginInfo loginInfo, Func<IActionResult> response)
{
if (loginInfo == null) throw new ArgumentNullException(nameof(loginInfo));
if (response == null) throw new ArgumentNullException(nameof(response));
// Sign in the user with this external login provider (which auto links, etc...)
SignInResult result = await _signInManager.ExternalLoginSignInAsync(loginInfo, isPersistent: false);
Merge remote-tracking branch 'origin/v8/dev' into v9/feature/merge-v8-05072021 # Conflicts: # build/NuSpecs/UmbracoCms.Web.nuspec # src/SolutionInfo.cs # src/Umbraco.Core/Compose/RelateOnTrashComponent.cs # src/Umbraco.Core/Composing/Current.cs # src/Umbraco.Core/Constants-AppSettings.cs # src/Umbraco.Core/Constants-SqlTemplates.cs # src/Umbraco.Core/Dashboards/ContentDashboardSettings.cs # src/Umbraco.Core/Dashboards/IContentDashboardSettings.cs # src/Umbraco.Core/Extensions/PublishedPropertyExtension.cs # src/Umbraco.Core/HealthChecks/Checks/Services/SmtpCheck.cs # src/Umbraco.Core/Models/IReadOnlyContentBase.cs # src/Umbraco.Core/Models/Mapping/MemberTabsAndPropertiesMapper.cs # src/Umbraco.Core/Models/Mapping/UserMapDefinition.cs # src/Umbraco.Core/Models/ReadOnlyContentBaseAdapter.cs # src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs # src/Umbraco.Core/PropertyEditors/IPropertyCacheCompression.cs # src/Umbraco.Core/PropertyEditors/IPropertyCacheCompressionOptions.cs # src/Umbraco.Core/PropertyEditors/MediaPicker3Configuration.cs # src/Umbraco.Core/PropertyEditors/NoopPropertyCacheCompressionOptions.cs # src/Umbraco.Core/PropertyEditors/PropertyCacheCompression.cs # src/Umbraco.Core/Routing/UrlProviderExtensions.cs # src/Umbraco.Core/Runtime/CoreRuntime.cs # src/Umbraco.Core/Services/ILocalizedTextService.cs # src/Umbraco.Core/Services/LocalizedTextServiceExtensions.cs # src/Umbraco.Examine.Lucene/BackOfficeExamineSearcher.cs # src/Umbraco.Examine/UmbracoContentIndex.cs # src/Umbraco.Infrastructure/Examine/GenericIndexDiagnostics.cs # src/Umbraco.Infrastructure/IPublishedContentQuery.cs # src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs # src/Umbraco.Infrastructure/Models/MediaWithCrops.cs # src/Umbraco.Infrastructure/Persistence/NPocoDatabaseExtensions.cs # src/Umbraco.Infrastructure/Persistence/UmbracoDatabase.cs # src/Umbraco.Infrastructure/PropertyEditors/ImageCropperConfiguration.cs # src/Umbraco.Infrastructure/PropertyEditors/MediaPicker3PropertyEditor.cs # src/Umbraco.Infrastructure/PropertyEditors/NestedContentPropertyEditor.cs # src/Umbraco.Infrastructure/PropertyEditors/UploadFileTypeValidator.cs # src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/ImageCropperValue.cs # src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MediaPickerWithCropsValueConverter.cs # src/Umbraco.Infrastructure/PublishedContentQuery.cs # src/Umbraco.Infrastructure/Search/UmbracoTreeSearcherFields.cs # src/Umbraco.Infrastructure/Services/Implement/LocalizedTextService.cs # src/Umbraco.Persistence.SqlCe/SqlCeSyntaxProvider.cs # src/Umbraco.PublishedCache.NuCache/DataSource/BTree.DictionaryOfPropertyDataSerializer.cs # src/Umbraco.PublishedCache.NuCache/DataSource/BTree.cs # src/Umbraco.PublishedCache.NuCache/DataSource/ContentData.cs # src/Umbraco.PublishedCache.NuCache/DataSource/ContentSourceDto.cs # src/Umbraco.PublishedCache.NuCache/DataSource/PropertyData.cs # src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentTypeServiceVariantsTests.cs # src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ImageCropperTest.cs # src/Umbraco.Tests/App.config # src/Umbraco.Tests/Models/Mapping/ContentWebModelMappingTests.cs # src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs # src/Umbraco.Tests/PublishedContent/NuCacheTests.cs # src/Umbraco.Tests/Scoping/ScopedNuCacheTests.cs # src/Umbraco.Web.BackOffice/Controllers/CodeFileController.cs # src/Umbraco.Web.BackOffice/Controllers/ContentController.cs # src/Umbraco.Web.BackOffice/Controllers/ContentTypeControllerBase.cs # src/Umbraco.Web.BackOffice/Controllers/DashboardController.cs # src/Umbraco.Web.BackOffice/Controllers/DataTypeController.cs # src/Umbraco.Web.BackOffice/Controllers/MediaController.cs # src/Umbraco.Web.BackOffice/Controllers/PackageInstallController.cs # src/Umbraco.Web.BackOffice/Controllers/TemplateQueryController.cs # src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs # src/Umbraco.Web.BackOffice/Trees/ContentTreeControllerBase.cs # src/Umbraco.Web.BackOffice/Trees/DataTypeTreeController.cs # src/Umbraco.Web.BackOffice/Trees/MediaTypeTreeController.cs # src/Umbraco.Web.Common/Extensions/FriendlyImageCropperTemplateExtensions.cs # src/Umbraco.Web.Common/Extensions/ImageCropperTemplateCoreExtensions.cs # src/Umbraco.Web.Common/Macros/MacroRenderer.cs # src/Umbraco.Web.UI.NetCore/umbraco/config/lang/da.xml # src/Umbraco.Web.UI/Umbraco/config/lang/it.xml # src/Umbraco.Web.UI/web.Template.Debug.config # src/Umbraco.Web.UI/web.Template.config # src/Umbraco.Web/Compose/NotificationsComponent.cs # src/Umbraco.Web/Composing/ModuleInjector.cs # src/Umbraco.Web/Editors/AuthenticationController.cs # src/Umbraco.Web/Editors/BackOfficeController.cs # src/Umbraco.Web/Editors/ContentTypeController.cs # src/Umbraco.Web/Editors/CurrentUserController.cs # src/Umbraco.Web/Editors/DictionaryController.cs # src/Umbraco.Web/Editors/MediaTypeController.cs # src/Umbraco.Web/Editors/MemberController.cs # src/Umbraco.Web/Editors/MemberGroupController.cs # src/Umbraco.Web/Editors/MemberTypeController.cs # src/Umbraco.Web/Editors/NuCacheStatusController.cs # src/Umbraco.Web/Editors/UserGroupsController.cs # src/Umbraco.Web/Editors/UsersController.cs # src/Umbraco.Web/HealthCheck/Checks/Config/AbstractConfigCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Config/CompilationDebugCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationService.cs # src/Umbraco.Web/HealthCheck/Checks/Config/CustomErrorsCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Config/MacroErrorsCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Config/NotificationEmailCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Config/TraceCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Config/TrySkipIisCustomErrorsCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Permissions/FolderAndFilePermissionsCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Security/BaseHttpHeaderCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs # src/Umbraco.Web/HealthCheck/Checks/Security/HttpsCheck.cs # src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs # src/Umbraco.Web/Models/Trees/MenuItemList.cs # src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentCacheDataModel.cs # src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs # src/Umbraco.Web/PublishedCache/NuCache/DataSource/SerializerBase.cs # src/Umbraco.Web/PublishedCache/NuCache/NuCacheComposer.cs # src/Umbraco.Web/Runtime/WebRuntime.cs # src/Umbraco.Web/Search/ExamineComponent.cs # src/Umbraco.Web/Trees/ApplicationTreeController.cs # src/Umbraco.Web/Trees/MemberTreeController.cs # src/Umbraco.Web/UrlHelperRenderExtensions.cs
2021-07-05 20:58:04 +02:00
var errors = new List<string>();
if (result == SignInResult.Success)
{
// Update any authentication tokens if succeeded
await _signInManager.UpdateExternalAuthenticationTokensAsync(loginInfo);
// Check if we are in an upgrade state, if so we need to redirect
if (_runtimeState.Level == Core.RuntimeLevel.Upgrade)
{
// redirect to the authorize upgrade endpoint which redirect to the installer
return RedirectToAction(nameof(AuthorizeUpgrade));
}
}
else if (result == SignInResult.TwoFactorRequired)
{
2020-12-02 12:22:08 +11:00
var attemptedUser = await _userManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey);
if (attemptedUser == null)
{
return new ValidationErrorResult($"No local user found for the login provider {loginInfo.LoginProvider} - {loginInfo.ProviderKey}");
}
var twofactorView = _backOfficeTwoFactorOptions.GetTwoFactorView(attemptedUser.UserName);
if (twofactorView.IsNullOrWhiteSpace())
{
return new ValidationErrorResult($"The registered {typeof(IBackOfficeTwoFactorOptions)} of type {_backOfficeTwoFactorOptions.GetType()} did not return a view for two factor auth ");
}
// create a with information to display a custom two factor send code view
var verifyResponse = new ObjectResult(new
{
twoFactorView = twofactorView,
userId = attemptedUser.Id
})
{
StatusCode = StatusCodes.Status402PaymentRequired
};
return verifyResponse;
}
else if (result == SignInResult.LockedOut)
{
2020-12-02 12:22:08 +11:00
errors.Add($"The local user {loginInfo.Principal.Identity.Name} for the external provider {loginInfo.ProviderDisplayName} is locked out.");
}
else if (result == SignInResult.NotAllowed)
{
2020-12-02 12:22:08 +11:00
// This occurs when SignInManager.CanSignInAsync fails which is when RequireConfirmedEmail , RequireConfirmedPhoneNumber or RequireConfirmedAccount fails
// however since we don't enforce those rules (yet) this shouldn't happen.
errors.Add($"The user {loginInfo.Principal.Identity.Name} for the external provider {loginInfo.ProviderDisplayName} has not confirmed their details and cannot sign in.");
}
else if (result == SignInResult.Failed)
{
// Failed only occurs when the user does not exist
errors.Add("The requested provider (" + loginInfo.LoginProvider + ") has not been linked to an account, the provider must be linked from the back office.");
}
else if (result == AutoLinkSignInResult.FailedNotLinked)
{
errors.Add("The requested provider (" + loginInfo.LoginProvider + ") has not been linked to an account, the provider must be linked from the back office.");
}
else if (result == AutoLinkSignInResult.FailedNoEmail)
2020-10-23 14:18:53 +11:00
{
errors.Add($"The requested provider ({loginInfo.LoginProvider}) has not provided the email claim {ClaimTypes.Email}, the account cannot be linked.");
2020-10-23 14:18:53 +11:00
}
else if (result is AutoLinkSignInResult autoLinkSignInResult && autoLinkSignInResult.Errors.Count > 0)
2020-10-23 14:18:53 +11:00
{
errors.AddRange(autoLinkSignInResult.Errors);
2020-10-23 14:18:53 +11:00
}
else if (!result.Succeeded)
{
// this shouldn't occur, the above should catch the correct error but we'll be safe just in case
errors.Add($"An unknown error with the requested provider ({loginInfo.LoginProvider}) occurred.");
}
2020-10-23 14:18:53 +11:00
if (errors.Count > 0)
2020-10-23 14:18:53 +11:00
{
ViewData.SetExternalSignInProviderErrors(
new BackOfficeExternalLoginProviderErrors(
loginInfo.LoginProvider,
errors));
2020-10-23 14:18:53 +11:00
}
return response();
2020-10-23 14:18:53 +11:00
}
private IActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return Redirect("/");
}
}
}