From 915f1cb34c3c63d3c88ebced61dd35d80255408c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 2 Dec 2021 11:54:24 +0100 Subject: [PATCH] V9: Fix for migration of non-default configurated users/members (#11684) * https://github.com/umbraco/Umbraco-CMS/issues/11366 Fallback to try login using super legacy HMACSHA1 even when the algorithm is stated as being HMACSHA256. The issue is that v8 saves HMACSHA256 on the user, but when configured to use legacy encoding it actually uses HMACSHA1 * Support migration of members with: UseLegacyEncoding+Clear UseLegacyEncoding+Encrypted (Requires machine key) UseLegacyEncoding+Hashed * Fixes unit tests * Avoid exceptions + unit tests * Save unknown algorithm if we dont know it, instead of persisting a wrong algorithm. * Added setting to enable clear text password rehashes. * Removed support for migration of clear text passwords * Fixed unit test --- src/JsonSchema/AppSettings.cs | 1 + .../Models/LegacyPasswordMigrationSettings.cs | 30 +++++ src/Umbraco.Core/Constants-Configuration.cs | 1 + src/Umbraco.Core/Constants-Security.cs | 1 + .../UmbracoBuilder.Configuration.cs | 1 + .../Security/LegacyPasswordSecurity.cs | 30 ++++- .../Implement/MemberRepository.cs | 3 + .../Security/MemberPasswordHasher.cs | 116 +++++++++++++++++- .../Security/MemberUserStore.cs | 6 + .../Security/UmbracoPasswordHasher.cs | 7 ++ .../Security/UmbracoUserManager.cs | 9 +- .../Security/MemberPasswordHasherTests.cs | 84 +++++-------- 12 files changed, 224 insertions(+), 65 deletions(-) create mode 100644 src/Umbraco.Core/Configuration/Models/LegacyPasswordMigrationSettings.cs diff --git a/src/JsonSchema/AppSettings.cs b/src/JsonSchema/AppSettings.cs index 4af6685d8a..8db2bbb8c7 100644 --- a/src/JsonSchema/AppSettings.cs +++ b/src/JsonSchema/AppSettings.cs @@ -82,6 +82,7 @@ namespace JsonSchema public BasicAuthSettings BasicAuth { get; set; } public PackageMigrationSettings PackageMigration { get; set; } + public LegacyPasswordMigrationSettings LegacyPasswordMigration { get; set; } } /// diff --git a/src/Umbraco.Core/Configuration/Models/LegacyPasswordMigrationSettings.cs b/src/Umbraco.Core/Configuration/Models/LegacyPasswordMigrationSettings.cs new file mode 100644 index 0000000000..c3909ed619 --- /dev/null +++ b/src/Umbraco.Core/Configuration/Models/LegacyPasswordMigrationSettings.cs @@ -0,0 +1,30 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using System.ComponentModel; + +namespace Umbraco.Cms.Core.Configuration.Models +{ + /// + /// Typed configuration options for legacy machine key settings used for migration of members from a v8 solution. + /// + [UmbracoOptions(Constants.Configuration.ConfigLegacyPasswordMigration)] + public class LegacyPasswordMigrationSettings + { + private const string StaticDecryptionKey = ""; + + /// + /// Gets the decryption algorithm. + /// + /// + /// Currently only AES is supported. This should include all machine keys generated by Umbraco. + /// + public string MachineKeyDecryption => "AES"; + + /// + /// Gets or sets the decryption hex-formatted string key found in legacy web.config machineKey configuration-element. + /// + [DefaultValue(StaticDecryptionKey)] + public string MachineKeyDecryptionKey { get; set; } = StaticDecryptionKey; + } +} diff --git a/src/Umbraco.Core/Constants-Configuration.cs b/src/Umbraco.Core/Constants-Configuration.cs index c36f5813ab..063d733821 100644 --- a/src/Umbraco.Core/Constants-Configuration.cs +++ b/src/Umbraco.Core/Constants-Configuration.cs @@ -26,6 +26,7 @@ public const string ConfigHostingDebug = ConfigHostingPrefix + "Debug"; public const string ConfigCustomErrorsMode = ConfigCustomErrorsPrefix + "Mode"; public const string ConfigActiveDirectory = ConfigPrefix + "ActiveDirectory"; + public const string ConfigLegacyPasswordMigration = ConfigPrefix + "LegacyPasswordMigration"; public const string ConfigContent = ConfigPrefix + "Content"; public const string ConfigCoreDebug = ConfigCorePrefix + "Debug"; public const string ConfigExceptionFilter = ConfigPrefix + "ExceptionFilter"; diff --git a/src/Umbraco.Core/Constants-Security.cs b/src/Umbraco.Core/Constants-Security.cs index 0506a66ad2..b509c12ff5 100644 --- a/src/Umbraco.Core/Constants-Security.cs +++ b/src/Umbraco.Core/Constants-Security.cs @@ -66,6 +66,7 @@ namespace Umbraco.Cms.Core public const string AspNetCoreV2PasswordHashAlgorithmName = "PBKDF2.ASPNETCORE.V2"; public const string AspNetUmbraco8PasswordHashAlgorithmName = "HMACSHA256"; public const string AspNetUmbraco4PasswordHashAlgorithmName = "HMACSHA1"; + public const string UnknownPasswordConfigJson = "{\"hashAlgorithm\":\"Unknown\"}"; } } } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs index 9b31ed7056..6ef87464e8 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -73,6 +73,7 @@ namespace Umbraco.Cms.Core.DependencyInjection .AddUmbracoOptions() .AddUmbracoOptions() .AddUmbracoOptions() + .AddUmbracoOptions() .AddUmbracoOptions(); return builder; diff --git a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs index bf56c3161d..8f6813e7ba 100644 --- a/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs +++ b/src/Umbraco.Core/Security/LegacyPasswordSecurity.cs @@ -50,9 +50,33 @@ namespace Umbraco.Cms.Core.Security 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; + try + { + var storedHashedPass = ParseStoredHashPassword(algorithm, dbPassword, out var salt); + var hashed = HashPassword(algorithm, password, salt); + return storedHashedPass == hashed; + } + catch (ArgumentOutOfRangeException) + { + //This can happen if the length of the password is wrong and a salt cannot be extracted. + return false; + } + + } + + /// + /// Verify a legacy hashed password (HMACSHA1) + /// + public bool VerifyLegacyHashedPassword(string password, string dbPassword) + { + var hashAlgorith = new HMACSHA1 + { + //the legacy salt was actually the password :( + Key = Encoding.Unicode.GetBytes(password) + }; + var hashed = Convert.ToBase64String(hashAlgorith.ComputeHash(Encoding.Unicode.GetBytes(password))); + + return dbPassword == hashed; } /// diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs index 4031971ddc..265e8344c8 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs @@ -774,6 +774,9 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement { memberDto.PasswordConfig = DefaultPasswordConfigJson; changedCols.Add("passwordConfig"); + }else if (memberDto.PasswordConfig == Constants.Security.UnknownPasswordConfigJson) + { + changedCols.Add("passwordConfig"); } // do NOT update the password if it has not changed or if it is null or empty diff --git a/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs b/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs index 2d2cad1624..e470bf0a6c 100644 --- a/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs +++ b/src/Umbraco.Infrastructure/Security/MemberPasswordHasher.cs @@ -1,9 +1,15 @@ using System; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; using Microsoft.AspNetCore.Identity; -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Core.Security; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Security @@ -16,9 +22,27 @@ namespace Umbraco.Cms.Core.Security /// public class MemberPasswordHasher : UmbracoPasswordHasher { + private readonly IOptions _legacyMachineKeySettings; + private readonly ILogger _logger; + + [Obsolete("Use ctor with all params")] public MemberPasswordHasher(LegacyPasswordSecurity legacyPasswordHasher, IJsonSerializer jsonSerializer) + : this(legacyPasswordHasher, + jsonSerializer, + StaticServiceProvider.Instance.GetRequiredService>(), + StaticServiceProvider.Instance.GetRequiredService>()) + { + } + + public MemberPasswordHasher( + LegacyPasswordSecurity legacyPasswordHasher, + IJsonSerializer jsonSerializer, + IOptions legacyMachineKeySettings, + ILogger logger) : base(legacyPasswordHasher, jsonSerializer) { + _legacyMachineKeySettings = legacyMachineKeySettings; + _logger = logger; } /// @@ -36,10 +60,21 @@ namespace Umbraco.Cms.Core.Security throw new ArgumentNullException(nameof(user)); } + var isPasswordAlgorithmKnown = user.PasswordConfig.IsNullOrWhiteSpace() == false && + user.PasswordConfig != Constants.Security.UnknownPasswordConfigJson; // if there's password config use the base implementation - if (!user.PasswordConfig.IsNullOrWhiteSpace()) + if (isPasswordAlgorithmKnown) { - return base.VerifyHashedPassword(user, hashedPassword, providedPassword); + var result = base.VerifyHashedPassword(user, hashedPassword, providedPassword); + if (result != PasswordVerificationResult.Failed) + { + return result; + } + } + // We need to check for clear text passwords from members as the first thing. This was possible in v8 :( + else if (IsSuccessfulLegacyPassword(hashedPassword, providedPassword)) + { + return PasswordVerificationResult.SuccessRehashNeeded; } // Else we need to detect what the password is. This will be the case @@ -66,7 +101,16 @@ namespace Umbraco.Cms.Core.Security return base.VerifyHashedPassword(user, hashedPassword, providedPassword); } - throw new InvalidOperationException("unable to determine member password hashing algorith"); + if (isPasswordAlgorithmKnown) + { + _logger.LogError("Unable to determine member password hashing algorithm"); + } + else + { + _logger.LogDebug("Unable to determine member password hashing algorithm, but this can happen when member enters a wrong password, before it has be rehashed"); + } + + return PasswordVerificationResult.Failed; } var isValid = LegacyPasswordSecurity.VerifyPassword( @@ -76,5 +120,65 @@ namespace Umbraco.Cms.Core.Security return isValid ? PasswordVerificationResult.SuccessRehashNeeded : PasswordVerificationResult.Failed; } + + private bool IsSuccessfulLegacyPassword(string hashedPassword, string providedPassword) + { + if (!string.IsNullOrEmpty(_legacyMachineKeySettings.Value.MachineKeyDecryptionKey)) + { + try + { + var decryptedPassword = DecryptLegacyPassword(hashedPassword, _legacyMachineKeySettings.Value.MachineKeyDecryption, _legacyMachineKeySettings.Value.MachineKeyDecryptionKey); + return decryptedPassword == providedPassword; + } + catch (Exception ex) + { + _logger.LogError(ex, "Could not decrypt password even that a DecryptionKey is provided. This means the DecryptionKey is wrong."); + return false; + } + } + + var result = LegacyPasswordSecurity.VerifyPassword(Constants.Security.AspNetUmbraco8PasswordHashAlgorithmName, providedPassword, hashedPassword); + return result || LegacyPasswordSecurity.VerifyPassword(Constants.Security.AspNetUmbraco4PasswordHashAlgorithmName, providedPassword, hashedPassword); + } + + private static string DecryptLegacyPassword(string encryptedPassword, string algorithmName, string decryptionKey) + { + SymmetricAlgorithm algorithm; + switch (algorithmName) + { + case "AES": + algorithm = new AesCryptoServiceProvider() + { + Key = StringToByteArray(decryptionKey), + IV = new byte[16] + }; + break; + default: + throw new NotSupportedException($"The algorithm ({algorithmName}) is not supported"); + } + + using (algorithm) + { + return DecryptLegacyPassword(encryptedPassword, algorithm); + } + } + + private static string DecryptLegacyPassword(string encryptedPassword, SymmetricAlgorithm algorithm) + { + using var memoryStream = new MemoryStream(); + ICryptoTransform cryptoTransform = algorithm.CreateDecryptor(); + var cryptoStream = new CryptoStream((Stream)memoryStream, cryptoTransform, CryptoStreamMode.Write); + var buf = Convert.FromBase64String(encryptedPassword); + cryptoStream.Write(buf, 0, 32); + cryptoStream.FlushFinalBlock(); + + return Encoding.Unicode.GetString(memoryStream.ToArray()); + } + + private static byte[] StringToByteArray(string hex) => + Enumerable.Range(0, hex.Length) + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) + .ToArray(); } } diff --git a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs index 086ff6a66c..d757cfb088 100644 --- a/src/Umbraco.Infrastructure/Security/MemberUserStore.cs +++ b/src/Umbraco.Infrastructure/Security/MemberUserStore.cs @@ -592,6 +592,12 @@ namespace Umbraco.Cms.Core.Security member.PasswordConfiguration = identityUser.PasswordConfig; } + if (member.PasswordConfiguration != identityUser.PasswordConfig) + { + changeType = MemberDataChangeType.FullSave; + member.PasswordConfiguration = identityUser.PasswordConfig; + } + if (member.SecurityStamp != identityUser.SecurityStamp) { changeType = MemberDataChangeType.FullSave; diff --git a/src/Umbraco.Infrastructure/Security/UmbracoPasswordHasher.cs b/src/Umbraco.Infrastructure/Security/UmbracoPasswordHasher.cs index 73d6d2b025..da08bc8713 100644 --- a/src/Umbraco.Infrastructure/Security/UmbracoPasswordHasher.cs +++ b/src/Umbraco.Infrastructure/Security/UmbracoPasswordHasher.cs @@ -53,6 +53,13 @@ namespace Umbraco.Cms.Core.Security if (LegacyPasswordSecurity.SupportHashAlgorithm(deserialized.HashAlgorithm)) { var result = LegacyPasswordSecurity.VerifyPassword(deserialized.HashAlgorithm, providedPassword, hashedPassword); + + //We need to special handle this case, apparently v8 still saves the user algorithm as {"hashAlgorithm":"HMACSHA256"}, when using legacy encoding and hasinging. + if (result == false) + { + result = LegacyPasswordSecurity.VerifyLegacyHashedPassword(providedPassword, hashedPassword); + } + return result ? PasswordVerificationResult.SuccessRehashNeeded : PasswordVerificationResult.Failed; diff --git a/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs b/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs index bf198af1c3..dfef27242b 100644 --- a/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs +++ b/src/Umbraco.Infrastructure/Security/UmbracoUserManager.cs @@ -96,7 +96,7 @@ namespace Umbraco.Cms.Core.Security string password = _passwordGenerator.GeneratePassword(); return password; } - + /// /// Used to validate the password without an identity user /// Validation code is based on the default ValidatePasswordAsync code @@ -205,6 +205,8 @@ namespace Umbraco.Cms.Core.Security await lockoutStore.ResetAccessFailedCountAsync(user, CancellationToken.None); + //Ensure the password config is null, so it is set to the default in repository + user.PasswordConfig = null; return await UpdateAsync(user); } @@ -234,6 +236,11 @@ namespace Umbraco.Cms.Core.Security // here we are persisting the value for the back office } + if (string.IsNullOrEmpty(user.PasswordConfig)) + { + //We cant pass null as that would be interpreted as the default algoritm, but due to the failing attempt we dont know. + user.PasswordConfig = Constants.Security.UnknownPasswordConfigJson; + } IdentityResult result = await UpdateAsync(user); return result; } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberPasswordHasherTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberPasswordHasherTests.cs index 3dd2826baa..c581747c24 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberPasswordHasherTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Security/MemberPasswordHasherTests.cs @@ -1,6 +1,10 @@ using System; using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Infrastructure.Security; using Umbraco.Cms.Infrastructure.Serialization; @@ -10,65 +14,35 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Security [TestFixture] public class MemberPasswordHasherTests { - private MemberPasswordHasher CreateSut() => new MemberPasswordHasher(new LegacyPasswordSecurity(), new JsonNetSerializer()); - [Test] - public void VerifyHashedPassword_GivenAnAspNetIdentity2PasswordHash_ThenExpectSuccessRehashNeeded() + [TestCase("Password123!", "AQAAAAEAACcQAAAAEGF/tTVoL6ef3bQPZFYfbgKFu1CDQIAMgyY1N4EDt9jqdG/hsOX93X1U6LNvlIQ3mw==", null, ExpectedResult = PasswordVerificationResult.Success, Description = "AspNetCoreIdentityPasswordHash: Correct password")] + [TestCase("wrongPassword", "AQAAAAEAACcQAAAAEGF/tTVoL6ef3bQPZFYfbgKFu1CDQIAMgyY1N4EDt9jqdG/hsOX93X1U6LNvlIQ3mw==", null, ExpectedResult = PasswordVerificationResult.Failed, Description = "AspNetCoreIdentityPasswordHash: Wrong password")] + [TestCase("Password123!", "yDiU2YyuYZU4jz6F0fpErQ==BxNRHkXBVyJs9gwWF6ktWdfDwYf5bwm+rvV7tOcNNx8=", null, ExpectedResult = PasswordVerificationResult.SuccessRehashNeeded, Description = "GivenALegacyPasswordHash: Correct password")] + [TestCase("wrongPassword", "yDiU2YyuYZU4jz6F0fpErQ==BxNRHkXBVyJs9gwWF6ktWdfDwYf5bwm+rvV7tOcNNx8=", null, ExpectedResult = PasswordVerificationResult.Failed, Description = "GivenALegacyPasswordHash: Wrong password")] + [TestCase("Password123!", "AJszAsQqxOYbASKfL3JVUu6cjU18ouizXDfX4j7wLlir8SWj2yQaTepE9e5bIohIsQ==", null, ExpectedResult = PasswordVerificationResult.SuccessRehashNeeded, Description = "GivenALegacyPasswordHash: Correct password")] + [TestCase("wrongPassword", "AJszAsQqxOYbASKfL3JVUu6cjU18ouizXDfX4j7wLlir8SWj2yQaTepE9e5bIohIsQ==", null, ExpectedResult = PasswordVerificationResult.Failed, Description = "GivenALegacyPasswordHash: Wrong password")] + [TestCase("1234567890", "1234567890", null, ExpectedResult = PasswordVerificationResult.Failed, Description = "ClearText: Correct password, but not supported")] + [TestCase("wrongPassword", "1234567890", null, ExpectedResult = PasswordVerificationResult.Failed, Description = "ClearText: Wrong password")] + [TestCase("1234567890", "XyFRG4/xJ5JGQJYqqIFK70BjHdM=", null, ExpectedResult = PasswordVerificationResult.SuccessRehashNeeded, Description = "Hashed: Correct password")] + [TestCase("wrongPassword", "XyFRG4/xJ5JGQJYqqIFK70BjHdM=", null, ExpectedResult = PasswordVerificationResult.Failed, Description = "Hashed: Wrong password")] + [TestCase("1234567890", "K2JPOhoqNoysfnnD67QsWDSliHrjoSTRTvv9yiaKf30=", "1D43BFA074DF6DCEF6E44A7F5B5F56CDDD60BE198FBBB0222C96A5BD696F3CAA", ExpectedResult = PasswordVerificationResult.SuccessRehashNeeded, Description = "Encrypted: Correct password and correct decryptionKey")] + [TestCase("wrongPassword", "K2JPOhoqNoysfnnD67QsWDSliHrjoSTRTvv9yiaKf30=", "1D43BFA074DF6DCEF6E44A7F5B5F56CDDD60BE198FBBB0222C96A5BD696F3CAA", ExpectedResult = PasswordVerificationResult.Failed, Description = "Encrypted: Wrong password but correct decryptionKey")] + [TestCase("1234567890", "qiuwRr4K7brpTcIzLFfR3iGG9zj4/z4ewHCVZmYUDKM=", "B491B602E0CE1D52450A8089FD2013B340743A7EFCC12B039BD11977A083ACA1", ExpectedResult = PasswordVerificationResult.Failed, Description = "Encrypted: Correct password but wrong decryptionKey")] + [TestCase("1234567890", "qiuwRr4K7brpTcIzLFfR3iGG9zj4/z4ewHCVZmYUDKM=", "InvalidDecryptionKey", ExpectedResult = PasswordVerificationResult.Failed, Description = "Encrypted: Invalid decryptionKey")] + public PasswordVerificationResult VerifyHashedPassword(string password, string encryptedPassword, string decryptionKey) { - const string password = "Password123!"; - const string hash = "AJszAsQqxOYbASKfL3JVUu6cjU18ouizXDfX4j7wLlir8SWj2yQaTepE9e5bIohIsQ=="; + var member = new MemberIdentityUser() { PasswordConfig = null }; - var sut = CreateSut(); - var result = sut.VerifyHashedPassword(new MemberIdentityUser(), hash, password); + var sut = new MemberPasswordHasher( + new LegacyPasswordSecurity(), + new JsonNetSerializer(), + Options.Create(new LegacyPasswordMigrationSettings() + { + MachineKeyDecryptionKey = decryptionKey + }), + NullLoggerFactory.Instance.CreateLogger()); - Assert.AreEqual(result, PasswordVerificationResult.SuccessRehashNeeded); - } - - [Test] - public void VerifyHashedPassword_GivenAnAspNetCoreIdentityPasswordHash_ThenExpectSuccess() - { - const string password = "Password123!"; - const string hash = "AQAAAAEAACcQAAAAEGF/tTVoL6ef3bQPZFYfbgKFu1CDQIAMgyY1N4EDt9jqdG/hsOX93X1U6LNvlIQ3mw=="; - - var sut = CreateSut(); - var result = sut.VerifyHashedPassword(new MemberIdentityUser(), hash, password); - - Assert.AreEqual(result, PasswordVerificationResult.Success); - } - - [Test] - public void VerifyHashedPassword_GivenALegacyPasswordHash_ThenExpectSuccessRehashNeeded() - { - const string password = "Password123!"; - const string hash = "yDiU2YyuYZU4jz6F0fpErQ==BxNRHkXBVyJs9gwWF6ktWdfDwYf5bwm+rvV7tOcNNx8="; - - var sut = CreateSut(); - var result = sut.VerifyHashedPassword(new MemberIdentityUser(), hash, password); - - Assert.AreEqual(result, PasswordVerificationResult.SuccessRehashNeeded); - } - - [Test] - public void VerifyHashedPassword_GivenAnUnknownBase64Hash_ThenExpectInvalidOperationException() - { - var hashBytes = new byte[] {3, 2, 1}; - var hash = Convert.ToBase64String(hashBytes); - - var sut = CreateSut(); - Assert.Throws(() => sut.VerifyHashedPassword(new MemberIdentityUser(), hash, "password")); - } - - [TestCase("AJszAsQqxOYbASKfL3JVUu6cjU18ouizXDfX4j7wLlir8SWj2yQaTepE9e5bIohIsQ==")] - [TestCase("AQAAAAEAACcQAAAAEGF/tTVoL6ef3bQPZFYfbgKFu1CDQIAMgyY1N4EDt9jqdG/hsOX93X1U6LNvlIQ3mw==")] - [TestCase("yDiU2YyuYZU4jz6F0fpErQ==BxNRHkXBVyJs9gwWF6ktWdfDwYf5bwm+rvV7tOcNNx8=")] - public void VerifyHashedPassword_GivenAnInvalidPassword_ThenExpectFailure(string hash) - { - const string invalidPassword = "nope"; - - var sut = CreateSut(); - var result = sut.VerifyHashedPassword(new MemberIdentityUser(), hash, invalidPassword); - - Assert.AreEqual(result, PasswordVerificationResult.Failed); + return sut.VerifyHashedPassword(member, encryptedPassword, password); } } }