From 63befa55d5ec53fe0ebb1b7c514f0d1a1afcbc3f Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 20 Jul 2017 12:53:09 +1000 Subject: [PATCH] Updates password changing for users to use ASP.NET Identity, there's still some more work here to do to implement the IUserAwarePasswordHashing and to result some of the TODOs. Also moved all password changing logic to PasswordChanger even for legacy membership providers. --- .../Security/BackOfficeUserManager.cs | 9 +- .../Security/MembershipPasswordHasher.cs | 31 --- .../MembershipProviderPasswordHasher.cs | 47 ++++ src/Umbraco.Core/Umbraco.Core.csproj | 2 +- .../Editors/AuthenticationController.cs | 4 +- .../Editors/CurrentUserController.cs | 5 +- .../Editors/PasswordChangeControllerHelper.cs | 39 --- src/Umbraco.Web/Editors/PasswordChanger.cs | 246 ++++++++++++++++++ src/Umbraco.Web/Editors/UsersController.cs | 6 +- .../Install/InstallSteps/NewInstallStep.cs | 1 + src/Umbraco.Web/Security/MembershipHelper.cs | 125 +-------- src/Umbraco.Web/Security/WebSecurity.cs | 8 +- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- 13 files changed, 320 insertions(+), 205 deletions(-) delete mode 100644 src/Umbraco.Core/Security/MembershipPasswordHasher.cs create mode 100644 src/Umbraco.Core/Security/MembershipProviderPasswordHasher.cs delete mode 100644 src/Umbraco.Web/Editors/PasswordChangeControllerHelper.cs create mode 100644 src/Umbraco.Web/Editors/PasswordChanger.cs diff --git a/src/Umbraco.Core/Security/BackOfficeUserManager.cs b/src/Umbraco.Core/Security/BackOfficeUserManager.cs index ad22c1426e..993f0671f3 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserManager.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserManager.cs @@ -137,7 +137,9 @@ namespace Umbraco.Core.Security /// Initializes the user manager with the correct options /// /// - /// + /// + /// The for the users called UsersMembershipProvider + /// /// /// protected void InitUserManager( @@ -153,11 +155,10 @@ namespace Umbraco.Core.Security }; // Configure validation logic for passwords - var provider = MembershipProviderExtensions.GetUsersMembershipProvider(); - manager.PasswordValidator = new MembershipProviderPasswordValidator(provider); + manager.PasswordValidator = new MembershipProviderPasswordValidator(membershipProvider); //use a custom hasher based on our membership provider - manager.PasswordHasher = new MembershipPasswordHasher(membershipProvider); + manager.PasswordHasher = new MembershipProviderPasswordHasher(membershipProvider); if (dataProtectionProvider != null) { diff --git a/src/Umbraco.Core/Security/MembershipPasswordHasher.cs b/src/Umbraco.Core/Security/MembershipPasswordHasher.cs deleted file mode 100644 index 56daa3efdd..0000000000 --- a/src/Umbraco.Core/Security/MembershipPasswordHasher.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.AspNet.Identity; - -namespace Umbraco.Core.Security -{ - /// - /// A custom password hasher that conforms to the current password hashing done in Umbraco - /// - internal class MembershipPasswordHasher : IPasswordHasher - { - private readonly MembershipProviderBase _provider; - - public MembershipPasswordHasher(MembershipProviderBase provider) - { - _provider = provider; - } - - public string HashPassword(string password) - { - return _provider.HashPasswordForStorage(password); - } - - public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword) - { - return _provider.VerifyPassword(providedPassword, hashedPassword) - ? PasswordVerificationResult.Success - : PasswordVerificationResult.Failed; - } - - - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Security/MembershipProviderPasswordHasher.cs b/src/Umbraco.Core/Security/MembershipProviderPasswordHasher.cs new file mode 100644 index 0000000000..01f62ca51b --- /dev/null +++ b/src/Umbraco.Core/Security/MembershipProviderPasswordHasher.cs @@ -0,0 +1,47 @@ +using System; +using Microsoft.AspNet.Identity; + +namespace Umbraco.Core.Security +{ + public interface IUserAwarePasswordHasher + where TKey : IEquatable + { + string HashPassword(TKey id, string password); + string VerifyHashedPassword(TKey id, string hashedPassword, string providedPassword); + } + + public interface IMembershipProviderPasswordHasher : IPasswordHasher + { + MembershipProviderBase MembershipProvider { get; } + } + + /// + /// A custom password hasher that conforms to the password hashing done with membership providers + /// + public class MembershipProviderPasswordHasher : IMembershipProviderPasswordHasher + { + /// + /// Exposes the underlying MembershipProvider + /// + public MembershipProviderBase MembershipProvider { get; private set; } + + public MembershipProviderPasswordHasher(MembershipProviderBase provider) + { + MembershipProvider = provider; + } + + public string HashPassword(string password) + { + return MembershipProvider.HashPasswordForStorage(password); + } + + public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword) + { + return MembershipProvider.VerifyPassword(providedPassword, hashedPassword) + ? PasswordVerificationResult.Success + : PasswordVerificationResult.Failed; + } + + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 0edbdc77cf..4d46e6c319 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -663,7 +663,7 @@ - + diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index f61e11a791..f38a9965cd 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -56,7 +56,9 @@ namespace Umbraco.Web.Editors /// [WebApi.UmbracoAuthorize(requireApproval: false)] public IDictionary GetMembershipProviderConfig() - { + { + //TODO: Check if the current PasswordValidator is an IMembershipProviderPasswordValidator, if + //it's not than we should return some generic defaults var provider = Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider(); return provider.GetConfiguration(Services.UserService); } diff --git a/src/Umbraco.Web/Editors/CurrentUserController.cs b/src/Umbraco.Web/Editors/CurrentUserController.cs index 8db46ec1e8..a374c5579d 100644 --- a/src/Umbraco.Web/Editors/CurrentUserController.cs +++ b/src/Umbraco.Web/Editors/CurrentUserController.cs @@ -86,9 +86,10 @@ namespace Umbraco.Web.Editors /// /// If the password is being reset it will return the newly reset password, otherwise will return an empty value /// - public ModelWithNotifications PostChangePassword(ChangingPasswordModel data) + public async Task> PostChangePassword(ChangingPasswordModel data) { - var passwordChangeResult = PasswordChangeControllerHelper.ChangePassword(Security.CurrentUser, data, ModelState, Members); + var passwordChanger = new PasswordChanger(Logger, Services.UserService); + var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(Security.CurrentUser, data, ModelState, UserManager); if (passwordChangeResult.Success) { diff --git a/src/Umbraco.Web/Editors/PasswordChangeControllerHelper.cs b/src/Umbraco.Web/Editors/PasswordChangeControllerHelper.cs deleted file mode 100644 index 899e0eb9da..0000000000 --- a/src/Umbraco.Web/Editors/PasswordChangeControllerHelper.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Linq; -using System.Web.Http.ModelBinding; -using Umbraco.Core; -using Umbraco.Core.Models.Membership; -using Umbraco.Web.Models; -using Umbraco.Web.Security; - -namespace Umbraco.Web.Editors -{ - internal class PasswordChangeControllerHelper - { - - public static Attempt ChangePassword( - IUser currentUser, - ChangingPasswordModel data, - ModelStateDictionary modelState, - MembershipHelper membersHelper) - { - var userProvider = Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider(); - - if (userProvider.RequiresQuestionAndAnswer) - { - throw new NotSupportedException("Currently the user editor does not support providers that have RequiresQuestionAndAnswer specified"); - } - - var passwordChangeResult = membersHelper.ChangePassword(currentUser.Username, data, userProvider); - if (passwordChangeResult.Success == false) - { - //it wasn't successful, so add the change error to the model state - var fieldName = passwordChangeResult.Result.ChangeError.MemberNames.FirstOrDefault() ?? "password"; - modelState.AddModelError(fieldName, - passwordChangeResult.Result.ChangeError.ErrorMessage); - } - - return passwordChangeResult; - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/PasswordChanger.cs b/src/Umbraco.Web/Editors/PasswordChanger.cs new file mode 100644 index 0000000000..ffd5a9b0fd --- /dev/null +++ b/src/Umbraco.Web/Editors/PasswordChanger.cs @@ -0,0 +1,246 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using System.Web.Http.ModelBinding; +using System.Web.Security; +using Microsoft.AspNet.Identity; +using umbraco.cms.businesslogic.packager; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.Identity; +using Umbraco.Core.Security; +using Umbraco.Core.Services; +using Umbraco.Web.Models; +using Umbraco.Web.Security; +using IUser = Umbraco.Core.Models.Membership.IUser; + +namespace Umbraco.Web.Editors +{ + internal class PasswordChanger + { + private readonly ILogger _logger; + private readonly IUserService _userService; + + public PasswordChanger(ILogger logger, IUserService userService) + { + _logger = logger; + _userService = userService; + } + + public async Task> ChangePasswordWithIdentityAsync( + IUser currentUser, + ChangingPasswordModel passwordModel, + ModelStateDictionary modelState, + BackOfficeUserManager userMgr) + { + if (passwordModel == null) throw new ArgumentNullException("passwordModel"); + if (userMgr == null) throw new ArgumentNullException("userMgr"); + + //check if this identity implementation is powered by an underlying membership provider (it will be in most cases) + var membershipPasswordHasher = userMgr.PasswordHasher as IMembershipProviderPasswordHasher; + + //check if this identity implementation is powered by an IUserAwarePasswordHasher (it will be by default in 7.7+ but not for upgrades) + var userAwarePasswordHasher = userMgr.PasswordHasher as IUserAwarePasswordHasher; + + if (membershipPasswordHasher != null && userAwarePasswordHasher == null) + { + //if this isn't using an IUserAwarePasswordHasher, then fallback to the old way + if (membershipPasswordHasher.MembershipProvider.RequiresQuestionAndAnswer) + throw new NotSupportedException("Currently the user editor does not support providers that have RequiresQuestionAndAnswer specified"); + return ChangePasswordWithMembershipProvider(currentUser.Username, passwordModel, membershipPasswordHasher.MembershipProvider); + } + + //get the real password validator, thsi should not be null but in some very rare cases it could be, in which case + //we need to create a default password validator to use since we have no idea what it actually is or what it's rules are + //this is an Edge Case! + var passwordValidator = userMgr.PasswordValidator as PasswordValidator + ?? (membershipPasswordHasher != null + ? new MembershipProviderPasswordValidator(membershipPasswordHasher.MembershipProvider) + : new PasswordValidator()); + + //Are we resetting the password?? + if (passwordModel.Reset.HasValue && passwordModel.Reset.Value) + { + //ok, we should be able to reset it + var resetToken = await userMgr.GeneratePasswordResetTokenAsync(currentUser.Id); + var newPass = Membership.GeneratePassword(passwordValidator.RequiredLength, passwordValidator.RequireNonLetterOrDigit ? 2 : 0); + var resetResult = await userMgr.ResetPasswordAsync(currentUser.Id, resetToken, newPass); + + if (resetResult.Succeeded == false) + { + var errors = string.Join(". ", resetResult.Errors); + _logger.Warn(string.Format("Could not reset member password {0}", errors)); + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not reset password, errors: " + errors, new[] { "resetPassword" }) }); + } + + return Attempt.Succeed(new PasswordChangedModel { ResetPassword = newPass }); + } + + //we're not resetting it so we need to try to change it. + + if (passwordModel.NewPassword.IsNullOrWhiteSpace()) + { + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Cannot set an empty password", new[] { "value" }) }); + } + + //we cannot arbitrarily change the password without knowing the old one and no old password was supplied - need to return an error + //TODO: What if the current user is admin? We should allow manually changing then? + if (passwordModel.OldPassword.IsNullOrWhiteSpace()) + { + //if password retrieval is not enabled but there is no old password we cannot continue + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the old password", new[] { "oldPassword" }) }); + } + + if (passwordModel.OldPassword.IsNullOrWhiteSpace() == false) + { + //if an old password is suplied try to change it + var changeResult = await userMgr.ChangePasswordAsync(currentUser.Id, passwordModel.OldPassword, passwordModel.NewPassword); + if (changeResult.Succeeded == false) + { + var errors = string.Join(". ", changeResult.Errors); + _logger.Warn(string.Format("Could not change member password {0}", errors)); + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, errors: " + errors, new[] { "value" }) }); + } + return Attempt.Succeed(new PasswordChangedModel()); + } + + //We shouldn't really get here + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, invalid information supplied", new[] { "value" }) }); + } + + /// + /// Changes password for a member/user given the membership provider and the password change model + /// + /// + /// + /// + /// + public Attempt ChangePasswordWithMembershipProvider(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider) + { + // YES! It is completely insane how many options you have to take into account based on the membership provider. yikes! + + if (passwordModel == null) throw new ArgumentNullException("passwordModel"); + if (membershipProvider == null) throw new ArgumentNullException("membershipProvider"); + + //Are we resetting the password?? + if (passwordModel.Reset.HasValue && passwordModel.Reset.Value) + { + var canReset = membershipProvider.CanResetPassword(_userService); + if (canReset == false) + { + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password reset is not enabled", new[] { "resetPassword" }) }); + } + if (membershipProvider.RequiresQuestionAndAnswer && passwordModel.Answer.IsNullOrWhiteSpace()) + { + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password reset requires a password answer", new[] { "resetPassword" }) }); + } + //ok, we should be able to reset it + try + { + var newPass = membershipProvider.ResetPassword( + username, + membershipProvider.RequiresQuestionAndAnswer ? passwordModel.Answer : null); + + //return the generated pword + return Attempt.Succeed(new PasswordChangedModel { ResetPassword = newPass }); + } + catch (Exception ex) + { + _logger.WarnWithException("Could not reset member password", ex); + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not reset password, error: " + ex.Message + " (see log for full details)", new[] { "resetPassword" }) }); + } + } + + //we're not resetting it so we need to try to change it. + + if (passwordModel.NewPassword.IsNullOrWhiteSpace()) + { + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Cannot set an empty password", new[] { "value" }) }); + } + + //This is an edge case and is only necessary for backwards compatibility: + var umbracoBaseProvider = membershipProvider as MembershipProviderBase; + if (umbracoBaseProvider != null && umbracoBaseProvider.AllowManuallyChangingPassword) + { + //this provider allows manually changing the password without the old password, so we can just do it + try + { + var result = umbracoBaseProvider.ChangePassword(username, "", passwordModel.NewPassword); + return result == false + ? Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, invalid username or password", new[] { "value" }) }) + : Attempt.Succeed(new PasswordChangedModel()); + } + catch (Exception ex) + { + _logger.WarnWithException("Could not change member password", ex); + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex.Message + " (see log for full details)", new[] { "value" }) }); + } + } + + //The provider does not support manually chaning the password but no old password supplied - need to return an error + if (passwordModel.OldPassword.IsNullOrWhiteSpace() && membershipProvider.EnablePasswordRetrieval == false) + { + //if password retrieval is not enabled but there is no old password we cannot continue + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the old password", new[] { "oldPassword" }) }); + } + + if (passwordModel.OldPassword.IsNullOrWhiteSpace() == false) + { + //if an old password is suplied try to change it + + try + { + var result = membershipProvider.ChangePassword(username, passwordModel.OldPassword, passwordModel.NewPassword); + return result == false + ? Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, invalid username or password", new[] { "oldPassword" }) }) + : Attempt.Succeed(new PasswordChangedModel()); + } + catch (Exception ex) + { + _logger.WarnWithException("Could not change member password", ex); + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex.Message + " (see log for full details)", new[] { "value" }) }); + } + } + + if (membershipProvider.EnablePasswordRetrieval == false) + { + //we cannot continue if we cannot get the current password + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the old password", new[] { "oldPassword" }) }); + } + if (membershipProvider.RequiresQuestionAndAnswer && passwordModel.Answer.IsNullOrWhiteSpace()) + { + //if the question answer is required but there isn't one, we cannot continue + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the password answer", new[] { "value" }) }); + } + + //lets try to get the old one so we can change it + try + { + var oldPassword = membershipProvider.GetPassword( + username, + membershipProvider.RequiresQuestionAndAnswer ? passwordModel.Answer : null); + + try + { + var result = membershipProvider.ChangePassword(username, oldPassword, passwordModel.NewPassword); + return result == false + ? Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password", new[] { "value" }) }) + : Attempt.Succeed(new PasswordChangedModel()); + } + catch (Exception ex1) + { + _logger.WarnWithException("Could not change member password", ex1); + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex1.Message + " (see log for full details)", new[] { "value" }) }); + } + + } + catch (Exception ex2) + { + _logger.WarnWithException("Could not retrieve member password", ex2); + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex2.Message + " (see log for full details)", new[] { "value" }) }); + } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 0039128e86..9164ea8630 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -404,7 +404,7 @@ namespace Umbraco.Web.Editors /// /// /// - public UserDisplay PostSaveUser(UserSave userSave) + public async Task PostSaveUser(UserSave userSave) { if (userSave == null) throw new ArgumentNullException("userSave"); @@ -460,7 +460,9 @@ namespace Umbraco.Web.Editors var resetPasswordValue = string.Empty; if (userSave.ChangePassword != null) { - var passwordChangeResult = PasswordChangeControllerHelper.ChangePassword(found, userSave.ChangePassword, ModelState, Members); + var passwordChanger = new PasswordChanger(Logger, Services.UserService); + + var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(found, userSave.ChangePassword, ModelState, UserManager); if (passwordChangeResult.Success) { //depending on how the provider is configured, the password may be reset so let's store that for later diff --git a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs index 7a6a54f9e7..2d0daf1aa9 100644 --- a/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/NewInstallStep.cs @@ -32,6 +32,7 @@ namespace Umbraco.Web.Install.InstallSteps _applicationContext = applicationContext; } + //TODO: Change all logic in this step to use ASP.NET Identity NOT MembershipProviders private MembershipProvider CurrentProvider { get diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index 81487c41d9..43d3c55c65 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -13,6 +13,7 @@ using Umbraco.Core.Security; using Umbraco.Web.Models; using Umbraco.Web.PublishedCache; using Umbraco.Core.Cache; +using Umbraco.Web.Editors; using Umbraco.Web.Security.Providers; using MPE = global::Umbraco.Core.Security.MembershipProviderExtensions; @@ -655,128 +656,8 @@ namespace Umbraco.Web.Security /// public virtual Attempt ChangePassword(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider) { - // YES! It is completely insane how many options you have to take into account based on the membership provider. yikes! - - if (passwordModel == null) throw new ArgumentNullException("passwordModel"); - if (membershipProvider == null) throw new ArgumentNullException("membershipProvider"); - - //Are we resetting the password?? - if (passwordModel.Reset.HasValue && passwordModel.Reset.Value) - { - var canReset = membershipProvider.CanResetPassword(_applicationContext.Services.UserService); - if (canReset == false) - { - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password reset is not enabled", new[] { "resetPassword" }) }); - } - if (membershipProvider.RequiresQuestionAndAnswer && passwordModel.Answer.IsNullOrWhiteSpace()) - { - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password reset requires a password answer", new[] { "resetPassword" }) }); - } - //ok, we should be able to reset it - try - { - var newPass = membershipProvider.ResetPassword( - username, - membershipProvider.RequiresQuestionAndAnswer ? passwordModel.Answer : null); - - //return the generated pword - return Attempt.Succeed(new PasswordChangedModel { ResetPassword = newPass }); - } - catch (Exception ex) - { - LogHelper.WarnWithException("Could not reset member password", ex); - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not reset password, error: " + ex.Message + " (see log for full details)", new[] { "resetPassword" }) }); - } - } - - //we're not resetting it so we need to try to change it. - - if (passwordModel.NewPassword.IsNullOrWhiteSpace()) - { - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Cannot set an empty password", new[] { "value" }) }); - } - - //This is an edge case and is only necessary for backwards compatibility: - var umbracoBaseProvider = membershipProvider as MembershipProviderBase; - if (umbracoBaseProvider != null && umbracoBaseProvider.AllowManuallyChangingPassword) - { - //this provider allows manually changing the password without the old password, so we can just do it - try - { - var result = umbracoBaseProvider.ChangePassword(username, "", passwordModel.NewPassword); - return result == false - ? Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, invalid username or password", new[] { "value" }) }) - : Attempt.Succeed(new PasswordChangedModel()); - } - catch (Exception ex) - { - LogHelper.WarnWithException("Could not change member password", ex); - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex.Message + " (see log for full details)", new[] { "value" }) }); - } - } - - //The provider does not support manually chaning the password but no old password supplied - need to return an error - if (passwordModel.OldPassword.IsNullOrWhiteSpace() && membershipProvider.EnablePasswordRetrieval == false) - { - //if password retrieval is not enabled but there is no old password we cannot continue - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the old password", new[] { "oldPassword" }) }); - } - - if (passwordModel.OldPassword.IsNullOrWhiteSpace() == false) - { - //if an old password is suplied try to change it - - try - { - var result = membershipProvider.ChangePassword(username, passwordModel.OldPassword, passwordModel.NewPassword); - return result == false - ? Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, invalid username or password", new[] { "oldPassword" }) }) - : Attempt.Succeed(new PasswordChangedModel()); - } - catch (Exception ex) - { - LogHelper.WarnWithException("Could not change member password", ex); - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex.Message + " (see log for full details)", new[] { "value" }) }); - } - } - - if (membershipProvider.EnablePasswordRetrieval == false) - { - //we cannot continue if we cannot get the current password - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the old password", new[] { "oldPassword" }) }); - } - if (membershipProvider.RequiresQuestionAndAnswer && passwordModel.Answer.IsNullOrWhiteSpace()) - { - //if the question answer is required but there isn't one, we cannot continue - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password cannot be changed without the password answer", new[] { "value" }) }); - } - - //lets try to get the old one so we can change it - try - { - var oldPassword = membershipProvider.GetPassword( - username, - membershipProvider.RequiresQuestionAndAnswer ? passwordModel.Answer : null); - - try - { - var result = membershipProvider.ChangePassword(username, oldPassword, passwordModel.NewPassword); - return result == false - ? Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password", new[] { "value" }) }) - : Attempt.Succeed(new PasswordChangedModel()); - } - catch (Exception ex1) - { - LogHelper.WarnWithException("Could not change member password", ex1); - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex1.Message + " (see log for full details)", new[] { "value" }) }); - } - - } - catch (Exception ex2) - { - LogHelper.WarnWithException("Could not retrieve member password", ex2); - return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex2.Message + " (see log for full details)", new[] { "value" }) }); - } + var passwordChanger = new PasswordChanger(_applicationContext.ProfilingLogger.Logger, _applicationContext.Services.UserService); + return passwordChanger.ChangePasswordWithMembershipProvider(username, passwordModel, membershipProvider); } /// diff --git a/src/Umbraco.Web/Security/WebSecurity.cs b/src/Umbraco.Web/Security/WebSecurity.cs index e4a29bd6cc..e46250bed8 100644 --- a/src/Umbraco.Web/Security/WebSecurity.cs +++ b/src/Umbraco.Web/Security/WebSecurity.cs @@ -170,10 +170,14 @@ namespace Umbraco.Web.Security /// /// /// + /// + /// This uses ASP.NET Identity to perform the validation + /// public virtual bool ValidateBackOfficeCredentials(string username, string password) { - var membershipProvider = Core.Security.MembershipProviderExtensions.GetUsersMembershipProvider(); - return membershipProvider != null && membershipProvider.ValidateUser(username, password); + var backofficeuser = Mapper.Map(CurrentUser); + backofficeuser.UserName = username; + return UserManager.CheckPasswordAsync(backofficeuser, password).Result; } /// diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index afa4790262..29f4d04f26 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -333,7 +333,7 @@ - +