/// <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" })
                }));
            }
        }
Exemple #7
0
        /// <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" })
                }));
            }
        }
Exemple #9
0
        /// <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));
        }
Exemple #10
0
        /// <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));
        }