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

595 lines
25 KiB
C#
Raw Normal View History

using System.Globalization;
using System.Net;
using System.Security.Claims;
using System.Security.Principal;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core;
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.Install;
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 SignInResult = Microsoft.AspNetCore.Identity.SignInResult;
namespace Umbraco.Cms.Web.BackOffice.Controllers;
[DisableBrowserCache]
[UmbracoRequireHttps]
[PluginController(Constants.Web.Mvc.BackOfficeArea)]
[IsBackOffice]
public class BackOfficeController : UmbracoController
{
private readonly AppCaches _appCaches;
private readonly IBackOfficeSecurityAccessor _backofficeSecurityAccessor;
private readonly BackOfficeServerVariables _backOfficeServerVariables;
private readonly IBackOfficeTwoFactorOptions _backOfficeTwoFactorOptions;
private readonly IBackOfficeExternalLoginProviders _externalLogins;
private readonly IGridConfig _gridConfig;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IJsonSerializer _jsonSerializer;
private readonly ILogger<BackOfficeController> _logger;
private readonly IManifestParser _manifestParser;
private readonly IRuntimeMinifier _runtimeMinifier;
private readonly IRuntimeState _runtimeState;
private readonly IOptions<SecuritySettings> _securitySettings;
private readonly ServerVariablesParser _serverVariables;
private readonly IBackOfficeSignInManager _signInManager;
private readonly ILocalizedTextService _textService;
// 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 GlobalSettings _globalSettings;
[ActivatorUtilitiesConstructor]
public BackOfficeController(
IBackOfficeUserManager userManager,
IRuntimeState runtimeState,
IRuntimeMinifier runtimeMinifier,
IOptionsSnapshot<GlobalSettings> globalSettings,
IHostingEnvironment hostingEnvironment,
ILocalizedTextService textService,
IGridConfig gridConfig,
BackOfficeServerVariables backOfficeServerVariables,
AppCaches appCaches,
IBackOfficeSignInManager signInManager,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
ILogger<BackOfficeController> logger,
IJsonSerializer jsonSerializer,
IBackOfficeExternalLoginProviders externalLogins,
IHttpContextAccessor httpContextAccessor,
IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions,
IManifestParser manifestParser,
ServerVariablesParser serverVariables,
IOptions<SecuritySettings> securitySettings)
{
_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;
_jsonSerializer = jsonSerializer;
_externalLogins = externalLogins;
_httpContextAccessor = httpContextAccessor;
_backOfficeTwoFactorOptions = backOfficeTwoFactorOptions;
_manifestParser = manifestParser;
_serverVariables = serverVariables;
_securitySettings = securitySettings;
}
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> Default()
{
// Check if we not are in an run state, if so we need to redirect
if (_runtimeState.Level != RuntimeLevel.Run)
{
return Redirect("/");
}
// force authentication to occur since this is not an authorized endpoint
AuthenticateResult result = await this.AuthenticateBackOfficeAsync();
2021-07-26 16:55:41 -06:00
var viewPath = Path.Combine(Constants.SystemDirectories.Umbraco, 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)
{
AuthenticateResult authenticate = await this.AuthenticateBackOfficeAsync();
//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 (authenticate.Succeeded)
{
await _signInManager.SignOutAsync();
}
if (invite == null)
{
_logger.LogWarning("VerifyUser endpoint reached with invalid token: NULL");
return RedirectToAction(nameof(Default));
}
var parts = 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];
BackOfficeIdentityUser? identityUser = await _userManager.FindByIdAsync(id);
if (identityUser == null)
{
_logger.LogWarning("VerifyUser endpoint reached with non existing user: {UserId}", id);
return RedirectToAction(nameof(Default));
}
IdentityResult 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);
//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(HttpStatusCode.ServiceUnavailable)]
[AllowAnonymous]
public async Task<IActionResult> AuthorizeUpgrade()
{
// 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<InstallController>(), 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");
return await RenderDefaultOrProcessExternalLoginAsync(
result,
//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>
/// <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 async Task<Dictionary<string, Dictionary<string, string>>> LocalizedText(string? culture = null)
{
CultureInfo? cultureInfo;
if (string.IsNullOrWhiteSpace(culture))
{
// Force authentication to occur since this is not an authorized endpoint, we need this to get a user.
AuthenticateResult authenticationResult = await this.AuthenticateBackOfficeAsync();
// We have to get the culture from the Identity, we can't rely on thread culture
// It's entirely likely for a user to have a different culture in the backoffice, than their system.
IIdentity? user = authenticationResult.Principal?.Identity;
cultureInfo = authenticationResult.Succeeded && user is not null
? user.GetCulture()
: CultureInfo.GetCultureInfo(_globalSettings.DefaultUILanguage);
}
else
{
cultureInfo = CultureInfo.GetCultureInfo(culture);
}
IDictionary<string, string> allValues = _textService.GetAllStoredValues(cultureInfo!);
var pathedValues = allValues.Select(kv =>
{
var slashIndex = kv.Key.IndexOf('/');
var areaAlias = kv.Key[..slashIndex];
var valueAlias = kv.Key[(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)]
[AngularJsonOnlyConfiguration]
[HttpGet]
public IEnumerable<IGridEditorConfig> GetGridConfig() => _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()
{
// cache the result if debugging is disabled
var serverVars = await _serverVariables.ParseAsync(await _backOfficeServerVariables.GetServerVariablesAsync());
var result = _hostingEnvironment.IsDebugMode
? serverVars
: _appCaches.RuntimeCache.GetCacheItem(
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 || Uri.TryCreate(redirectUrl, UriKind.Absolute, out _))
{
redirectUrl = Url.Action(nameof(Default), this.GetControllerName());
}
// Configures the redirect URL and user identifier for the specified external login
AuthenticationProperties 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
AuthenticationProperties 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)
{
BackOfficeIdentityUser? user = await _userManager.FindByIdAsync(userId.ToString(CultureInfo.InvariantCulture));
if (user != null)
{
var result = await _userManager.VerifyUserTokenAsync(user, "Default", "ResetPassword", resetCode);
if (result)
{
//Add a flag and redirect for it to be displayed
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
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()
{
V11/feature/update to dotnet 7 (#12712) * Update projects to .NET 7 * Fix nullability errors * Fix up pipelines to run 7.0 * Update langversion to preview * Revert "Fix up pipelines to run 7.0" This reverts commit d0fa8d01b8126a4eaa59832a3814a567705419ae. * Fix up pipelines again, this time without indentation changes * Include preview versions * Versions not Version * Fix ModelTypeTests * Fix MemberPasswordHasherTests Microsoft wants to use SHA512 instead of SHA256, so our old hashes will return SuccessRehashNeeded now * Use dotnet cli instead of nuget restore * Update src/Umbraco.Web.UI/Umbraco.Web.UI.csproj * Update dependencies * Fix nullability issues * Fix unit test * Fix nullability in ChangingPasswordModel OldPassword can be null, if we're changing the password with password reset enabled. Additionally, we might as well use the new required keyword instead of supressing null. * Use required keyword instead of supressing null * Fix up pipelines again * fix up spelling-error * Use dotnet cli instead of nuget restore * Fix up another NuGet command * Use dotnet version 7 before building * Include preview versions * Remove condition * Use dotnet 7 before running powershell script * Update templates to .net 7 * Download version 7 before running linux container * Move use dotnet 7 even earlier in E2E process * Remove dotnet 7 * Reintroduce .NET 7 task * Update linux docker container and remove dotnet 7 from yml * Fix up dockerfile with ARG * Fix up docker file with nightly builds of dotnet 7 * Reintroduce dotnet 7 so windows can use it * Use aspnet 7 in docker Co-authored-by: Nikolaj <nikolajlauridsen@protonmail.ch> Co-authored-by: Zeegaan <nge@umbraco.dk>
2022-08-23 11:31:05 +02:00
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);
if (addLoginResult.Succeeded)
{
// Update any authentication tokens if succeeded
await _signInManager.UpdateExternalAuthenticationTokensAsync(info);
return RedirectToLocal(Url.Action(nameof(Default), this.GetControllerName()));
}
2020-10-23 14:18:53 +11:00
//Add errors and redirect for it to be displayed
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));
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
//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) ||
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
ExternalLoginInfo? loginInfo = await _signInManager.GetExternalLoginInfoAsync();
if (loginInfo == null || loginInfo.Principal == null)
{
// if the user is not logged in, check if there's any auto login redirects specified
if (!authenticateResult.Succeeded)
{
var oauthRedirectAuthProvider = _externalLogins.GetAutoLoginProvider();
if (!oauthRedirectAuthProvider.IsNullOrWhiteSpace())
{
return ExternalLogin(oauthRedirectAuthProvider!);
}
}
2020-12-02 12:22:08 +11:00
return defaultResponse();
}
2020-12-02 12:22:08 +11:00
//we're just logging in with an external source, not linking accounts
return await ExternalSignInAsync(loginInfo, externalSignInResponse);
}
2020-12-02 12:22:08 +11:00
private async Task<IActionResult> ExternalSignInAsync(ExternalLoginInfo loginInfo, Func<IActionResult> response)
{
if (loginInfo == null)
{
throw new ArgumentNullException(nameof(loginInfo));
}
2020-12-02 12:22:08 +11:00
if (response == null)
{
throw new ArgumentNullException(nameof(response));
}
2020-12-02 12:22:08 +11:00
// Sign in the user with this external login provider (which auto links, etc...)
SignInResult result = await _signInManager.ExternalLoginSignInAsync(loginInfo, false, _securitySettings.Value.UserBypassTwoFactorForExternalLogins);
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 == RuntimeLevel.Upgrade)
2020-10-23 14:18:53 +11:00
{
// redirect to the the installer
return Redirect("/");
2020-10-23 14:18:53 +11:00
}
}
else if (result == SignInResult.TwoFactorRequired)
{
V11/feature/update to dotnet 7 (#12712) * Update projects to .NET 7 * Fix nullability errors * Fix up pipelines to run 7.0 * Update langversion to preview * Revert "Fix up pipelines to run 7.0" This reverts commit d0fa8d01b8126a4eaa59832a3814a567705419ae. * Fix up pipelines again, this time without indentation changes * Include preview versions * Versions not Version * Fix ModelTypeTests * Fix MemberPasswordHasherTests Microsoft wants to use SHA512 instead of SHA256, so our old hashes will return SuccessRehashNeeded now * Use dotnet cli instead of nuget restore * Update src/Umbraco.Web.UI/Umbraco.Web.UI.csproj * Update dependencies * Fix nullability issues * Fix unit test * Fix nullability in ChangingPasswordModel OldPassword can be null, if we're changing the password with password reset enabled. Additionally, we might as well use the new required keyword instead of supressing null. * Use required keyword instead of supressing null * Fix up pipelines again * fix up spelling-error * Use dotnet cli instead of nuget restore * Fix up another NuGet command * Use dotnet version 7 before building * Include preview versions * Remove condition * Use dotnet 7 before running powershell script * Update templates to .net 7 * Download version 7 before running linux container * Move use dotnet 7 even earlier in E2E process * Remove dotnet 7 * Reintroduce .NET 7 task * Update linux docker container and remove dotnet 7 from yml * Fix up dockerfile with ARG * Fix up docker file with nightly builds of dotnet 7 * Reintroduce dotnet 7 so windows can use it * Use aspnet 7 in docker Co-authored-by: Nikolaj <nikolajlauridsen@protonmail.ch> Co-authored-by: Zeegaan <nge@umbraco.dk>
2022-08-23 11:31:05 +02:00
BackOfficeIdentityUser? attemptedUser = await _userManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey);
if (attemptedUser?.UserName is null)
{
return new ValidationErrorResult(
$"No local user found for the login provider {loginInfo.LoginProvider} - {loginInfo.ProviderKey}");
}
2020-10-23 14:18:53 +11:00
var twofactorView = _backOfficeTwoFactorOptions.GetTwoFactorView(attemptedUser.UserName);
if (twofactorView.IsNullOrWhiteSpace())
2020-10-23 14:18:53 +11:00
{
return new ValidationErrorResult(
$"The registered {typeof(IBackOfficeTwoFactorOptions)} of type {_backOfficeTwoFactorOptions.GetType()} did not return a view for two factor auth ");
2020-10-23 14:18:53 +11:00
}
// 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)
{
errors.Add(
$"The local user {loginInfo.Principal.Identity?.Name} for the external provider {loginInfo.ProviderDisplayName} is locked out.");
}
else if (result == SignInResult.NotAllowed)
{
// 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 == ExternalLoginSignInResult.NotAllowed)
{
// This occurs when the external provider has approved the login but custom logic in OnExternalLogin has denined it.
errors.Add(
$"The user {loginInfo.Principal.Identity?.Name} for the external provider {loginInfo.ProviderDisplayName} has not been accepted and cannot sign in.");
}
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)
{
errors.Add(
$"The requested provider ({loginInfo.LoginProvider}) has not provided the email claim {ClaimTypes.Email}, the account cannot be linked.");
}
else if (result is AutoLinkSignInResult autoLinkSignInResult && autoLinkSignInResult.Errors.Count > 0)
{
errors.AddRange(autoLinkSignInResult.Errors);
}
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)
{
ViewData.SetExternalSignInProviderErrors(
new BackOfficeExternalLoginProviderErrors(
loginInfo.LoginProvider,
errors));
}
return response();
}
private IActionResult RedirectToLocal(string? returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return Redirect("/");
}
}