From 3d28552a77f110fe9e69ee85fbfa89914cb154a8 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 9 Feb 2022 10:19:39 +0100 Subject: [PATCH] Add settings to bypass 2fa for external logins (#11959) * Added settings for bypassing 2fa for external logins * Fixed issue with saving roles using member ID before the member had an ID. * Added missing extension method * Removed test classes from git * rollback csproj --- .../Configuration/Models/SecuritySettings.cs | 14 +++++ .../Security/MemberUserStore.cs | 3 + .../Controllers/BackOfficeController.cs | 55 ++++++++++++++++++- .../UmbracoBuilder.BackOfficeIdentity.cs | 10 ++++ src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 1 + .../Controllers/UmbExternalLoginController.cs | 9 ++- 6 files changed, 88 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs index 7d4dd45fb8..982ba8c63e 100644 --- a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs +++ b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs @@ -11,6 +11,8 @@ namespace Umbraco.Cms.Core.Configuration.Models [UmbracoOptions(Constants.Configuration.ConfigSecurity)] public class SecuritySettings { + internal const bool StaticMemberBypassTwoFactorForExternalLogins = true; + internal const bool StaticUserBypassTwoFactorForExternalLogins = true; internal const bool StaticKeepUserLoggedIn = false; internal const bool StaticHideDisabledUsersInBackOffice = false; internal const bool StaticAllowPasswordReset = true; @@ -66,5 +68,17 @@ namespace Umbraco.Cms.Core.Configuration.Models /// Gets or sets a value for the member password settings. /// public MemberPasswordConfigurationSettings MemberPassword { get; set; } + + /// + /// Gets or sets a value indicating whether to bypass the two factor requirement in Umbraco when using external login for members. Thereby rely on the External login and potential 2FA at that provider. + /// + [DefaultValue(StaticMemberBypassTwoFactorForExternalLogins)] + public bool MemberBypassTwoFactorForExternalLogins { get; set; } = StaticMemberBypassTwoFactorForExternalLogins; + + /// + /// Gets or sets a value indicating whether to bypass the two factor requirement in Umbraco when using external login for users. Thereby rely on the External login and potential 2FA at that provider. + /// + [DefaultValue(StaticUserBypassTwoFactorForExternalLogins)] + public bool UserBypassTwoFactorForExternalLogins { get; set; } = StaticUserBypassTwoFactorForExternalLogins; } } diff --git a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs index 4fba880e81..420d66b0b4 100644 --- a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs @@ -112,6 +112,9 @@ namespace Umbraco.Cms.Core.Security // create the member _memberService.Save(memberEntity); + //We need to add roles now that the member has an Id. It do not work implicit in UpdateMemberProperties + _memberService.AssignRoles(new[] { memberEntity.Id }, user.Roles.Select(x => x.RoleId).ToArray()); + if (!memberEntity.HasIdentity) { throw new DataException("Could not create the member, check logs for details"); diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs index aa5fc83e1e..e866409c17 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeController.cs @@ -10,6 +10,7 @@ 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; @@ -31,6 +32,7 @@ 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.DependencyInjection; using Umbraco.Cms.Web.Common.Filters; using Umbraco.Extensions; using Constants = Umbraco.Cms.Core.Constants; @@ -68,7 +70,10 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers private readonly IBackOfficeTwoFactorOptions _backOfficeTwoFactorOptions; private readonly IManifestParser _manifestParser; private readonly ServerVariablesParser _serverVariables; + private readonly IOptions _securitySettings; + + [ActivatorUtilitiesConstructor] public BackOfficeController( IBackOfficeUserManager userManager, IRuntimeState runtimeState, @@ -87,7 +92,8 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers IHttpContextAccessor httpContextAccessor, IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions, IManifestParser manifestParser, - ServerVariablesParser serverVariables) + ServerVariablesParser serverVariables, + IOptions securitySettings) { _userManager = userManager; _runtimeState = runtimeState; @@ -107,6 +113,51 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers _backOfficeTwoFactorOptions = backOfficeTwoFactorOptions; _manifestParser = manifestParser; _serverVariables = serverVariables; + _securitySettings = securitySettings; + } + + [Obsolete("Use ctor with all params. This overload will be removed in Umbraco 10.")] + public BackOfficeController( + IBackOfficeUserManager userManager, + IRuntimeState runtimeState, + IRuntimeMinifier runtimeMinifier, + IOptions globalSettings, + IHostingEnvironment hostingEnvironment, + ILocalizedTextService textService, + IGridConfig gridConfig, + BackOfficeServerVariables backOfficeServerVariables, + AppCaches appCaches, + IBackOfficeSignInManager signInManager, + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + ILogger logger, + IJsonSerializer jsonSerializer, + IBackOfficeExternalLoginProviders externalLogins, + IHttpContextAccessor httpContextAccessor, + IBackOfficeTwoFactorOptions backOfficeTwoFactorOptions, + IManifestParser manifestParser, + ServerVariablesParser serverVariables) + : this(userManager, + runtimeState, + runtimeMinifier, + globalSettings, + hostingEnvironment, + textService, + gridConfig, + backOfficeServerVariables, + appCaches, + signInManager, + backofficeSecurityAccessor, + logger, + jsonSerializer, + externalLogins, + httpContextAccessor, + backOfficeTwoFactorOptions, + manifestParser, + serverVariables, + StaticServiceProvider.Instance.GetRequiredService>() + ) + { + } [HttpGet] @@ -458,7 +509,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers 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); + SignInResult result = await _signInManager.ExternalLoginSignInAsync(loginInfo, isPersistent: false, bypassTwoFactor: _securitySettings.Value.UserBypassTwoFactorForExternalLogins); var errors = new List(); diff --git a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs index e9cc213598..1dc5bda7a9 100644 --- a/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs +++ b/src/Umbraco.Web.BackOffice/DependencyInjection/UmbracoBuilder.BackOfficeIdentity.cs @@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Net; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Security; using Umbraco.Cms.Web.BackOffice.Security; using Umbraco.Cms.Web.Common.AspNetCore; using Umbraco.Cms.Web.Common.Security; @@ -77,5 +78,14 @@ namespace Umbraco.Extensions return umbracoBuilder; } + public static BackOfficeIdentityBuilder AddTwoFactorProvider(this BackOfficeIdentityBuilder identityBuilder, string providerName) where T : class, ITwoFactorProvider + { + identityBuilder.Services.AddSingleton(); + identityBuilder.Services.AddSingleton(); + identityBuilder.AddTokenProvider>(providerName); + + return identityBuilder; + } + } } diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 69f812b6e6..46ff558aee 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Umbraco.Web.Website/Controllers/UmbExternalLoginController.cs b/src/Umbraco.Web.Website/Controllers/UmbExternalLoginController.cs index c43754e170..cb9188f5d0 100644 --- a/src/Umbraco.Web.Website/Controllers/UmbExternalLoginController.cs +++ b/src/Umbraco.Web.Website/Controllers/UmbExternalLoginController.cs @@ -8,7 +8,9 @@ using Microsoft.AspNetCore.Http.Extensions; 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.Models; using Umbraco.Cms.Core.Logging; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Routing; @@ -29,6 +31,7 @@ namespace Umbraco.Cms.Web.Website.Controllers { private readonly IMemberManager _memberManager; private readonly ITwoFactorLoginService _twoFactorLoginService; + private readonly IOptions _securitySettings; private readonly ILogger _logger; private readonly IMemberSignInManagerExternalLogins _memberSignInManager; @@ -42,7 +45,8 @@ namespace Umbraco.Cms.Web.Website.Controllers IPublishedUrlProvider publishedUrlProvider, IMemberSignInManagerExternalLogins memberSignInManager, IMemberManager memberManager, - ITwoFactorLoginService twoFactorLoginService) + ITwoFactorLoginService twoFactorLoginService, + IOptions securitySettings) : base( umbracoContextAccessor, databaseFactory, @@ -55,6 +59,7 @@ namespace Umbraco.Cms.Web.Website.Controllers _memberSignInManager = memberSignInManager; _memberManager = memberManager; _twoFactorLoginService = twoFactorLoginService; + _securitySettings = securitySettings; } /// @@ -95,7 +100,7 @@ namespace Umbraco.Cms.Web.Website.Controllers } else { - SignInResult result = await _memberSignInManager.ExternalLoginSignInAsync(loginInfo, false); + SignInResult result = await _memberSignInManager.ExternalLoginSignInAsync(loginInfo, false, _securitySettings.Value.MemberBypassTwoFactorForExternalLogins); if (result == SignInResult.Success) {