diff --git a/src/Umbraco.Core/Security/MembershipProviderBase.cs b/src/Umbraco.Core/Security/MembershipProviderBase.cs index 9d76c8e595..3794327781 100644 --- a/src/Umbraco.Core/Security/MembershipProviderBase.cs +++ b/src/Umbraco.Core/Security/MembershipProviderBase.cs @@ -11,76 +11,6 @@ using Umbraco.Core.Logging; namespace Umbraco.Core.Security { - - public abstract class UmbracoMembershipProviderBase : MembershipProviderBase - { - protected UmbracoMembershipProviderBase() - { - //Set the defaults! - DefaultMemberTypeAlias = "Member"; - } - - public string DefaultMemberTypeAlias { get; protected set; } - - /// - /// Adds a new membership user to the data source. - /// - /// The user name for the new user. - /// The password for the new user. - /// The e-mail address for the new user. - /// The password question for the new user. - /// The password answer for the new user - /// Whether or not the new user is approved to be validated. - /// The unique identifier from the membership data source for the user. - /// A enumeration value indicating whether the user was created successfully. - /// - /// A object populated with the information for the newly created user. - /// - protected sealed override MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) - { - return PerformCreateUser(DefaultMemberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); - } - - /// - /// Adds a new membership user to the data source. - /// - /// The member type alias to use when creating the member - /// The user name for the new user. - /// The password for the new user. - /// The e-mail address for the new user. - /// The password question for the new user. - /// The password answer for the new user - /// Whether or not the new user is approved to be validated. - /// The unique identifier from the membership data source for the user. - /// A enumeration value indicating whether the user was created successfully. - /// - /// A object populated with the information for the newly created user. - /// - public MembershipUser CreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) - { - //do the base validation first - base.CreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); - - return PerformCreateUser(memberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); - } - - /// - /// Adds a new membership user to the data source. - /// - /// The member type alias to use when creating the member - /// The user name for the new user. - /// The password for the new user. - /// The e-mail address for the new user. - /// The password question for the new user. - /// The password answer for the new user - /// Whether or not the new user is approved to be validated. - /// The unique identifier from the membership data source for the user. - /// A enumeration value indicating whether the user was created successfully. - /// - /// A object populated with the information for the newly created user. - /// - protected abstract MembershipUser PerformCreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status); - } /// /// A base membership provider class offering much of the underlying functionality for initializing and password encryption/hashing. /// @@ -741,7 +671,7 @@ namespace Umbraco.Core.Security /// /// /// - protected string EncryptOrHashNewPassword(string newPassword, out string salt) + protected internal string EncryptOrHashNewPassword(string newPassword, out string salt) { salt = GenerateSalt(); return EncryptOrHashPassword(newPassword, salt); diff --git a/src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs b/src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs new file mode 100644 index 0000000000..6ecb6eedce --- /dev/null +++ b/src/Umbraco.Core/Security/UmbracoMembershipProviderBase.cs @@ -0,0 +1,78 @@ +using System.Web.Security; + +namespace Umbraco.Core.Security +{ + + /// + /// A base membership provider class for umbraco members (not users) + /// + public abstract class UmbracoMembershipProviderBase : MembershipProviderBase + { + protected UmbracoMembershipProviderBase() + { + //Set the defaults! + DefaultMemberTypeAlias = "Member"; + } + + public string DefaultMemberTypeAlias { get; protected set; } + + /// + /// Adds a new membership user to the data source. + /// + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + protected sealed override MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) + { + return PerformCreateUser(DefaultMemberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); + } + + /// + /// Adds a new membership user to the data source. + /// + /// The member type alias to use when creating the member + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + public MembershipUser CreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) + { + //do the base validation first + base.CreateUser(username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); + + return PerformCreateUser(memberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status); + } + + /// + /// Adds a new membership user to the data source. + /// + /// The member type alias to use when creating the member + /// The user name for the new user. + /// The password for the new user. + /// The e-mail address for the new user. + /// The password question for the new user. + /// The password answer for the new user + /// Whether or not the new user is approved to be validated. + /// The unique identifier from the membership data source for the user. + /// A enumeration value indicating whether the user was created successfully. + /// + /// A object populated with the information for the newly created user. + /// + protected abstract MembershipUser PerformCreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 87c15573c6..05bcafba38 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.Services /// /// Defines the MemberService, which is an easy access to operations involving (umbraco) members. /// - internal interface IMemberService : IMembershipMemberService + public interface IMemberService : IMembershipMemberService { /// /// Checks if a member with the id exists diff --git a/src/Umbraco.Core/Services/IMembershipMemberService.cs b/src/Umbraco.Core/Services/IMembershipMemberService.cs index 6c3027d21c..0b9ff79665 100644 --- a/src/Umbraco.Core/Services/IMembershipMemberService.cs +++ b/src/Umbraco.Core/Services/IMembershipMemberService.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Services /// /// Idea is to have this is an isolated interface so that it can be easily 'replaced' in the membership provider impl. /// - internal interface IMembershipMemberService : IService + public interface IMembershipMemberService : IService { /// /// Checks if a member with the username exists diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 9f7bd325a1..3119ae7cf0 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -741,6 +741,7 @@ + diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index d86051cdee..0af67e158b 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -82,6 +82,7 @@ + diff --git a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs index cf29e71eab..2af97b49e2 100644 --- a/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs +++ b/src/Umbraco.Tests/Membership/MembershipProviderBaseTests.cs @@ -7,8 +7,13 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web.Security; +using Moq; using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; using Umbraco.Core.Security; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers.Entities; using Umbraco.Web.Security.Providers; namespace Umbraco.Tests.Membership @@ -20,31 +25,243 @@ namespace Umbraco.Tests.Membership //public void Set_Default_Member_Type_On_Init() //[Test] - //public void Question_Answer_Is_Encrypted() + //public void Create_User_Already_Exists() + //{ + + //} + + //[Test] + //public void Create_User_Requires_Unique_Email() + //{ + + //} + + [Test] + public void Answer_Is_Encrypted() + { + IMember createdMember = null; + var memberType = MockedContentTypes.CreateSimpleMemberType(); + foreach (var p in Constants.Conventions.Member.GetStandardPropertyTypeStubs()) + { + memberType.AddPropertyType(p.Value); + } + var mServiceMock = new Mock(); + mServiceMock.Setup(service => service.Exists("test")).Returns(false); + mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); + mServiceMock.Setup( + service => service.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string u, string e, string p, string m) => + { + createdMember = new Member("test", e, u, p, memberType); + }) + .Returns(() => createdMember); + var provider = new MembersMembershipProvider(mServiceMock.Object); + + MembershipCreateStatus status; + provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); + + Assert.AreNotEqual("test", createdMember.PasswordAnswer); + Assert.AreEqual(provider.EncryptString("test"), createdMember.PasswordAnswer); + } + + [Test] + public void Password_Encrypted_With_Salt() + { + IMember createdMember = null; + var memberType = MockedContentTypes.CreateSimpleMemberType(); + foreach (var p in Constants.Conventions.Member.GetStandardPropertyTypeStubs()) + { + memberType.AddPropertyType(p.Value); + } + var mServiceMock = new Mock(); + mServiceMock.Setup(service => service.Exists("test")).Returns(false); + mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); + mServiceMock.Setup( + service => service.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string u, string e, string p, string m) => + { + createdMember = new Member("test", e, u, p, memberType); + }) + .Returns(() => createdMember); + + var provider = new MembersMembershipProvider(mServiceMock.Object); + provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Encrypted" } }); + MembershipCreateStatus status; + provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); + + Assert.AreNotEqual("test", createdMember.Password); + //Assert.AreNotEqual(provider.EncryptString("test"), createdMember.PasswordAnswer); + string salt; + var encodedPassword = provider.EncryptOrHashNewPassword("test", out salt); + Assert.AreEqual(encodedPassword, createdMember.Password); + } + + //[Test] + //public void Password_Hashed_With_Salt() + //{ + // IMember createdMember = null; + // var memberType = MockedContentTypes.CreateSimpleMemberType(); + // foreach (var p in Constants.Conventions.Member.GetStandardPropertyTypeStubs()) + // { + // memberType.AddPropertyType(p.Value); + // } + // var mServiceMock = new Mock(); + // mServiceMock.Setup(service => service.Exists("test")).Returns(false); + // mServiceMock.Setup(service => service.GetByEmail("test@test.com")).Returns(() => null); + // mServiceMock.Setup( + // service => service.CreateMember(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + // .Callback((string u, string e, string p, string m) => + // { + // createdMember = new Member("test", e, u, p, memberType); + // }) + // .Returns(() => createdMember); + + // var provider = new MembersMembershipProvider(mServiceMock.Object); + // provider.Initialize("test", new NameValueCollection { { "passwordFormat", "Hashed" } }); + // MembershipCreateStatus status; + // provider.CreateUser("test", "test", "test", "test@test.com", "test", "test", true, "test", out status); + + // Assert.AreNotEqual("test", createdMember.Password); + // Assert.AreNotEqual(provider.EncryptString("test"), createdMember.PasswordAnswer); + // string salt; + // var encodedPassword = provider.EncryptOrHashNewPassword("test", out salt); + // Assert.AreEqual(encodedPassword, createdMember.Password); + //} + + //[Test] + //public void Password_Encrypted_Validated_With_Salt() + + //[Test] + //public void Password_Encrypted_Validated_With_Salt() + } [TestFixture] public class MembershipProviderBaseTests { - //[Test] - //public void Change_Password_Base_Validation() - //[Test] - //public void ChangePasswordQuestionAndAnswer_Base_Validation() - //[Test] - //public void CreateUser_Base_Validation() + [Test] + public void Change_Password_Without_AllowManuallyChangingPassword_And_No_Pass_Validation() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.AllowManuallyChangingPassword).Returns(false); + var provider = providerMock.Object; - //[Test] - //public void GetPassword_Base_Validation() + Assert.Throws(() => provider.ChangePassword("test", "", "test")); + } - //[Test] - //public void ResetPassword_Base_Validation() + [Test] + public void Change_Password_With_AllowManuallyChangingPassword_And_Invalid_Creds() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.AllowManuallyChangingPassword).Returns(false); + providerMock.Setup(@base => @base.ValidateUser("test", "test")).Returns(false); + var provider = providerMock.Object; + + Assert.IsFalse(provider.ChangePassword("test", "test", "test")); + + } + + [Test] + public void ChangePasswordQuestionAndAnswer_Without_RequiresQuestionAndAnswer() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.RequiresQuestionAndAnswer).Returns(false); + var provider = providerMock.Object; + + Assert.Throws(() => provider.ChangePasswordQuestionAndAnswer("test", "test", "test", "test")); + } + + [Test] + public void ChangePasswordQuestionAndAnswer_Without_AllowManuallyChangingPassword_And_Invalid_Creds() + { + var providerMock = new Mock() { CallBase = true }; + providerMock.Setup(@base => @base.RequiresQuestionAndAnswer).Returns(true); + providerMock.Setup(@base => @base.AllowManuallyChangingPassword).Returns(false); + providerMock.Setup(@base => @base.ValidateUser("test", "test")).Returns(false); + var provider = providerMock.Object; + + Assert.IsFalse(provider.ChangePasswordQuestionAndAnswer("test", "test", "test", "test")); + } + + [Test] + public void CreateUser_Not_Whitespace() + { + var providerMock = new Mock() {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() { 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() { 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() { 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() { 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")); + } + + [Test] + public void ResetPassword_Without_EnablePasswordReset() + { + var providerMock = new Mock() { 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 provider = new TestProvider(); + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; provider.Initialize("test", new NameValueCollection()); Assert.AreEqual("test", provider.Name); @@ -65,7 +282,8 @@ namespace Umbraco.Tests.Membership [Test] public void Throws_Exception_With_Hashed_Password_And_Password_Retrieval() { - var provider = new TestProvider(); + var providerMock = new Mock() { CallBase = true }; + var provider = providerMock.Object; Assert.Throws(() => provider.Initialize("test", new NameValueCollection() { @@ -125,88 +343,5 @@ namespace Umbraco.Tests.Membership Assert.AreEqual(salt, initSalt); } - private class TestProvider : MembershipProviderBase - { - public override void UpdateUser(MembershipUser user) - { - throw new NotImplementedException(); - } - - public override bool ValidateUser(string username, string password) - { - throw new NotImplementedException(); - } - - public override bool UnlockUser(string userName) - { - throw new NotImplementedException(); - } - - public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) - { - throw new NotImplementedException(); - } - - public override MembershipUser GetUser(string username, bool userIsOnline) - { - throw new NotImplementedException(); - } - - public override string GetUserNameByEmail(string email) - { - throw new NotImplementedException(); - } - - public override bool DeleteUser(string username, bool deleteAllRelatedData) - { - throw new NotImplementedException(); - } - - public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) - { - throw new NotImplementedException(); - } - - public override int GetNumberOfUsersOnline() - { - throw new NotImplementedException(); - } - - public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) - { - throw new NotImplementedException(); - } - - public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) - { - throw new NotImplementedException(); - } - - protected override bool PerformChangePassword(string username, string oldPassword, string newPassword) - { - throw new NotImplementedException(); - } - - protected override bool PerformChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) - { - throw new NotImplementedException(); - } - - protected override MembershipUser PerformCreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) - { - throw new NotImplementedException(); - } - - protected override string PerformGetPassword(string username, string answer) - { - throw new NotImplementedException(); - } - - protected override string PerformResetPassword(string username, string answer, string generatedPassword) - { - throw new NotImplementedException(); - } - } - } } diff --git a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs index 5e263f76a4..e4fbda1d08 100644 --- a/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/MembersMembershipProvider.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; +using System.Web.Configuration; using System.Web.Hosting; using System.Web.Security; using Umbraco.Core; @@ -30,6 +31,15 @@ namespace Umbraco.Web.Security.Providers get { return _memberService ?? (_memberService = ApplicationContext.Current.Services.MemberService); } } + public MembersMembershipProvider() + { + } + + internal MembersMembershipProvider(IMemberService memberService) + { + _memberService = memberService; + } + public string ProviderName { get { return "MembersMembershipProvider"; } @@ -80,7 +90,7 @@ namespace Umbraco.Web.Security.Providers // This is allowed based on the overridden AllowManuallyChangingPassword option. // in order to support updating passwords from the umbraco core, we can't validate the old password - var m = _memberService.GetByUsername(username); + var m = MemberService.GetByUsername(username); if (m == null) return false; string salt; @@ -88,8 +98,8 @@ namespace Umbraco.Web.Security.Providers m.Password = FormatPasswordForStorage(encodedPassword, salt); m.LastPasswordChangeDate = DateTime.Now; - - _memberService.Save(m); + + MemberService.Save(m); return true; } @@ -113,7 +123,7 @@ namespace Umbraco.Web.Security.Providers } member.PasswordQuestion = newPasswordQuestion; - member.PasswordAnswer = newPasswordAnswer; + member.PasswordAnswer = EncryptString(newPasswordAnswer); MemberService.Save(member); @@ -158,10 +168,14 @@ namespace Umbraco.Web.Security.Providers string salt; var encodedPassword = EncryptOrHashNewPassword(password, out salt); - var member = MemberService.CreateMember(email, username, encodedPassword, memberTypeAlias); + var member = MemberService.CreateMember( + email, + username, + FormatPasswordForStorage(encodedPassword, salt), + memberTypeAlias); member.PasswordQuestion = passwordQuestion; - member.PasswordAnswer = passwordAnswer; + member.PasswordAnswer = EncryptString(passwordAnswer); member.IsApproved = isApproved; member.LastLoginDate = DateTime.Now; member.LastPasswordChangeDate = DateTime.Now; @@ -289,9 +303,9 @@ namespace Umbraco.Web.Security.Providers throw new ProviderException("The supplied user is not found"); } - //TODO: We need to encrypt the answer here to match against the encrypted answer in the database + var encAnswer = EncryptString(answer); - if (RequiresQuestionAndAnswer && m.PasswordAnswer != answer) + if (RequiresQuestionAndAnswer && m.PasswordAnswer != encAnswer) { throw new ProviderException("Incorrect password answer"); } @@ -301,6 +315,15 @@ namespace Umbraco.Web.Security.Providers return decodedPassword; } + internal string EncryptString(string str) + { + var bytes = Encoding.Unicode.GetBytes(str); + var password = new byte[bytes.Length]; + Buffer.BlockCopy(bytes, 0, password, 0, bytes.Length); + var encBytes = EncryptPassword(password, MembershipPasswordCompatibilityMode.Framework40); + return Convert.ToBase64String(encBytes); + } + /// /// Gets information from the data source for a user. Provides an option to update the last-activity date/time stamp for the user. /// @@ -395,16 +418,16 @@ namespace Umbraco.Web.Security.Providers throw new ProviderException("The member is locked out."); } - //TODO: We need to encrypt the answer here to match against the encrypted answer in the database + var encAnswer = EncryptString(answer); - if (RequiresQuestionAndAnswer && m.PasswordAnswer != answer) + if (RequiresQuestionAndAnswer && m.PasswordAnswer != encAnswer) { throw new ProviderException("Incorrect password answer"); } string salt; var encodedPassword = EncryptOrHashNewPassword(generatedPassword, out salt); - m.Password = encodedPassword; + m.Password = FormatPasswordForStorage(encodedPassword, salt); m.LastPasswordChangeDate = DateTime.Now; MemberService.Save(m);