diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index fda4c23dc0..9bcdedca70 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -62,6 +62,7 @@ public const string AspNetCoreV3PasswordHashAlgorithmName = "PBKDF2.ASPNETCORE.V3"; public const string AspNetCoreV2PasswordHashAlgorithmName = "PBKDF2.ASPNETCORE.V2"; public const string AspNetUmbraco8PasswordHashAlgorithmName = "HMACSHA256"; + public const string AspNetUmbraco4PasswordHashAlgorithmName = "HMACSHA1"; } } } diff --git a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs index 9b14c3ccba..d72ecda56c 100644 --- a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs +++ b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Security.Cryptography; using System.Text; using Umbraco.Core.Configuration; @@ -11,66 +12,115 @@ namespace Umbraco.Core.Security /// public class LegacyPasswordSecurity { - // TODO: This class no longer has the logic available to verify the old old old password format, we should - // include this ability so that upgrades for very old versions/data can work and then auto-migrate to the new password format. - - private readonly IPasswordConfiguration _passwordConfiguration; - private readonly PasswordGenerator _generator; - - /// - /// Constructor - /// - /// - public LegacyPasswordSecurity(IPasswordConfiguration passwordConfiguration) - { - _passwordConfiguration = passwordConfiguration; - _generator = new PasswordGenerator(passwordConfiguration); - } - - public string GeneratePassword() => _generator.GeneratePassword(); - - /// - /// Returns a hashed password value used to store in a data store - /// - /// - /// - public string HashPasswordForStorage(string password) + // Used for tests + [EditorBrowsable(EditorBrowsableState.Never)] + public string HashPasswordForStorage(string algorithmType, string password) { if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("password cannot be empty", nameof(password)); string salt; - var hashed = HashNewPassword(password, out salt); - return FormatPasswordForStorage(hashed, salt); + var hashed = HashNewPassword(algorithmType, password, out salt); + return FormatPasswordForStorage(algorithmType, hashed, salt); + } + + // Used for tests + [EditorBrowsable(EditorBrowsableState.Never)] + public string FormatPasswordForStorage(string algorithmType, string hashedPassword, string salt) + { + if (IsLegacySHA1Algorithm(algorithmType)) + { + return hashedPassword; + } + + return salt + hashedPassword; } /// - /// If the password format is a hashed keyed algorithm then we will pre-pend the salt used to hash the password - /// to the hashed password itself. + /// Verifies if the password matches the expected hash+salt of the stored password string /// - /// + /// The hashing algorithm for the stored password. + /// The password. + /// The value of the password stored in a data store. + /// + public bool VerifyPassword(string algorithm, string password, string dbPassword) + { + if (string.IsNullOrWhiteSpace(dbPassword)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(dbPassword)); + + if (dbPassword.StartsWith(Constants.Security.EmptyPasswordPrefix)) + return false; + + var storedHashedPass = ParseStoredHashPassword(algorithm, dbPassword, out var salt); + var hashed = HashPassword(algorithm, password, salt); + return storedHashedPass == hashed; + } + + /// + /// Create a new password hash and a new salt + /// + /// The hashing algorithm for the password. + /// /// /// - public string FormatPasswordForStorage(string hashedPassword, string salt) + // TODO: Do we need this method? We shouldn't be using this class to create new password hashes for storage + public string HashNewPassword(string algorithm, string newPassword, out string salt) { - return salt + hashedPassword; + salt = GenerateSalt(); + return HashPassword(algorithm, newPassword, salt); + } + + /// + /// Parses out the hashed password and the salt from the stored password string value + /// + /// The hashing algorithm for the stored password. + /// + /// returns the salt + /// + public string ParseStoredHashPassword(string algorithm, string storedString, out string salt) + { + if (string.IsNullOrWhiteSpace(storedString)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(storedString)); + + // This is for the <= v4 hashing algorithm for which there was no salt + if (IsLegacySHA1Algorithm(algorithm)) + { + salt = string.Empty; + return storedString; + } + + + var saltLen = GenerateSalt(); + salt = storedString.Substring(0, saltLen.Length); + return storedString.Substring(saltLen.Length); + } + + public static string GenerateSalt() + { + var numArray = new byte[16]; + new RNGCryptoServiceProvider().GetBytes(numArray); + return Convert.ToBase64String(numArray); } /// /// Hashes a password with a given salt /// + /// The hashing algorithm for the password. /// /// /// - public string HashPassword(string pass, string salt) + private string HashPassword(string algorithmType, string pass, string salt) { + if (IsLegacySHA1Algorithm(algorithmType)) + { + return HashLegacySHA1Password(pass); + } + //This is the correct way to implement this (as per the sql membership provider) var bytes = Encoding.Unicode.GetBytes(pass); var saltBytes = Convert.FromBase64String(salt); byte[] inArray; - var hashAlgorithm = GetCurrentHashAlgorithm(); + var hashAlgorithm = GetHashAlgorithm(algorithmType); var algorithm = hashAlgorithm as KeyedHashAlgorithm; if (algorithm != null) { @@ -113,83 +163,64 @@ namespace Umbraco.Core.Security return Convert.ToBase64String(inArray); } - /// - /// Verifies if the password matches the expected hash+salt of the stored password string - /// - /// The password. - /// The value of the password stored in a data store. - /// - public bool VerifyPassword(string password, string dbPassword) - { - if (string.IsNullOrWhiteSpace(dbPassword)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(dbPassword)); - - if (dbPassword.StartsWith(Constants.Security.EmptyPasswordPrefix)) - return false; - - var storedHashedPass = ParseStoredHashPassword(dbPassword, out var salt); - var hashed = HashPassword(password, salt); - return storedHashedPass == hashed; - } - - /// - /// Create a new password hash and a new salt - /// - /// - /// - /// - public string HashNewPassword(string newPassword, out string salt) - { - salt = GenerateSalt(); - return HashPassword(newPassword, salt); - } - - /// - /// Parses out the hashed password and the salt from the stored password string value - /// - /// - /// returns the salt - /// - public string ParseStoredHashPassword(string storedString, out string salt) - { - if (string.IsNullOrWhiteSpace(storedString)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(storedString)); - - var saltLen = GenerateSalt(); - salt = storedString.Substring(0, saltLen.Length); - return storedString.Substring(saltLen.Length); - } - - public static string GenerateSalt() - { - var numArray = new byte[16]; - new RNGCryptoServiceProvider().GetBytes(numArray); - return Convert.ToBase64String(numArray); - } - /// /// Return the hash algorithm to use based on the /// + /// The hashing algorithm name. /// /// - private HashAlgorithm GetCurrentHashAlgorithm() + private HashAlgorithm GetHashAlgorithm(string algorithm) { - if (_passwordConfiguration.HashAlgorithmType.IsNullOrWhiteSpace()) + if (algorithm.IsNullOrWhiteSpace()) throw new InvalidOperationException("No hash algorithm type specified"); - var alg = HashAlgorithm.Create(_passwordConfiguration.HashAlgorithmType); + var alg = HashAlgorithm.Create(algorithm); if (alg == null) - throw new InvalidOperationException($"The hash algorithm specified {_passwordConfiguration.HashAlgorithmType} cannot be resolved"); + throw new InvalidOperationException($"The hash algorithm specified {algorithm} cannot be resolved"); return alg; } public bool SupportHashAlgorithm(string algorithm) { - if (algorithm.InvariantEquals(typeof(HMACSHA256).Name)) + // This is for the v6-v8 hashing algorithm + if (algorithm.InvariantEquals(Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName)) + return true; + + // This is for the <= v4 hashing algorithm + if (IsLegacySHA1Algorithm(algorithm)) return true; - // TODO: Need to add the old old old format in here too which was just HMACSHA1 IIRC but had a custom key implementation as the password itself return false; } + private bool IsLegacySHA1Algorithm(string algorithm) => algorithm.InvariantEquals(Constants.Security.AspNetUmbraco4PasswordHashAlgorithmName); + + /// + /// Hashes the password with the old v4 algorithm + /// + /// The password. + /// The encoded password. + private string HashLegacySHA1Password(string password) + { + var hashAlgorithm = GetLegacySHA1Algorithm(password); + var hash = Convert.ToBase64String(hashAlgorithm.ComputeHash(Encoding.Unicode.GetBytes(password))); + return hash; + } + + /// + /// Returns the old v4 algorithm and settings + /// + /// + /// + private HashAlgorithm GetLegacySHA1Algorithm(string password) + { + return new HMACSHA1 + { + //the legacy salt was actually the password :( + Key = Encoding.Unicode.GetBytes(password) + }; + } + } } diff --git a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs index d14a951877..84fc0e599c 100644 --- a/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs +++ b/src/Umbraco.Infrastructure/BackOffice/BackOfficeUserStore.cs @@ -246,6 +246,7 @@ namespace Umbraco.Core.BackOffice if (string.IsNullOrEmpty(passwordHash)) throw new ArgumentException("Value can't be empty.", nameof(passwordHash)); user.PasswordHash = passwordHash; + user.PasswordConfig = null; // Clear this so that it's reset at the repository level return Task.CompletedTask; } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/LegacyPasswordSecurityTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/LegacyPasswordSecurityTests.cs new file mode 100644 index 0000000000..c070657dd5 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/LegacyPasswordSecurityTests.cs @@ -0,0 +1,109 @@ +using Moq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Security; + +namespace Umbraco.Tests.UnitTests.Umbraco.Core.Security +{ + [TestFixture] + public class LegacyPasswordSecurityTests + { + + [Test] + public void Check_Password_Hashed_Non_KeyedHashAlgorithm() + { + var passwordConfiguration = Mock.Of(x => x.HashAlgorithmType == "SHA256"); + var passwordSecurity = new LegacyPasswordSecurity(); + + string salt; + var pass = "ThisIsAHashedPassword"; + var hashed = passwordSecurity.HashNewPassword(passwordConfiguration.HashAlgorithmType, pass, out salt); + var storedPassword = passwordSecurity.FormatPasswordForStorage(passwordConfiguration.HashAlgorithmType, hashed, salt); + + var result = passwordSecurity.VerifyPassword(passwordConfiguration.HashAlgorithmType, "ThisIsAHashedPassword", storedPassword); + + Assert.IsTrue(result); + } + + [Test] + public void Check_Password_Hashed_KeyedHashAlgorithm() + { + var passwordConfiguration = Mock.Of(x => x.HashAlgorithmType == Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName); + var passwordSecurity = new LegacyPasswordSecurity(); + + string salt; + var pass = "ThisIsAHashedPassword"; + var hashed = passwordSecurity.HashNewPassword(passwordConfiguration.HashAlgorithmType, pass, out salt); + var storedPassword = passwordSecurity.FormatPasswordForStorage(passwordConfiguration.HashAlgorithmType, hashed, salt); + + var result = passwordSecurity.VerifyPassword(passwordConfiguration.HashAlgorithmType, "ThisIsAHashedPassword", storedPassword); + + Assert.IsTrue(result); + } + + [Test] + public void Check_Password_Legacy_v4_SHA1() + { + var passwordConfiguration = Mock.Of(x => x.HashAlgorithmType == Constants.Security.AspNetUmbraco4PasswordHashAlgorithmName); + var passwordSecurity = new LegacyPasswordSecurity(); + + string salt; + var pass = "ThisIsAHashedPassword"; + var hashed = passwordSecurity.HashNewPassword(passwordConfiguration.HashAlgorithmType, pass, out salt); + var storedPassword = passwordSecurity.FormatPasswordForStorage(passwordConfiguration.HashAlgorithmType, hashed, salt); + + var result = passwordSecurity.VerifyPassword(passwordConfiguration.HashAlgorithmType, "ThisIsAHashedPassword", storedPassword); + + Assert.IsTrue(result); + } + + [Test] + public void Format_Pass_For_Storage_Hashed() + { + var passwordConfiguration = Mock.Of(x => x.HashAlgorithmType == Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName); + var passwordSecurity = new LegacyPasswordSecurity(); + + var salt = LegacyPasswordSecurity.GenerateSalt(); + var stored = "ThisIsAHashedPassword"; + + var result = passwordSecurity.FormatPasswordForStorage(passwordConfiguration.HashAlgorithmType, stored, salt); + + Assert.AreEqual(salt + "ThisIsAHashedPassword", result); + } + + [Test] + public void Get_Stored_Password_Hashed() + { + var passwordConfiguration = Mock.Of(x => x.HashAlgorithmType == Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName); + var passwordSecurity = new LegacyPasswordSecurity(); + + var salt = LegacyPasswordSecurity.GenerateSalt(); + var stored = salt + "ThisIsAHashedPassword"; + + string initSalt; + var result = passwordSecurity.ParseStoredHashPassword(passwordConfiguration.HashAlgorithmType, stored, out initSalt); + + Assert.AreEqual("ThisIsAHashedPassword", result); + } + + /// + /// The salt generated is always the same length + /// + [Test] + public void Check_Salt_Length() + { + var lastLength = 0; + for (var i = 0; i < 10000; i++) + { + var result = LegacyPasswordSecurity.GenerateSalt(); + + if (i > 0) + Assert.AreEqual(lastLength, result.Length); + + lastLength = result.Length; + } + } + + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/PasswordSecurityTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/PasswordSecurityTests.cs deleted file mode 100644 index 0ef6063910..0000000000 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Core/Security/PasswordSecurityTests.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Moq; -using NUnit.Framework; -using System.Security.Cryptography; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Security; - -namespace Umbraco.Tests.UnitTests.Umbraco.Core.Security -{ - [TestFixture] - public class PasswordSecurityTests - { - - [Test] - public void Check_Password_Hashed_Non_KeyedHashAlgorithm() - { - var passwordSecurity = new LegacyPasswordSecurity(Mock.Of(x => x.HashAlgorithmType == "SHA256")); - - string salt; - var pass = "ThisIsAHashedPassword"; - var hashed = passwordSecurity.HashNewPassword(pass, out salt); - var storedPassword = passwordSecurity.FormatPasswordForStorage(hashed, salt); - - var result = passwordSecurity.VerifyPassword("ThisIsAHashedPassword", storedPassword); - - Assert.IsTrue(result); - } - - [Test] - public void Check_Password_Hashed_KeyedHashAlgorithm() - { - var passwordSecurity = new LegacyPasswordSecurity(Mock.Of(x => x.HashAlgorithmType == Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName)); - - string salt; - var pass = "ThisIsAHashedPassword"; - var hashed = passwordSecurity.HashNewPassword(pass, out salt); - var storedPassword = passwordSecurity.FormatPasswordForStorage(hashed, salt); - - var result = passwordSecurity.VerifyPassword("ThisIsAHashedPassword", storedPassword); - - Assert.IsTrue(result); - } - - [Test] - public void Format_Pass_For_Storage_Hashed() - { - var passwordSecurity = new LegacyPasswordSecurity(Mock.Of(x => x.HashAlgorithmType == Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName)); - - var salt = LegacyPasswordSecurity.GenerateSalt(); - var stored = "ThisIsAHashedPassword"; - - var result = passwordSecurity.FormatPasswordForStorage(stored, salt); - - Assert.AreEqual(salt + "ThisIsAHashedPassword", result); - } - - [Test] - public void Get_Stored_Password_Hashed() - { - var passwordSecurity = new LegacyPasswordSecurity(Mock.Of(x => x.HashAlgorithmType == Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName)); - - var salt = LegacyPasswordSecurity.GenerateSalt(); - var stored = salt + "ThisIsAHashedPassword"; - - string initSalt; - var result = passwordSecurity.ParseStoredHashPassword(stored, out initSalt); - - Assert.AreEqual("ThisIsAHashedPassword", result); - } - - /// - /// The salt generated is always the same length - /// - [Test] - public void Check_Salt_Length() - { - var lastLength = 0; - for (var i = 0; i < 10000; i++) - { - var result = LegacyPasswordSecurity.GenerateSalt(); - - if (i > 0) - Assert.AreEqual(lastLength, result.Length); - - lastLength = result.Length; - } - } - - } -} diff --git a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs deleted file mode 100644 index 1dc261714c..0000000000 --- a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs +++ /dev/null @@ -1,165 +0,0 @@ -using System; -using System.Collections.Specialized; -using System.Configuration.Provider; -using System.Security.Cryptography; -using System.Web.Security; -using Moq; -using NUnit.Framework; -using Umbraco.Core.Security; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.Testing; -using Umbraco.Web.Composing; -using Umbraco.Web.Security; - -namespace Umbraco.Tests.Membership -{ - [TestFixture] - [UmbracoTest(WithApplication = true)] - public class MembershipProviderBaseTests : UmbracoTestBase - { - [Test] - public void ChangePasswordQuestionAndAnswer_Without_RequiresQuestionAndAnswer() - { - var providerMock = new Mock(TestHelper.GetHostingEnvironment()) { CallBase = true }; - providerMock.Setup(@base => @base.RequiresQuestionAndAnswer).Returns(false); - var provider = providerMock.Object; - - Assert.Throws(() => provider.ChangePasswordQuestionAndAnswer("test", "test", "test", "test")); - } - - [Test] - public void CreateUser_Not_Whitespace() - { - var providerMock = new Mock(TestHelper.GetHostingEnvironment()) {CallBase = true}; - var provider = providerMock.Object; - - MembershipCreateStatus status; - var result = provider.CreateUser("", "", "test@test.com", "", "", true, "", out status); - - Assert.IsNull(result); - Assert.AreEqual(MembershipCreateStatus.InvalidUserName, status); - } - - [Test] - public void CreateUser_Invalid_Question() - { - var providerMock = new Mock(TestHelper.GetHostingEnvironment()) { CallBase = true }; - providerMock.Setup(@base => @base.RequiresQuestionAndAnswer).Returns(true); - var provider = providerMock.Object; - - MembershipCreateStatus status; - var result = provider.CreateUser("test", "test", "test@test.com", "", "", true, "", out status); - - Assert.IsNull(result); - Assert.AreEqual(MembershipCreateStatus.InvalidQuestion, status); - } - - [Test] - public void CreateUser_Invalid_Answer() - { - var providerMock = new Mock(TestHelper.GetHostingEnvironment()) { CallBase = true }; - providerMock.Setup(@base => @base.RequiresQuestionAndAnswer).Returns(true); - var provider = providerMock.Object; - - MembershipCreateStatus status; - var result = provider.CreateUser("test", "test", "test@test.com", "test", "", true, "", out status); - - Assert.IsNull(result); - Assert.AreEqual(MembershipCreateStatus.InvalidAnswer, status); - } - - [Test] - public void GetPassword_Without_EnablePasswordRetrieval() - { - var providerMock = new Mock(TestHelper.GetHostingEnvironment()) { CallBase = true }; - providerMock.Setup(@base => @base.EnablePasswordRetrieval).Returns(false); - var provider = providerMock.Object; - - Assert.Throws(() => provider.GetPassword("test", "test")); - } - - [Test] - public void GetPassword_With_Hashed() - { - var providerMock = new Mock(TestHelper.GetHostingEnvironment()) { CallBase = true }; - providerMock.Setup(@base => @base.EnablePasswordRetrieval).Returns(true); - providerMock.Setup(@base => @base.PasswordFormat).Returns(MembershipPasswordFormat.Hashed); - var provider = providerMock.Object; - - Assert.Throws(() => provider.GetPassword("test", "test")); - } - - // FIXME: in v7 this test relies on ApplicationContext.Current being null, which makes little - // sense, not going to port the weird code in MembershipProviderBase.ResetPassword, so - // what shall we do? - [Test] - [Ignore("makes no sense?")] - public void ResetPassword_Without_EnablePasswordReset() - { - var providerMock = new Mock(TestHelper.GetHostingEnvironment()) { CallBase = true }; - providerMock.Setup(@base => @base.EnablePasswordReset).Returns(false); - var provider = providerMock.Object; - - Assert.Throws(() => provider.ResetPassword("test", "test")); - } - - [Test] - public void Sets_Defaults() - { - var providerMock = new Mock(TestHelper.GetHostingEnvironment()) { CallBase = true }; - var provider = providerMock.Object; - provider.Initialize("test", new NameValueCollection()); - - Assert.AreEqual("test", provider.Name); - Assert.AreEqual(MembershipProviderBase.GetDefaultAppName(TestHelper.GetHostingEnvironment()), provider.ApplicationName); - Assert.AreEqual(false, provider.EnablePasswordRetrieval); - Assert.AreEqual(true, provider.EnablePasswordReset); - Assert.AreEqual(false, provider.RequiresQuestionAndAnswer); - Assert.AreEqual(true, provider.RequiresUniqueEmail); - Assert.AreEqual(5, provider.MaxInvalidPasswordAttempts); - Assert.AreEqual(10, provider.PasswordAttemptWindow); - Assert.AreEqual(provider.DefaultMinPasswordLength, provider.MinRequiredPasswordLength); - Assert.AreEqual(provider.DefaultMinNonAlphanumericChars, provider.MinRequiredNonAlphanumericCharacters); - Assert.AreEqual(null, provider.PasswordStrengthRegularExpression); - Assert.AreEqual(provider.DefaultUseLegacyEncoding, provider.UseLegacyEncoding); - Assert.AreEqual(MembershipPasswordFormat.Hashed, provider.PasswordFormat); - } - - [Test] - public void Throws_Exception_With_Hashed_Password_And_Password_Retrieval() - { - var providerMock = new Mock(TestHelper.GetHostingEnvironment()) { CallBase = true }; - var provider = providerMock.Object; - - Assert.Throws(() => provider.Initialize("test", new NameValueCollection() - { - {"enablePasswordRetrieval", "true"}, - {"passwordFormat", "Hashed"} - })); - } - - [TestCase("hello", 0, "", 5, true)] - [TestCase("hello", 0, "", 4, true)] - [TestCase("hello", 0, "", 6, false)] - [TestCase("hello", 1, "", 5, false)] - [TestCase("hello!", 1, "", 0, true)] - [TestCase("hello!", 2, "", 0, false)] - [TestCase("hello!!", 2, "", 0, true)] - //8 characters or more in length, at least 1 lowercase letter,at least 1 character that is not a lower letter. - [TestCase("hello", 0, "(?=.{8,})[a-z]+[^a-z]+|[^a-z]+[a-z]+", 0, false)] - [TestCase("helloooo", 0, "(?=.{8,})[a-z]+[^a-z]+|[^a-z]+[a-z]+", 0, false)] - [TestCase("helloooO", 0, "(?=.{8,})[a-z]+[^a-z]+|[^a-z]+[a-z]+", 0, true)] - [TestCase("HELLOOOO", 0, "(?=.{8,})[a-z]+[^a-z]+|[^a-z]+[a-z]+", 0, false)] - [TestCase("HELLOOOo", 0, "(?=.{8,})[a-z]+[^a-z]+|[^a-z]+[a-z]+", 0, true)] - public void Valid_Password(string password, int minRequiredNonAlphanumericChars, string strengthRegex, int minLength, bool pass) - { - var result = MembershipProviderBase.IsPasswordValid(password, minRequiredNonAlphanumericChars, strengthRegex, minLength); - Assert.AreEqual(pass, result.Success); - } - - - - - - } -} diff --git a/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs b/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs deleted file mode 100644 index 4f2cb22b4a..0000000000 --- a/src/Umbraco.Tests/Membership/UmbracoServiceMembershipProviderTests.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System.Collections.Specialized; -using System.Threading; -using System.Web.Security; -using Moq; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Services; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Entities; -using Umbraco.Tests.Testing; -using Umbraco.Web.Security.Providers; - -namespace Umbraco.Tests.Membership -{ - [TestFixture] - [Apartment(ApartmentState.STA)] - [UmbracoTest(Database = UmbracoTestOptions.Database.None, WithApplication = true)] - public class UmbracoServiceMembershipProviderTests : UmbracoTestBase - { - [Test] - public void Sets_Default_Member_Type_From_Service_On_Init() - { - var memberTypeServiceMock = new Mock(); - memberTypeServiceMock.Setup(x => x.GetDefault()).Returns("Blah"); - var provider = new MembersMembershipProvider(Mock.Of(), memberTypeServiceMock.Object, TestHelper.GetUmbracoVersion(), TestHelper.GetHostingEnvironment(), TestHelper.GetIpResolver()); - provider.Initialize("test", new NameValueCollection()); - - Assert.AreEqual("Blah", provider.DefaultMemberTypeAlias); - } - - [Test] - public void Sets_Default_Member_Type_From_Config_On_Init() - { - var memberTypeServiceMock = new Mock(); - memberTypeServiceMock.Setup(x => x.GetDefault()).Returns("Blah"); - var provider = new MembersMembershipProvider(Mock.Of(), memberTypeServiceMock.Object, TestHelper.GetUmbracoVersion(), TestHelper.GetHostingEnvironment(), TestHelper.GetIpResolver()); - provider.Initialize("test", new NameValueCollection { { "defaultMemberTypeAlias", "Hello" } }); - - Assert.AreEqual("Hello", provider.DefaultMemberTypeAlias); - } - - [Test] - public void Create_User_Already_Exists() - { - var memberTypeServiceMock = new Mock(); - memberTypeServiceMock.Setup(x => x.GetDefault()).Returns("Member"); - var membershipServiceMock = new Mock(); - membershipServiceMock.Setup(service => service.Exists("test")).Returns(true); - - var provider = new MembersMembershipProvider(membershipServiceMock.Object, memberTypeServiceMock.Object, TestHelper.GetUmbracoVersion(), TestHelper.GetHostingEnvironment(), TestHelper.GetIpResolver()); - provider.Initialize("test", new NameValueCollection()); - - MembershipCreateStatus status; - var user = provider.CreateUser("test", "test", "testtest$1", "test@test.com", "test", "test", true, "test", out status); - - Assert.IsNull(user); - } - - [Test] - public void Create_User_Requires_Unique_Email() - { - var memberTypeServiceMock = new Mock(); - memberTypeServiceMock.Setup(x => x.GetDefault()).Returns("Member"); - var membershipServiceMock = new Mock(); - membershipServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => new Member("test", MockedContentTypes.CreateSimpleMemberType())); - - var provider = new MembersMembershipProvider(membershipServiceMock.Object, memberTypeServiceMock.Object, TestHelper.GetUmbracoVersion(), TestHelper.GetHostingEnvironment(), TestHelper.GetIpResolver()); - provider.Initialize("test", new NameValueCollection { { "requiresUniqueEmail", "true" } }); - - MembershipCreateStatus status; - var user = provider.CreateUser("test", "test", "testtest$1", "test@test.com", "test", "test", true, "test", out status); - - Assert.IsNull(user); - } - - [Test] - public void Password_Hashed_With_Salt() - { - IMember createdMember = null; - var memberType = MockedContentTypes.CreateSimpleMemberType(); - foreach (var p in ConventionsHelper.GetStandardPropertyTypeStubs(TestHelper.ShortStringHelper)) - { - memberType.AddPropertyType(p.Value); - } - var memberTypeServiceMock = new Mock(); - memberTypeServiceMock.Setup(x => x.GetDefault()).Returns("Member"); - var membershipServiceMock = new Mock(); - membershipServiceMock.Setup(service => service.Exists("test")).Returns(false); - membershipServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); - membershipServiceMock.Setup( - service => service.CreateWithIdentity(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((string u, string e, string p, string m, bool isApproved) => - { - createdMember = new Member("test", e, u, p, memberType, isApproved); - }) - .Returns(() => createdMember); - - var provider = new MembersMembershipProvider(membershipServiceMock.Object, memberTypeServiceMock.Object, TestHelper.GetUmbracoVersion(), TestHelper.GetHostingEnvironment(), TestHelper.GetIpResolver()); - provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" }, { "hashAlgorithmType", Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName } }); - - - MembershipCreateStatus status; - provider.CreateUser("test", "test", "testtest$1", "test@test.com", "test", "test", true, "test", out status); - - Assert.AreNotEqual("test", createdMember.RawPasswordValue); - - string salt; - var storedPassword = provider.PasswordSecurity.ParseStoredHashPassword(createdMember.RawPasswordValue, out salt); - var hashedPassword = provider.PasswordSecurity.HashPassword("testtest$1", salt); - Assert.AreEqual(hashedPassword, storedPassword); - } - - } -} diff --git a/src/Umbraco.Tests/Security/BackOfficeOwinUserManagerTests.cs b/src/Umbraco.Tests/Security/BackOfficeOwinUserManagerTests.cs deleted file mode 100644 index 32c85121d2..0000000000 --- a/src/Umbraco.Tests/Security/BackOfficeOwinUserManagerTests.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; -using Microsoft.Owin.Security.DataProtection; -using Moq; -using NUnit.Framework; -using Umbraco.Core; -using Umbraco.Core.BackOffice; -using Umbraco.Core.Configuration; -using Umbraco.Core.Configuration.Models; -using Umbraco.Core.Models.Membership; -using Umbraco.Net; -using Umbraco.Web.Security; - -namespace Umbraco.Tests.Security -{ - public class BackOfficeOwinUserManagerTests - { - [Test] - public async Task CheckPasswordAsync_When_Default_Password_Hasher_Validates_Umbraco7_Hash_Expect_Valid_Password() - { - const string v7Hash = "7Uob6fMTTxDIhWGebYiSxg==P+hgvWlXLbDd4cFLADn811KOaVI/9pg1PNvTuG5NklY="; - const string plaintext = "4XxzH3s3&J"; - - var userPasswordConfiguration = new UserPasswordConfigurationSettings() - { - HashAlgorithmType = Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName - }; - var mockIpResolver = new Mock(); - var mockUserStore = new Mock>(); - var mockDataProtectionProvider = new Mock(); - - mockDataProtectionProvider.Setup(x => x.Create(It.IsAny())) - .Returns(new Mock().Object); - - - var userManager = BackOfficeOwinUserManager.Create( - Options.Create(userPasswordConfiguration), - mockIpResolver.Object, - mockUserStore.Object, - null, - mockDataProtectionProvider.Object, - new NullLogger>()); - - var globalSettings = new GlobalSettings() - { - DefaultUILanguage = "test" - }; - - var user = new BackOfficeIdentityUser(globalSettings, 2, new List()) - { - UserName = "alice", - Name = "Alice", - Email = "alice@umbraco.test", - PasswordHash = v7Hash - }; - - mockUserStore.Setup(x => x.GetPasswordHashAsync(user, It.IsAny())) - .ReturnsAsync(v7Hash); - - var isValidPassword = await userManager.CheckPasswordAsync(user, plaintext); - - Assert.True(isValidPassword); - } - } -} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index e8b188bd63..8e7e38c495 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -150,7 +150,6 @@ - @@ -233,8 +232,6 @@ - - diff --git a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs index 18393421d2..fd4d5c96cc 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MemberController.cs @@ -305,18 +305,22 @@ namespace Umbraco.Web.BackOffice.Controllers private IMember CreateMemberData(MemberSave contentItem) { - var memberType = _memberTypeService.Get(contentItem.ContentTypeAlias); - if (memberType == null) - throw new InvalidOperationException($"No member type found with alias {contentItem.ContentTypeAlias}"); - var member = new Member(contentItem.Name, contentItem.Email, contentItem.Username, memberType, true) - { - CreatorId = _backofficeSecurityAccessor.BackofficeSecurity.CurrentUser.Id, - RawPasswordValue = _passwordSecurity.HashPasswordForStorage(contentItem.Password.NewPassword), - Comments = contentItem.Comments, - IsApproved = contentItem.IsApproved - }; + throw new NotImplementedException("Members have not been migrated to netcore"); - return member; + // TODO: all member password processing and creation needs to be done with a new aspnet identity MemberUserManager that hasn't been created yet. + + //var memberType = _memberTypeService.Get(contentItem.ContentTypeAlias); + //if (memberType == null) + // throw new InvalidOperationException($"No member type found with alias {contentItem.ContentTypeAlias}"); + //var member = new Member(contentItem.Name, contentItem.Email, contentItem.Username, memberType, true) + //{ + // CreatorId = _backofficeSecurityAccessor.BackofficeSecurity.CurrentUser.Id, + // RawPasswordValue = _passwordSecurity.HashPasswordForStorage(contentItem.Password.NewPassword), + // Comments = contentItem.Comments, + // IsApproved = contentItem.IsApproved + //}; + + //return member; } /// @@ -372,8 +376,10 @@ namespace Umbraco.Web.BackOffice.Controllers if (contentItem.Password == null) return; + throw new NotImplementedException("Members have not been migrated to netcore"); + // TODO: all member password processing and creation needs to be done with a new aspnet identity MemberUserManager that hasn't been created yet. // set the password - contentItem.PersistedContent.RawPasswordValue = _passwordSecurity.HashPasswordForStorage(contentItem.Password.NewPassword); + //contentItem.PersistedContent.RawPasswordValue = _passwordSecurity.HashPasswordForStorage(contentItem.Password.NewPassword); } private static void UpdateName(MemberSave memberSave) diff --git a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs index 8dc1b8c417..71fa97b9cb 100644 --- a/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs @@ -86,7 +86,7 @@ namespace Umbraco.Extensions services.TryAddScoped, PasswordValidator>(); services.TryAddScoped>( services => new BackOfficePasswordHasher( - new LegacyPasswordSecurity(services.GetRequiredService>().Value), + new LegacyPasswordSecurity(), services.GetRequiredService())); services.TryAddScoped, DefaultUserConfirmation>(); services.TryAddScoped, UserClaimsPrincipalFactory>(); diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficePasswordHasher.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficePasswordHasher.cs index df3bc2935b..6c61e7bb35 100644 --- a/src/Umbraco.Web.BackOffice/Security/BackOfficePasswordHasher.cs +++ b/src/Umbraco.Web.BackOffice/Security/BackOfficePasswordHasher.cs @@ -11,72 +11,73 @@ namespace Umbraco.Web.BackOffice.Security /// /// A password hasher for back office users /// + /// + /// This allows us to verify passwords in old formats and roll forward to the latest format + /// public class BackOfficePasswordHasher : PasswordHasher { - private readonly LegacyPasswordSecurity _passwordSecurity; + private readonly LegacyPasswordSecurity _legacyPasswordSecurity; private readonly IJsonSerializer _jsonSerializer; + private readonly PasswordHasher _aspnetV2PasswordHasher = new PasswordHasher(new V2PasswordHasherOptions()); public BackOfficePasswordHasher(LegacyPasswordSecurity passwordSecurity, IJsonSerializer jsonSerializer) { - _passwordSecurity = passwordSecurity; + _legacyPasswordSecurity = passwordSecurity; _jsonSerializer = jsonSerializer; } public override string HashPassword(BackOfficeIdentityUser user, string password) + { + // Always use the latest/current hash algorithm when hashing new passwords for storage. + // NOTE: This is only overridden to show that we can since we may need to adjust this in the future + // if new/different formats are required. + return base.HashPassword(user, password); + } + + /// + /// Verifies a user's hashed password + /// + /// + /// + /// + /// + /// + /// This will check the user's current hashed password format stored with their user row and use that to verify the hash. This could be any hashes + /// from the very old v4, to the older v6-v8, to the older aspnet identity and finally to the most recent + /// + public override PasswordVerificationResult VerifyHashedPassword(BackOfficeIdentityUser user, string hashedPassword, string providedPassword) { if (!user.PasswordConfig.IsNullOrWhiteSpace()) { // check if the (legacy) password security supports this hash algorith and if so then use it var deserialized = _jsonSerializer.Deserialize(user.PasswordConfig); - if (_passwordSecurity.SupportHashAlgorithm(deserialized.HashAlgorithm)) - return _passwordSecurity.HashPasswordForStorage(password); + if (_legacyPasswordSecurity.SupportHashAlgorithm(deserialized.HashAlgorithm)) + { + var result = _legacyPasswordSecurity.VerifyPassword(deserialized.HashAlgorithm, providedPassword, hashedPassword); + return result + ? PasswordVerificationResult.SuccessRehashNeeded + : PasswordVerificationResult.Failed; + } - // We will explicitly detect names here since this allows us to future proof these checks. + // We will explicitly detect names here // The default is PBKDF2.ASPNETCORE.V3: // PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations. // The underlying class only lets us change 2 things which is the version: options.CompatibilityMode and the iteration count // The PBKDF2.ASPNETCORE.V2 settings are: // PBKDF2 with HMAC-SHA1, 128-bit salt, 256-bit subkey, 1000 iterations. - switch (deserialized.HashAlgorithm) - { - case Constants.Security.AspNetCoreV3PasswordHashAlgorithmName: - return base.HashPassword(user, password); - case Constants.Security.AspNetCoreV2PasswordHashAlgorithmName: - var v2Hasher = new PasswordHasher(new V2PasswordHasherOptions()); - return v2Hasher.HashPassword(user, password); - } - } - - // else keep the default - return base.HashPassword(user, password); - } - - public override PasswordVerificationResult VerifyHashedPassword(BackOfficeIdentityUser user, string hashedPassword, string providedPassword) - { - if (!user.PasswordConfig.IsNullOrWhiteSpace()) - { - // check if the (legacy) password security supports this hash algorith and if so then use it - var deserialized = _jsonSerializer.Deserialize(user.PasswordConfig); - if (_passwordSecurity.SupportHashAlgorithm(deserialized.HashAlgorithm)) - { - var result = _passwordSecurity.VerifyPassword(providedPassword, hashedPassword); - return result - ? PasswordVerificationResult.Success - : PasswordVerificationResult.Failed; - } - switch (deserialized.HashAlgorithm) { case Constants.Security.AspNetCoreV3PasswordHashAlgorithmName: return base.VerifyHashedPassword(user, hashedPassword, providedPassword); case Constants.Security.AspNetCoreV2PasswordHashAlgorithmName: - var v2Hasher = new PasswordHasher(new V2PasswordHasherOptions()); - return v2Hasher.VerifyHashedPassword(user, hashedPassword, providedPassword); + var legacyResult = _aspnetV2PasswordHasher.VerifyHashedPassword(user, hashedPassword, providedPassword); + if (legacyResult == PasswordVerificationResult.Success) return PasswordVerificationResult.SuccessRehashNeeded; + return legacyResult; } } - // else go the default + // else go the default (v3) return base.VerifyHashedPassword(user, hashedPassword, providedPassword); } diff --git a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs index 45411b5e69..0d91892445 100644 --- a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs +++ b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs @@ -39,7 +39,7 @@ namespace Umbraco.Web.Common.Runtime [ComposeAfter(typeof(CoreInitialComposer))] public class AspNetCoreComposer : ComponentComposer, IComposer { - public new void Compose(Composition composition) + public override void Compose(Composition composition) { base.Compose(composition); @@ -99,7 +99,7 @@ namespace Umbraco.Web.Common.Runtime composition.RegisterUnique(); composition.RegisterUnique(); - composition.RegisterUnique(factory => new LegacyPasswordSecurity(factory.GetInstance>().Value)); + composition.RegisterUnique(factory => new LegacyPasswordSecurity()); } } } diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js index bf5fe6b4fb..179faeb843 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/dependencies.js @@ -281,7 +281,7 @@ function dependencies() { var assetsTask = gulp.src(config.sources.globs.assets, { allowEmpty: true }); assetsTask = assetsTask.pipe(imagemin([ imagemin.gifsicle({interlaced: true}), - imagemin.jpegtran({progressive: true}), + imagemin.mozjpeg({progressive: true}), imagemin.optipng({optimizationLevel: 5}), imagemin.svgo({ plugins: [ diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 5c4df7b12c..eae04dc289 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -61,7 +61,7 @@ "gulp-cli": "^2.3.0", "gulp-concat": "2.6.1", "gulp-eslint": "6.0.0", - "gulp-imagemin": "6.1.1", + "gulp-imagemin": "7.1.0", "gulp-less": "4.0.1", "gulp-notify": "^3.0.0", "gulp-postcss": "8.0.0", diff --git a/src/Umbraco.Web/Security/BackOfficeOwinUserManager.cs b/src/Umbraco.Web/Security/BackOfficeOwinUserManager.cs index 65d7836d42..2f5e858687 100644 --- a/src/Umbraco.Web/Security/BackOfficeOwinUserManager.cs +++ b/src/Umbraco.Web/Security/BackOfficeOwinUserManager.cs @@ -7,15 +7,14 @@ using Microsoft.Extensions.Options; using Microsoft.Owin.Security.DataProtection; using Umbraco.Core; using Umbraco.Core.BackOffice; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Mapping; -using Umbraco.Core.Security; using Umbraco.Core.Services; using Umbraco.Net; namespace Umbraco.Web.Security { + // TODO: Most of this is already migrated to netcore, there's probably not much more to go and then we can complete remove it public class BackOfficeOwinUserManager : BackOfficeUserManager { public const string OwinMarkerKey = "Umbraco.Web.Security.Identity.BackOfficeUserManagerMarker"; @@ -118,11 +117,6 @@ namespace Umbraco.Web.Security #endregion - protected override IPasswordHasher GetDefaultPasswordHasher(IPasswordConfiguration passwordConfiguration) - { - return new UserAwarePasswordHasher(new LegacyPasswordSecurity(passwordConfiguration)); - } - protected void InitUserManager(BackOfficeOwinUserManager manager, IDataProtectionProvider dataProtectionProvider) { // use a custom hasher based on our membership provider diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index 10adc76e6b..fa1ddae980 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -80,7 +80,7 @@ namespace Umbraco.Web.Security.Providers CustomHashAlgorithmType.IsNullOrWhiteSpace() ? Membership.HashAlgorithmType : CustomHashAlgorithmType, MaxInvalidPasswordAttempts)); - _passwordSecurity = new Lazy(() => new LegacyPasswordSecurity(PasswordConfiguration)); + _passwordSecurity = new Lazy(() => new LegacyPasswordSecurity()); } diff --git a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs index 53cbd266dc..8a92226d7e 100644 --- a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs @@ -83,9 +83,9 @@ namespace Umbraco.Web.Security.Providers if (m == null) return false; string salt; - var encodedPassword = PasswordSecurity.HashNewPassword(newPassword, out salt); + var encodedPassword = PasswordSecurity.HashNewPassword(Membership.HashAlgorithmType, newPassword, out salt); - m.RawPasswordValue = PasswordSecurity.FormatPasswordForStorage(encodedPassword, salt); + m.RawPasswordValue = PasswordSecurity.FormatPasswordForStorage(Membership.HashAlgorithmType, encodedPassword, salt); m.LastPasswordChangeDate = DateTime.Now; MemberService.Save(m); @@ -143,12 +143,12 @@ namespace Umbraco.Web.Security.Providers } string salt; - var encodedPassword = PasswordSecurity.HashNewPassword(password, out salt); + var encodedPassword = PasswordSecurity.HashNewPassword(Membership.HashAlgorithmType, password, out salt); var member = MemberService.CreateWithIdentity( username, email, - PasswordSecurity.FormatPasswordForStorage(encodedPassword, salt), + PasswordSecurity.FormatPasswordForStorage(Membership.HashAlgorithmType, encodedPassword, salt), memberTypeAlias, isApproved); @@ -406,8 +406,8 @@ namespace Umbraco.Web.Security.Providers } string salt; - var encodedPassword = PasswordSecurity.HashNewPassword(generatedPassword, out salt); - m.RawPasswordValue = PasswordSecurity.FormatPasswordForStorage(encodedPassword, salt); + var encodedPassword = PasswordSecurity.HashNewPassword(Membership.HashAlgorithmType, generatedPassword, out salt); + m.RawPasswordValue = PasswordSecurity.FormatPasswordForStorage(Membership.HashAlgorithmType, encodedPassword, salt); m.LastPasswordChangeDate = DateTime.Now; MemberService.Save(m); @@ -519,7 +519,7 @@ namespace Umbraco.Web.Security.Providers }; } - var authenticated = PasswordSecurity.VerifyPassword(password, member.RawPasswordValue); + var authenticated = PasswordSecurity.VerifyPassword(Membership.HashAlgorithmType, password, member.RawPasswordValue); var requiresFullSave = false; diff --git a/src/Umbraco.Web/Security/UserAwarePasswordHasher.cs b/src/Umbraco.Web/Security/UserAwarePasswordHasher.cs deleted file mode 100644 index 040d79be02..0000000000 --- a/src/Umbraco.Web/Security/UserAwarePasswordHasher.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Umbraco.Core.BackOffice; -using Umbraco.Core.Security; - -namespace Umbraco.Web.Security -{ - public class UserAwarePasswordHasher : IPasswordHasher - where T : BackOfficeIdentityUser - { - private readonly LegacyPasswordSecurity _passwordSecurity; - - public UserAwarePasswordHasher(LegacyPasswordSecurity passwordSecurity) - { - _passwordSecurity = passwordSecurity; - } - - public string HashPassword(string password) - { - return _passwordSecurity.HashPasswordForStorage(password); - } - - public string HashPassword(T user, string password) - { - // TODO: Implement the logic for this, we need to lookup the password format for the user and hash accordingly: http://issues.umbraco.org/issue/U4-10089 - //NOTE: For now this just falls back to the hashing we are currently using - - return HashPassword(password); - } - - public PasswordVerificationResult VerifyHashedPassword(T user, string hashedPassword, string providedPassword) - { - // TODO: Implement the logic for this, we need to lookup the password format for the user and hash accordingly: http://issues.umbraco.org/issue/U4-10089 - //NOTE: For now this just falls back to the hashing we are currently using - - return _passwordSecurity.VerifyPassword(providedPassword, hashedPassword) - ? PasswordVerificationResult.Success - : PasswordVerificationResult.Failed; - } - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 099c995126..e879f37eef 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -194,7 +194,6 @@ -