/// <summary> /// /// </summary> /// <param name="changingPasswordModel"></param> /// <returns></returns> public async Task <ModelWithNotifications <string> > PostChangePassword(ChangingPasswordModel changingPasswordModel) { changingPasswordModel = changingPasswordModel ?? throw new ArgumentNullException(nameof(changingPasswordModel)); if (ModelState.IsValid == false) { throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); } var intId = changingPasswordModel.Id.TryConvertTo <int>(); if (intId.Success == false) { throw new HttpResponseException(HttpStatusCode.NotFound); } var found = Services.UserService.GetUserById(intId.Result); if (found == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } var passwordChanger = new PasswordChanger(Logger, Services.UserService, UmbracoContext.HttpContext); var passwordChangeResult = await passwordChanger.ChangePasswordWithIdentityAsync(Security.CurrentUser, found, changingPasswordModel, UserManager); if (passwordChangeResult.Success) { var result = new ModelWithNotifications <string>(passwordChangeResult.Result.ResetPassword); result.AddSuccessNotification(Services.TextService.Localize("general/success"), Services.TextService.Localize("user/passwordChangedGeneric")); return(result); } foreach (var memberName in passwordChangeResult.Result.ChangeError.MemberNames) { ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage); } throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); }
/// <summary> /// Changes password for a member/user given the membership provider and the password change model /// </summary> /// <param name="username"></param> /// <param name="passwordModel"></param> /// <param name="membershipProvider"></param> /// <returns></returns> public virtual Attempt <PasswordChangedModel> ChangePassword(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider) { var passwordChanger = new PasswordChanger(_logger, _userService, HttpContext); return(passwordChanger.ChangePasswordWithMembershipProvider(username, passwordModel, membershipProvider)); }
/// <summary> /// Changes password for a member/user given the membership provider name and the password change model /// </summary> /// <param name="username"></param> /// <param name="passwordModel"></param> /// <param name="membershipProviderName"></param> /// <returns></returns> public virtual Attempt <PasswordChangedModel> ChangePassword(string username, ChangingPasswordModel passwordModel, string membershipProviderName) { var provider = Membership.Providers[membershipProviderName]; if (provider == null) { throw new InvalidOperationException("Could not find provider with name " + membershipProviderName); } return(ChangePassword(username, passwordModel, provider)); }
/// <summary> /// Changes the password for a user based on the many different rules and config options /// </summary> /// <param name="currentUser">The user performing the password save action</param> /// <param name="savingUser">The user who's password is being changed</param> /// <param name="passwordModel"></param> /// <param name="userMgr"></param> /// <returns></returns> public async Task <Attempt <PasswordChangedModel> > ChangePasswordWithIdentityAsync( IUser currentUser, IUser savingUser, ChangingPasswordModel passwordModel, BackOfficeUserManager <BackOfficeIdentityUser> 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 <BackOfficeIdentityUser, int>; 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(savingUser.Username, passwordModel, membershipPasswordHasher.MembershipProvider)); } //if we are here, then a IUserAwarePasswordHasher is available, however we cannot proceed in that case if for some odd reason //the user has configured the membership provider to not be hashed. This will actually never occur because the BackOfficeUserManager //will throw if it's not hashed, but we should make sure to check anyways (i.e. in case we want to unit test!) if (membershipPasswordHasher != null && membershipPasswordHasher.MembershipProvider.PasswordFormat != MembershipPasswordFormat.Hashed) { throw new InvalidOperationException("The membership provider cannot have a password format of " + membershipPasswordHasher.MembershipProvider.PasswordFormat + " and be configured with secured hashed passwords"); } //Are we resetting the password?? In ASP.NET Identity APIs, this flag indicates that an admin user is changing another user's password //without knowing the original password. if (passwordModel.Reset.HasValue && passwordModel.Reset.Value) { //if it's the current user, the current user cannot reset their own password if (currentUser.Username == savingUser.Username) { return(Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password reset is not allowed", new[] { "resetPassword" }) })); } //if the current user has access to reset/manually change the password if (currentUser.HasSectionAccess(Umbraco.Core.Constants.Applications.Users) == false) { return(Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("The current user is not authorized", new[] { "resetPassword" }) })); } //ok, we should be able to reset it var resetToken = await userMgr.GeneratePasswordResetTokenAsync(savingUser.Id); var newPass = passwordModel.NewPassword.IsNullOrWhiteSpace() ? userMgr.GeneratePassword() : passwordModel.NewPassword; var resetResult = await userMgr.ChangePasswordWithResetAsync(savingUser.Id, resetToken, newPass); if (resetResult.Succeeded == false) { var errors = string.Join(". ", resetResult.Errors); _logger.Warn <PasswordChanger>(string.Format("Could not reset user password {0}", errors)); return(Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not reset password, errors: " + errors, new[] { "resetPassword" }) })); } return(Attempt.Succeed(new PasswordChangedModel())); } //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(savingUser.Id, passwordModel.OldPassword, passwordModel.NewPassword); if (changeResult.Succeeded == false) { var errors = string.Join(". ", changeResult.Errors); _logger.Warn <PasswordChanger>(string.Format("Could not change user password {0}", errors)); return(Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, errors: " + errors, new[] { "oldPassword" }) })); } 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" }) })); }
/// <summary> /// Changes password for a member/user given the membership provider and the password change model /// </summary> /// <param name="username">The username of the user having their password changed</param> /// <param name="passwordModel"></param> /// <param name="membershipProvider"></param> /// <returns></returns> public Attempt <PasswordChangedModel> 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"); } BackOfficeUserManager <BackOfficeIdentityUser> backofficeUserManager = null; var userId = -1; if (membershipProvider.IsUmbracoUsersProvider()) { backofficeUserManager = _httpContext.GetOwinContext().GetBackOfficeUserManager(); if (backofficeUserManager != null) { var profile = _userService.GetProfileByUserName(username); if (profile != null) { int.TryParse(profile.Id.ToString(), out userId); } } } //Are we resetting the password?? //TODO: I don't think this is required anymore since from 7.7 we no longer display the reset password checkbox since that didn't make sense. 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); if (membershipProvider.IsUmbracoUsersProvider() && backofficeUserManager != null && userId >= 0) { backofficeUserManager.RaisePasswordResetEvent(userId); } //return the generated pword return(Attempt.Succeed(new PasswordChangedModel { ResetPassword = newPass })); } catch (Exception ex) { _logger.WarnWithException <PasswordChanger>("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 <PasswordChanger>("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); if (result && backofficeUserManager != null && userId >= 0) { backofficeUserManager.RaisePasswordChangedEvent(userId); } 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 <PasswordChanger>("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 <PasswordChanger>("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 <PasswordChanger>("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" }) })); } }
/// <summary> /// Changes password for a member/user given the membership provider and the password change model /// </summary> /// <param name="username"></param> /// <param name="passwordModel"></param> /// <param name="membershipProvider"></param> /// <returns></returns> public virtual Attempt <PasswordChangedModel> 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) { if (membershipProvider.EnablePasswordReset == 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 <WebSecurity>("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 <WebSecurity>("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[] { "value" }) })); } 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[] { "value" }) }) : Attempt.Succeed(new PasswordChangedModel())); } catch (Exception ex) { LogHelper.WarnWithException <WebSecurity>("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[] { "value" }) })); } 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 <WebSecurity>("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 <WebSecurity>("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" }) })); } }
/// <summary> /// Changes the password for a user based on the many different rules and config options /// </summary> /// <param name="changingPasswordModel">The changing password model</param> /// <param name="userMgr">The identity manager to use to update the password</param> /// Create an adapter to pass through everything - adapting the member into a user for this functionality /// <returns>The outcome of the password changed model</returns> public async Task <Attempt <PasswordChangedModel> > ChangePasswordWithIdentityAsync( ChangingPasswordModel changingPasswordModel, IUmbracoUserManager <TUser> userMgr) { if (changingPasswordModel == null) { throw new ArgumentNullException(nameof(changingPasswordModel)); } if (userMgr == null) { throw new ArgumentNullException(nameof(userMgr)); } if (changingPasswordModel.NewPassword.IsNullOrWhiteSpace()) { return(Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Cannot set an empty password", new[] { "value" }) })); } var userId = changingPasswordModel.Id.ToString(); TUser identityUser = await userMgr.FindByIdAsync(userId); if (identityUser == null) { // this really shouldn't ever happen... but just in case return(Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Password could not be verified", new[] { "oldPassword" }) })); } // Are we just changing another user/member's password? if (changingPasswordModel.OldPassword.IsNullOrWhiteSpace()) { // ok, we should be able to reset it string resetToken = await userMgr.GeneratePasswordResetTokenAsync(identityUser); IdentityResult resetResult = await userMgr.ChangePasswordWithResetAsync(userId, resetToken, changingPasswordModel.NewPassword); if (resetResult.Succeeded == false) { string errors = resetResult.Errors.ToErrorMessage(); _logger.LogWarning("Could not reset user password {PasswordErrors}", errors); return(Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult(errors, new[] { "value" }) })); } return(Attempt.Succeed(new PasswordChangedModel())); } // is the old password correct? bool validateResult = await userMgr.CheckPasswordAsync(identityUser, changingPasswordModel.OldPassword); if (validateResult == false) { // no, fail with an error message for "oldPassword" return(Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Incorrect password", new[] { "oldPassword" }) })); } // can we change to the new password? IdentityResult changeResult = await userMgr.ChangePasswordAsync(identityUser, changingPasswordModel.OldPassword, changingPasswordModel.NewPassword); if (changeResult.Succeeded == false) { // no, fail with error messages for "password" string errors = changeResult.Errors.ToErrorMessage(); _logger.LogWarning("Could not change user password {PasswordErrors}", errors); return(Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult(errors, new[] { "password" }) })); } return(Attempt.Succeed(new PasswordChangedModel())); }
/// <summary> /// Changes password for a member/user given the membership provider and the password change model /// </summary> /// <param name="username">The username of the user having their password changed</param> /// <param name="passwordModel"></param> /// <param name="membershipProvider"></param> /// <returns></returns> public Attempt <PasswordChangedModel> ChangePasswordWithMembershipProvider(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider) { var umbracoBaseProvider = membershipProvider as MembershipProviderBase; // 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(nameof(passwordModel)); } if (membershipProvider == null) { throw new ArgumentNullException(nameof(membershipProvider)); } BackOfficeUserManager <BackOfficeIdentityUser> backofficeUserManager = null; var userId = -1; if (membershipProvider.IsUmbracoUsersProvider()) { backofficeUserManager = _httpContext.GetOwinContext().GetBackOfficeUserManager(); if (backofficeUserManager != null) { var profile = _userService.GetProfileByUserName(username); if (profile != null) { int.TryParse(profile.Id.ToString(), out userId); } } } //Are we resetting the password? //This flag indicates that either an admin user is changing another user's password without knowing the original password // or that the password needs to be reset to an auto-generated one. if (passwordModel.Reset.HasValue && passwordModel.Reset.Value) { //if a new password is supplied then it's an admin user trying to change another user's password without knowing the original password //this is only possible when using a membership provider if the membership provider supports AllowManuallyChangingPassword if (passwordModel.NewPassword.IsNullOrWhiteSpace() == false) { 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, string.Empty, passwordModel.NewPassword); if (result && backofficeUserManager != null && userId >= 0) { backofficeUserManager.RaisePasswordChangedEvent(userId); } 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.Warn <PasswordChanger>("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" }) })); } } else { return(Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Provider does not support manually changing passwords", new[] { "value" }) })); } } //we've made it here which means we need to generate a new password 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); if (membershipProvider.IsUmbracoUsersProvider() && backofficeUserManager != null && userId >= 0) { backofficeUserManager.RaisePasswordResetEvent(userId); } //return the generated pword return(Attempt.Succeed(new PasswordChangedModel { ResetPassword = newPass })); } catch (Exception ex) { _logger.Warn <PasswordChanger>(ex, "Could not reset member password"); 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" }) })); } //without being able to retrieve the original password, //we cannot arbitrarily change the password without knowing the old one and no old password was 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 supplied try to change it try { var result = membershipProvider.ChangePassword(username, passwordModel.OldPassword, passwordModel.NewPassword); if (result && backofficeUserManager != null && userId >= 0) { backofficeUserManager.RaisePasswordChangedEvent(userId); } 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.Warn <PasswordChanger>(ex, "Could not change member password"); 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.Warn <PasswordChanger>(ex1, "Could not change member password"); 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.Warn <PasswordChanger>(ex2, "Could not retrieve member password"); return(Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex2.Message + " (see log for full details)", new[] { "value" }) })); } }
/// <summary> /// /// </summary> /// <param name="changingPasswordModel"></param> /// <returns></returns> public async Task <ActionResult <ModelWithNotifications <string?> > > PostChangePassword(ChangingPasswordModel changingPasswordModel) { changingPasswordModel = changingPasswordModel ?? throw new ArgumentNullException(nameof(changingPasswordModel)); if (ModelState.IsValid == false) { return(ValidationProblem(ModelState)); } IUser?found = _userService.GetUserById(changingPasswordModel.Id); if (found == null) { return(NotFound()); } IUser?currentUser = _backofficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; // if it's the current user, the current user cannot reset their own password without providing their old password if (currentUser?.Username == found.Username && string.IsNullOrEmpty(changingPasswordModel.OldPassword)) { return(ValidationProblem("Password reset is not allowed without providing old password")); } if ((!currentUser?.IsAdmin() ?? false) && found.IsAdmin()) { return(ValidationProblem("The current user cannot change the password for the specified user")); } Attempt <PasswordChangedModel?> passwordChangeResult = await _passwordChanger.ChangePasswordWithIdentityAsync(changingPasswordModel, _userManager); if (passwordChangeResult.Success) { var result = new ModelWithNotifications <string?>(passwordChangeResult.Result?.ResetPassword); result.AddSuccessNotification(_localizedTextService.Localize("general", "success"), _localizedTextService.Localize("user", "passwordChangedGeneric")); return(result); } if (passwordChangeResult.Result?.ChangeError is not null) { foreach (string memberName in passwordChangeResult.Result.ChangeError.MemberNames) { ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage ?? string.Empty); } } return(ValidationProblem(ModelState)); }
/// <summary> /// Changes password for a member/user given the membership provider and the password change model /// </summary> /// <param name="username"></param> /// <param name="passwordModel"></param> /// <param name="membershipProvider"></param> /// <returns></returns> public virtual Attempt <PasswordChangedModel> ChangePassword(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider) { var passwordChanger = new PasswordChanger(_applicationContext.ProfilingLogger.Logger, _applicationContext.Services.UserService, UmbracoContext.Current.HttpContext); return(passwordChanger.ChangePasswordWithMembershipProvider(username, passwordModel, membershipProvider)); }
/// <summary> /// Update existing member data /// </summary> /// <param name="contentItem">The member to save</param> /// <remarks> /// We need to use both IMemberService and ASP.NET Identity to do our updates because Identity is responsible for passwords/security. /// When this method is called, the IMember will already have updated/mapped values from the http POST. /// So then we do this in order: /// 1. Deal with sensitive property values on IMember /// 2. Use IMemberService to persist all changes /// 3. Use ASP.NET and MemberUserManager to deal with lockouts /// 4. Use ASP.NET, MemberUserManager and password changer to deal with passwords /// 5. Deal with groups/roles /// </remarks> private async Task <ActionResult <bool> > UpdateMemberAsync(MemberSave contentItem) { if (contentItem.PersistedContent is not null) { contentItem.PersistedContent.WriterId = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.Id ?? -1; } // If the user doesn't have access to sensitive values, then we need to check if any of the built in member property types // have been marked as sensitive. If that is the case we cannot change these persisted values no matter what value has been posted. // There's only 3 special ones we need to deal with that are part of the MemberSave instance: Comments, IsApproved, IsLockedOut // but we will take care of this in a generic way below so that it works for all props. if (!_backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser?.HasAccessToSensitiveData() ?? true) { IMemberType?memberType = contentItem.PersistedContent is null ? null : _memberTypeService.Get(contentItem.PersistedContent.ContentTypeId); var sensitiveProperties = memberType? .PropertyTypes.Where(x => memberType.IsSensitiveProperty(x.Alias)) .ToList(); if (sensitiveProperties is not null) { foreach (IPropertyType sensitiveProperty in sensitiveProperties) { // TODO: This logic seems to deviate from the logic that is in v8 where we are explitly checking // against 3 properties: Comments, IsApproved, IsLockedOut, is the v8 version incorrect? ContentPropertyBasic?destProp = contentItem.Properties.FirstOrDefault(x => x.Alias == sensitiveProperty.Alias); if (destProp != null) { // if found, change the value of the contentItem model to the persisted value so it remains unchanged object?origValue = contentItem.PersistedContent?.GetValue(sensitiveProperty.Alias); destProp.Value = origValue; } } } } if (contentItem.PersistedContent is not null) { // First save the IMember with mapped values before we start updating data with aspnet identity _memberService.Save(contentItem.PersistedContent); } bool needsResync = false; MemberIdentityUser identityMember = await _memberManager.FindByIdAsync(contentItem.Id?.ToString()); if (identityMember == null) { return(ValidationProblem("Member was not found")); } // Handle unlocking with the member manager (takes care of other nuances) if (identityMember.IsLockedOut && contentItem.IsLockedOut == false) { IdentityResult unlockResult = await _memberManager.SetLockoutEndDateAsync(identityMember, DateTimeOffset.Now.AddMinutes(-1)); if (unlockResult.Succeeded == false) { return(ValidationProblem( $"Could not unlock for member {contentItem.Id} - error {unlockResult.Errors.ToErrorMessage()}")); } needsResync = true; } else if (identityMember.IsLockedOut == false && contentItem.IsLockedOut) { // NOTE: This should not ever happen unless someone is mucking around with the request data. // An admin cannot simply lock a user, they get locked out by password attempts, but an admin can unlock them return(ValidationProblem("An admin cannot lock a member")); } // If we're changing the password... // Handle changing with the member manager & password changer (takes care of other nuances) if (contentItem.Password != null) { IdentityResult validatePassword = await _memberManager.ValidatePasswordAsync(contentItem.Password.NewPassword); if (validatePassword.Succeeded == false) { return(ValidationProblem(validatePassword.Errors.ToErrorMessage())); } if (!int.TryParse(identityMember.Id, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intId)) { return(ValidationProblem("Member ID was not valid")); } var changingPasswordModel = new ChangingPasswordModel { Id = intId, OldPassword = contentItem.Password.OldPassword, NewPassword = contentItem.Password.NewPassword, }; // Change and persist the password Attempt <PasswordChangedModel?> passwordChangeResult = await _passwordChanger.ChangePasswordWithIdentityAsync(changingPasswordModel, _memberManager); if (!passwordChangeResult.Success) { foreach (string memberName in passwordChangeResult.Result?.ChangeError?.MemberNames ?? Enumerable.Empty <string>()) { ModelState.AddModelError(memberName, passwordChangeResult.Result?.ChangeError?.ErrorMessage ?? string.Empty); } return(ValidationProblem(ModelState)); } needsResync = true; } // Update the roles and check for changes ActionResult <bool> rolesChanged = await AddOrUpdateRoles(contentItem.Groups, identityMember); if (!rolesChanged.Value && rolesChanged.Result != null) { return(rolesChanged.Result); } else { needsResync = true; } // If there have been underlying changes made by ASP.NET Identity, then we need to resync the // IMember on the PersistedContent with what is stored since it will be mapped to display. if (needsResync && contentItem.PersistedContent is not null) { contentItem.PersistedContent = _memberService.GetById(contentItem.PersistedContent.Id) !; } return(true); }
/// <summary> /// Changes the users password /// </summary> /// <param name="changingPasswordModel">The changing password model</param> /// <returns> /// If the password is being reset it will return the newly reset password, otherwise will return an empty value /// </returns> public async Task <ActionResult <ModelWithNotifications <string> > > PostChangePassword(ChangingPasswordModel changingPasswordModel) { IUser currentUser = _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser; changingPasswordModel.Id = currentUser.Id; // all current users have access to reset/manually change their password Attempt <PasswordChangedModel> passwordChangeResult = await _passwordChanger.ChangePasswordWithIdentityAsync(changingPasswordModel, _backOfficeUserManager); if (passwordChangeResult.Success) { // even if we weren't resetting this, it is the correct value (null), otherwise if we were resetting then it will contain the new pword var result = new ModelWithNotifications <string>(passwordChangeResult.Result.ResetPassword); result.AddSuccessNotification(_localizedTextService.Localize("user", "password"), _localizedTextService.Localize("user", "passwordChanged")); return(result); } foreach (string memberName in passwordChangeResult.Result.ChangeError.MemberNames) { ModelState.AddModelError(memberName, passwordChangeResult.Result.ChangeError.ErrorMessage); } return(ValidationProblem(ModelState)); }