Populated user and member password configuration from settings file.

This commit is contained in:
Andy Butland
2019-12-23 14:35:39 +01:00
parent 8aa6fff801
commit 0670caee15
21 changed files with 324 additions and 29 deletions

View File

@@ -28,9 +28,6 @@ namespace Umbraco.Core
public static IUmbracoSettingsSection Settings(this Configs configs)
=> configs.GetConfig<IUmbracoSettingsSection>();
public static IUserPasswordConfiguration UserPasswordConfig(this Configs configs)
=> configs.GetConfig<IUserPasswordConfiguration>();
public static IHealthChecks HealthChecks(this Configs configs)
=> configs.GetConfig<IHealthChecks>();
@@ -40,11 +37,28 @@ namespace Umbraco.Core
public static ICoreDebug CoreDebug(this Configs configs)
=> configs.GetConfig<ICoreDebug>();
public static IUserPasswordConfiguration UserPasswordConfiguration(this Configs configs)
=> configs.GetConfig<IUserPasswordConfiguration>();
public static IMemberPasswordConfiguration MemberPasswordConfiguration(this Configs configs)
=> configs.GetConfig<IMemberPasswordConfiguration>();
public static void AddPasswordConfigurations(this Configs configs)
{
configs.Add<IUserPasswordConfiguration>(() =>
{
return new UserPasswordConfiguration(configs.Settings().Security.UserPasswordConfiguration);
});
configs.Add<IMemberPasswordConfiguration>(() =>
{
return new MemberPasswordConfiguration(configs.Settings().Security.MemberPasswordConfiguration);
});
}
public static void AddCoreConfigs(this Configs configs, IIOHelper ioHelper)
{
var configDir = new DirectoryInfo(ioHelper.MapPath(Constants.SystemDirectories.Config));
// GridConfig depends on runtime caches, manifest parsers... and cannot be available during composition
configs.Add<IGridConfig>(factory => new GridConfig(
factory.GetInstance<ILogger>(),

View File

@@ -0,0 +1,15 @@
using Umbraco.Core.Configuration.UmbracoSettings;
namespace Umbraco.Core.Configuration
{
/// <summary>
/// The password configuration for back office users
/// </summary>
public class MemberPasswordConfiguration : PasswordConfiguration, IMemberPasswordConfiguration
{
public MemberPasswordConfiguration(IMemberPasswordConfigurationSection configSection)
: base(configSection)
{
}
}
}

View File

@@ -0,0 +1,41 @@
using System;
using Umbraco.Core.Configuration.UmbracoSettings;
namespace Umbraco.Core.Configuration
{
public abstract class PasswordConfiguration : IPasswordConfiguration
{
protected PasswordConfiguration(IPasswordConfigurationSection configSection)
{
if (configSection == null)
{
throw new ArgumentNullException(nameof(configSection));
}
RequiredLength = configSection.RequiredLength;
RequireNonLetterOrDigit = configSection.RequireNonLetterOrDigit;
RequireDigit = configSection.RequireDigit;
RequireLowercase = configSection.RequireLowercase;
RequireUppercase = configSection.RequireUppercase;
UseLegacyEncoding = configSection.UseLegacyEncoding;
HashAlgorithmType = configSection.HashAlgorithmType;
MaxFailedAccessAttemptsBeforeLockout = configSection.MaxFailedAccessAttemptsBeforeLockout;
}
public int RequiredLength { get; }
public bool RequireNonLetterOrDigit { get; }
public bool RequireDigit { get; }
public bool RequireLowercase { get; }
public bool RequireUppercase { get; }
public bool UseLegacyEncoding { get; }
public string HashAlgorithmType { get; }
public int MaxFailedAccessAttemptsBeforeLockout { get; }
}
}

View File

@@ -0,0 +1,6 @@
namespace Umbraco.Core.Configuration.UmbracoSettings
{
public interface IMemberPasswordConfigurationSection : IPasswordConfigurationSection
{
}
}

View File

@@ -0,0 +1,21 @@
namespace Umbraco.Core.Configuration.UmbracoSettings
{
public interface IPasswordConfigurationSection : IUmbracoConfigurationSection
{
int RequiredLength { get; }
bool RequireNonLetterOrDigit { get; }
bool RequireDigit { get; }
bool RequireLowercase { get; }
bool RequireUppercase { get; }
bool UseLegacyEncoding { get; }
string HashAlgorithmType { get; }
int MaxFailedAccessAttemptsBeforeLockout { get; }
}
}

View File

@@ -3,7 +3,7 @@
public interface ISecuritySection : IUmbracoConfigurationSection
{
bool KeepUserLoggedIn { get; }
bool HideDisabledUsersInBackoffice { get; }
/// <summary>
@@ -23,5 +23,9 @@
/// When this is false, the username and email fields will be shown in the user section.
/// </remarks>
bool UsernameIsEmail { get; }
IUserPasswordConfigurationSection UserPasswordConfiguration { get; }
IMemberPasswordConfigurationSection MemberPasswordConfiguration { get; }
}
}

View File

@@ -0,0 +1,6 @@
namespace Umbraco.Core.Configuration.UmbracoSettings
{
public interface IUserPasswordConfigurationSection : IPasswordConfigurationSection
{
}
}

View File

@@ -0,0 +1,15 @@
using Umbraco.Core.Configuration.UmbracoSettings;
namespace Umbraco.Core.Configuration
{
/// <summary>
/// The password configuration for back office users
/// </summary>
public class UserPasswordConfiguration : PasswordConfiguration, IUserPasswordConfiguration
{
public UserPasswordConfiguration(IUserPasswordConfigurationSection configSection)
: base(configSection)
{
}
}
}

View File

@@ -13,6 +13,7 @@ namespace Umbraco.Core.Configuration
}
public IHostingSettings HostingSettings { get; } = new HostingSettings();
public ICoreDebug CoreDebug { get; } = new CoreDebug();
public IUmbracoSettingsSection UmbracoSettings { get; }
@@ -21,33 +22,19 @@ namespace Umbraco.Core.Configuration
{
var configs = new Configs(section => ConfigurationManager.GetSection(section));
configs.Add<IGlobalSettings>(() => new GlobalSettings(ioHelper));
configs.Add<IHostingSettings>(() => HostingSettings);
configs.Add(() => HostingSettings);
configs.Add<IUmbracoSettingsSection>("umbracoConfiguration/settings");
configs.Add<IHealthChecks>("umbracoConfiguration/HealthChecks");
configs.Add<IUserPasswordConfiguration>(() => new DefaultPasswordConfig());
configs.Add<IMemberPasswordConfiguration>(() => new DefaultPasswordConfig());
configs.Add<ICoreDebug>(() => CoreDebug);
// Password configuration is held within IUmbracoSettingsSection from umbracoConfiguration/settings but we'll add explicitly
// so it can be independently retrieved in classes that need it.
configs.AddPasswordConfigurations();
configs.Add(() => CoreDebug);
configs.Add<IConnectionStrings>(() => new ConnectionStrings());
configs.AddCoreConfigs(ioHelper);
return configs;
}
}
// Default/static user password configs
// TODO: Make this configurable somewhere - we've removed membership providers for users, so could be a section in the umbracosettings.config file?
// keeping in mind that we will also be removing the members membership provider so there will be 2x the same/similar configuration.
// TODO: Currently it doesn't actually seem possible to replace any sub-configuration unless totally replacing the IConfigsFactory??
internal class DefaultPasswordConfig : IUserPasswordConfiguration, IMemberPasswordConfiguration
{
public int RequiredLength => 12;
public bool RequireNonLetterOrDigit => false;
public bool RequireDigit => false;
public bool RequireLowercase => false;
public bool RequireUppercase => false;
public bool UseLegacyEncoding => false;
public string HashAlgorithmType => "HMACSHA256";
public int MaxFailedAccessAttemptsBeforeLockout => 5;
}
}

View File

@@ -0,0 +1,6 @@
namespace Umbraco.Core.Configuration.UmbracoSettings
{
internal class MemberPasswordConfigurationElement : PasswordConfigurationElement, IMemberPasswordConfigurationSection
{
}
}

View File

@@ -0,0 +1,31 @@
using System.Configuration;
namespace Umbraco.Core.Configuration.UmbracoSettings
{
internal class PasswordConfigurationElement : UmbracoConfigurationElement
{
[ConfigurationProperty("requiredLength", DefaultValue = "12")]
public int RequiredLength => (int)base["requiredLength"];
[ConfigurationProperty("requireNonLetterOrDigit", DefaultValue = "false")]
public bool RequireNonLetterOrDigit => (bool)base["requireNonLetterOrDigit"];
[ConfigurationProperty("requireDigit", DefaultValue = "false")]
public bool RequireDigit => (bool)base["requireDigit"];
[ConfigurationProperty("requireLowercase", DefaultValue = "false")]
public bool RequireLowercase => (bool)base["requireLowercase"];
[ConfigurationProperty("requireUppercase", DefaultValue = "false")]
public bool RequireUppercase => (bool)base["requireUppercase"];
[ConfigurationProperty("useLegacyEncoding", DefaultValue = "false")]
public bool UseLegacyEncoding => (bool)base["useLegacyEncoding"];
[ConfigurationProperty("hashAlgorithmType", DefaultValue = "HMACSHA256")]
public string HashAlgorithmType => (string)base["hashAlgorithmType"];
[ConfigurationProperty("maxFailedAccessAttemptsBeforeLockout", DefaultValue = "5")]
public int MaxFailedAccessAttemptsBeforeLockout => (int)base["maxFailedAccessAttemptsBeforeLockout"];
}
}

View File

@@ -32,6 +32,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
[ConfigurationProperty("authCookieDomain")]
internal InnerTextConfigurationElement<string> AuthCookieDomain => GetOptionalTextElement<string>("authCookieDomain", null);
[ConfigurationProperty("userPasswordConfiguration")]
public UserPasswordConfigurationElement UserPasswordConfiguration => (UserPasswordConfigurationElement)this["userPasswordConfiguration"];
[ConfigurationProperty("memberPasswordConfiguration")]
public MemberPasswordConfigurationElement MemberPasswordConfiguration => (MemberPasswordConfigurationElement)this["memberPasswordConfiguration"];
bool ISecuritySection.KeepUserLoggedIn => KeepUserLoggedIn;
bool ISecuritySection.HideDisabledUsersInBackoffice => HideDisabledUsersInBackoffice;
@@ -53,5 +59,9 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
string ISecuritySection.AuthCookieName => AuthCookieName;
string ISecuritySection.AuthCookieDomain => AuthCookieDomain;
IUserPasswordConfigurationSection ISecuritySection.UserPasswordConfiguration => UserPasswordConfiguration;
IMemberPasswordConfigurationSection ISecuritySection.MemberPasswordConfiguration => MemberPasswordConfiguration;
}
}

View File

@@ -0,0 +1,6 @@
namespace Umbraco.Core.Configuration.UmbracoSettings
{
internal class UserPasswordConfigurationElement : PasswordConfigurationElement, IUserPasswordConfigurationSection
{
}
}

View File

@@ -35,5 +35,101 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings
{
Assert.IsTrue(SettingsSection.Security.AuthCookieName == "UMB_UCONTEXT");
}
[Test]
public void UserPasswordConfiguration_RequiredLength()
{
Assert.IsTrue(SettingsSection.Security.UserPasswordConfiguration.RequiredLength == 12);
}
[Test]
public void UserPasswordConfiguration_RequireNonLetterOrDigit()
{
Assert.IsTrue(SettingsSection.Security.UserPasswordConfiguration.RequireNonLetterOrDigit == false);
}
[Test]
public void UserPasswordConfiguration_RequireDigit()
{
Assert.IsTrue(SettingsSection.Security.UserPasswordConfiguration.RequireDigit == false);
}
[Test]
public void UserPasswordConfiguration_RequireLowercase()
{
Assert.IsTrue(SettingsSection.Security.UserPasswordConfiguration.RequireLowercase == false);
}
[Test]
public void UserPasswordConfiguration_RequireUppercase()
{
Assert.IsTrue(SettingsSection.Security.UserPasswordConfiguration.RequireUppercase == false);
}
[Test]
public void UserPasswordConfiguration_UseLegacyEncoding()
{
Assert.IsTrue(SettingsSection.Security.UserPasswordConfiguration.UseLegacyEncoding == false);
}
[Test]
public void UserPasswordConfiguration_HashAlgorithmType()
{
Assert.IsTrue(SettingsSection.Security.UserPasswordConfiguration.HashAlgorithmType == "HMACSHA256");
}
[Test]
public void UserPasswordConfiguration_MaxFailedAccessAttemptsBeforeLockout()
{
Assert.IsTrue(SettingsSection.Security.UserPasswordConfiguration.MaxFailedAccessAttemptsBeforeLockout == 5);
}
[Test]
public void MemberPasswordConfiguration_RequiredLength()
{
Assert.IsTrue(SettingsSection.Security.MemberPasswordConfiguration.RequiredLength == 12);
}
[Test]
public void MemberPasswordConfiguration_RequireNonLetterOrDigit()
{
Assert.IsTrue(SettingsSection.Security.MemberPasswordConfiguration.RequireNonLetterOrDigit == false);
}
[Test]
public void MemberPasswordConfiguration_RequireDigit()
{
Assert.IsTrue(SettingsSection.Security.MemberPasswordConfiguration.RequireDigit == false);
}
[Test]
public void MemberPasswordConfiguration_RequireLowercase()
{
Assert.IsTrue(SettingsSection.Security.MemberPasswordConfiguration.RequireLowercase == false);
}
[Test]
public void MemberPasswordConfiguration_RequireUppercase()
{
Assert.IsTrue(SettingsSection.Security.MemberPasswordConfiguration.RequireUppercase == false);
}
[Test]
public void MemberPasswordConfiguration_UseLegacyEncoding()
{
Assert.IsTrue(SettingsSection.Security.MemberPasswordConfiguration.UseLegacyEncoding == false);
}
[Test]
public void MemberPasswordConfiguration_HashAlgorithmType()
{
Assert.IsTrue(SettingsSection.Security.MemberPasswordConfiguration.HashAlgorithmType == "HMACSHA256");
}
[Test]
public void MemberPasswordConfiguration_MaxFailedAccessAttemptsBeforeLockout()
{
Assert.IsTrue(SettingsSection.Security.MemberPasswordConfiguration.MaxFailedAccessAttemptsBeforeLockout == 5);
}
}
}

View File

@@ -14,7 +14,7 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings
[Test]
public void InternalRedirectPreservesTemplate()
{
Assert.IsTrue(SettingsSection.WebRouting.TrySkipIisCustomErrors == false);
Assert.IsTrue(SettingsSection.WebRouting.InternalRedirectPreservesTemplate == false);
}
[Test]

View File

@@ -69,6 +69,13 @@
<!-- set to true to enable the UI and API to allow back-office users to reset their passwords -->
<allowPasswordReset>true</allowPasswordReset>
<userPasswordConfiguration
requiredLength="12" requireNonLetterOrDigit="false" requireDigit="false" requireLowercase="false" requireUppercase="false"
useLegacyEncoding="false" hashAlgorithmType="HMACSHA256" maxFailedAccessAttemptsBeforeLockout="5" />
<memberPasswordConfiguration
requiredLength="12" requireNonLetterOrDigit="false" requireDigit="false" requireLowercase="false" requireUppercase="false"
useLegacyEncoding="false" hashAlgorithmType="HMACSHA256" maxFailedAccessAttemptsBeforeLockout="5" />
</security>
<requestHandler>

View File

@@ -0,0 +1,23 @@
using Umbraco.Core.Configuration;
namespace Umbraco.Tests.TestHelpers.Stubs
{
internal class TestUserPasswordConfig : IUserPasswordConfiguration
{
public int RequiredLength => 12;
public bool RequireNonLetterOrDigit => false;
public bool RequireDigit => false;
public bool RequireLowercase => false;
public bool RequireUppercase => false;
public bool UseLegacyEncoding => false;
public string HashAlgorithmType => "HMACSHA256";
public int MaxFailedAccessAttemptsBeforeLockout => 5;
}
}

View File

@@ -168,6 +168,7 @@
<Compile Include="Services\PropertyValidationServiceTests.cs" />
<Compile Include="Templates\HtmlLocalLinkParserTests.cs" />
<Compile Include="TestHelpers\RandomIdRamDirectory.cs" />
<Compile Include="TestHelpers\Stubs\TestUserPasswordConfig.cs" />
<Compile Include="Testing\Objects\TestDataSource.cs" />
<Compile Include="Published\PublishedSnapshotTestObjects.cs" />
<Compile Include="Published\ModelTypeTests.cs" />

View File

@@ -16,7 +16,6 @@ using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Mapping;
using Umbraco.Core.Persistence;
@@ -26,6 +25,7 @@ using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Services;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.ControllerTesting;
using Umbraco.Tests.TestHelpers.Stubs;
using Umbraco.Tests.Testing;
using Umbraco.Web;
using Umbraco.Web.Editors;
@@ -76,7 +76,7 @@ namespace Umbraco.Tests.Web.Controllers
}
IOHelper.ForceNotHosted = true;
var usersController = new AuthenticationController(
new DefaultPasswordConfig(),
new TestUserPasswordConfig(),
Factory.GetInstance<IGlobalSettings>(),
umbracoContextAccessor,
Factory.GetInstance<ISqlContext>(),

View File

@@ -63,6 +63,12 @@
<usernameIsEmail>true</usernameIsEmail>
<!-- change in 4.8: Disabled users are now showed dimmed and last in the tree. If you prefer not to display them set this to true -->
<hideDisabledUsersInBackoffice>false</hideDisabledUsersInBackoffice>
<userPasswordConfiguration
requiredLength="12" requireNonLetterOrDigit="false" requireDigit="false" requireLowercase="false" requireUppercase="false"
useLegacyEncoding="false" hashAlgorithmType="HMACSHA256" maxFailedAccessAttemptsBeforeLockout="5" />
<memberPasswordConfiguration
requiredLength="12" requireNonLetterOrDigit="false" requireDigit="false" requireLowercase="false" requireUppercase="false"
useLegacyEncoding="false" hashAlgorithmType="HMACSHA256" maxFailedAccessAttemptsBeforeLockout="5" />
</security>
<requestHandler>

View File

@@ -29,7 +29,7 @@ namespace Umbraco.Web
protected IUmbracoContextAccessor UmbracoContextAccessor => Current.UmbracoContextAccessor;
protected IGlobalSettings GlobalSettings => Current.Configs.Global();
protected IUmbracoSettingsSection UmbracoSettings => Current.Configs.Settings();
protected IUserPasswordConfiguration UserPasswordConfig => Current.Configs.UserPasswordConfig();
protected IUserPasswordConfiguration UserPasswordConfig => Current.Configs.UserPasswordConfiguration();
protected IRuntimeState RuntimeState => Core.Composing.Current.RuntimeState;
protected ServiceContext Services => Current.Services;
protected UmbracoMapper Mapper => Current.Mapper;