diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/user.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/user.resource.js index b0c338fa90..857ef65160 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/user.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/user.resource.js @@ -77,26 +77,17 @@ function userResource($q, $http, umbRequestHelper) { * * @description * Changes the current users password - * - * ##usage - *
-         * contentResource.getAll()
-         *    .then(function(userArray) {
-         *        var myUsers = userArray; 
-         *        alert('they are here!');
-         *    });
-         * 
* * @returns {Promise} resourcePromise object containing the user array. * */ - changePassword: function (oldPassword, newPassword) { + changePassword: function (changePasswordArgs) { return umbRequestHelper.resourcePromise( $http.post( umbRequestHelper.getApiUrl( "userApiBaseUrl", "PostChangePassword"), - { oldPassword: oldPassword, newPassword: newPassword }), + changePasswordArgs), 'Failed to change password'); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index 28f8679d1a..4dec2c17b9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -4,7 +4,7 @@ * @name umbraco.services.contentEditingHelper * @description A helper service for content/media/member controllers when editing/creating/saving content. **/ -function contentEditingHelper($location, $routeParams, notificationsService, serverValidationManager, dialogService) { +function contentEditingHelper($location, $routeParams, notificationsService, serverValidationManager, dialogService, formHelper) { return { @@ -95,60 +95,6 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser return changed; }, - /** - * @ngdoc method - * @name umbraco.services.contentEditingHelper#handleValidationErrors - * @methodOf umbraco.services.contentEditingHelper - * @function - * - * @description - * A function to handle the validation (modelState) errors collection which will happen on a 400 error indicating validation errors - * It's worth noting that when a 400 occurs, the data is still saved just never published, though this depends on if the entity is a new - * entity and whether or not the data fulfils the absolute basic requirements like having a mandatory Name. - */ - handleValidationErrors: function (allProps, modelState) { - - //find the content property for the current error, for use in the loop below - function findContentProp(props, propAlias) { - return _.find(props, function (item) { - return (item.alias === propAlias); - }); - } - - for (var e in modelState) { - //the alias in model state can be in dot notation which indicates - // * the first part is the content property alias - // * the second part is the field to which the valiation msg is associated with - //There will always be at least 2 parts since all model errors for properties are prefixed with "Properties" - var parts = e.split("."); - if (parts.length > 1) { - var propertyAlias = parts[1]; - - //find the content property for the current error - var contentProperty = findContentProp(allProps, propertyAlias); - - if (contentProperty) { - //if it contains 2 '.' then we will wire it up to a property's field - if (parts.length > 2) { - //add an error with a reference to the field for which the validation belongs too - serverValidationManager.addPropertyError(contentProperty.alias, parts[2], modelState[e][0]); - } - else { - //add a generic error for the property, no reference to a specific field - serverValidationManager.addPropertyError(contentProperty.alias, "", modelState[e][0]); - } - } - } - else { - //the parts are only 1, this means its not a property but a native content property - serverValidationManager.addFieldError(parts[0], modelState[e][0]); - } - - //add to notifications - notificationsService.error("Validation", modelState[e][0]); - } - }, - /** * @ngdoc function * @name umbraco.services.contentEditingHelper#handleSaveError @@ -174,7 +120,8 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser //now we need to look through all the validation errors if (args.err.data && (args.err.data.ModelState)) { - this.handleValidationErrors(args.allNewProps, args.err.data.ModelState); + //wire up the server validation errs + formHelper.handleServerValidation(args.err.data.ModelState); if (!args.redirectOnFailure || !this.redirectToCreatedContent(args.err.data.id, args.err.data.ModelState)) { //we are not redirecting because this is not new content, it is existing content. In this case diff --git a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js index 7efc8bebaa..647f124be6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js @@ -7,7 +7,7 @@ * A utility class used to streamline how forms are developed, to ensure that validation is check and displayed consistently and to ensure that the correct events * fire when they need to. */ -function formHelper(angularHelper, serverValidationManager, $timeout, notificationsService) { +function formHelper(angularHelper, serverValidationManager, $timeout, notificationsService, dialogService) { return { /** @@ -102,6 +102,86 @@ function formHelper(angularHelper, serverValidationManager, $timeout, notificati } args.scope.$broadcast("formSubmitted", { scope: args.scope }); + }, + + /** + * @ngdoc function + * @name umbraco.services.formHelper#handleError + * @methodOf umbraco.services.formHelper + * @function + * + * @description + * Needs to be called when a form submission fails, this will wire up all server validation errors in ModelState and + * add the correct messages to the notifications. If a server error has occurred this will show a ysod. + * + * @param {object} err The error object returned from the http promise + */ + handleError: function (err) { + //When the status is a 400 status with a custom header: X-Status-Reason: Validation failed, we have validation errors. + //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something). + //Or, some strange server error + if (err.status === 400) { + //now we need to look through all the validation errors + if (err.data && (err.data.ModelState)) { + + //wire up the server validation errs + this.handleServerValidation(err.data.ModelState); + + //execute all server validation events and subscribers + serverValidationManager.executeAndClearAllSubscriptions(); + } + else { + dialogService.ysodDialog(err); + } + } + else { + dialogService.ysodDialog(err); + } + }, + + /** + * @ngdoc function + * @name umbraco.services.formHelper#handleServerValidation + * @methodOf umbraco.services.formHelper + * @function + * + * @description + * This wires up all of the server validation model state so that valServer and valServerField directives work + * + * @param {object} err The error object returned from the http promise + */ + handleServerValidation: function(modelState) { + for (var e in modelState) { + + //the alias in model state can be in dot notation which indicates + // * the first part is the content property alias + // * the second part is the field to which the valiation msg is associated with + //There will always be at least 2 parts for properties since all model errors for properties are prefixed with "Properties" + //If it is not prefixed with "Properties" that means the error is for a field of the object directly. + + var parts = e.split("."); + if (parts.length > 1) { + var propertyAlias = parts[1]; + + //if it contains 2 '.' then we will wire it up to a property's field + if (parts.length > 2) { + //add an error with a reference to the field for which the validation belongs too + serverValidationManager.addPropertyError(propertyAlias, parts[2], modelState[e][0]); + } + else { + //add a generic error for the property, no reference to a specific field + serverValidationManager.addPropertyError(propertyAlias, "", modelState[e][0]); + } + + } + else { + //the parts are only 1, this means its not a property but a native content property + serverValidationManager.addFieldError(parts[0], modelState[e][0]); + } + + //add to notifications + notificationsService.error("Validation", modelState[e][0]); + } } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js b/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js index b8d97e374c..03f5e96ab0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/servervalidationmgr.service.js @@ -44,8 +44,11 @@ function serverValidationManager($timeout) { * @function * * @description - * This is primarily used for scenarios where the error collection needs to be persisted over a route change. Generally this - * is when a content item (or any item) is created. The controller should call this method once the data is bound to the scope + * This method needs to be called once all field and property errors are wired up. + * + * In some scenarios where the error collection needs to be persisted over a route change + * (i.e. when a content item (or any item) is created and the route redirects to the editor) + * the controller should call this method once the data is bound to the scope * so that any persisted validation errors are re-bound to their controls. Once they are re-binded this then clears the validation * colleciton so that if another route change occurs, the previously persisted validation errors are not re-bound to the new item. */ diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/ChangePassword.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/ChangePassword.html index 54d4827240..9d56075962 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/ChangePassword.html +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/ChangePassword.html @@ -1,6 +1,6 @@

Change password

@@ -9,32 +9,10 @@ - - + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js index 64040362d4..b113fdf93e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js @@ -84,9 +84,10 @@ function ChangePasswordDashboardController($scope, xmlhelper, $log, userResource //create the initial model for change password property editor $scope.changePasswordModel = { - alias: "password", + alias: "_umb_password", view: "changepassword", - config: {} + config: {}, + value: {} }; //go get the config for the membership provider and add it to the model @@ -101,20 +102,18 @@ function ChangePasswordDashboardController($scope, xmlhelper, $log, userResource ////this is the model we will pass to the service //$scope.profile = {}; - $scope.changePassword = function(p) { + $scope.changePassword = function() { if (formHelper.submitForm({ scope: $scope })) { - //userResource.changePassword(p.oldPassword, p.newPassword).then(function() { + userResource.changePassword($scope.changePasswordModel.value).then(function() { - // formHelper.resetForm({ scope: $scope, notifications: data.notifications }); - - // //TODO: This is temporary - server validation will work automatically with the val-server directives. - // $scope.passwordForm.$setValidity(true); - //}, function () { - // //TODO: This is temporary - server validation will work automatically with the val-server directives. - // //this only happens if there is a wrong oldPassword sent along - // $scope.passwordForm.oldpass.$setValidity("oldPassword", false); - //}); + formHelper.resetForm({ scope: $scope, notifications: data.notifications }); + + }, function (err) { + + formHelper.handleError(err); + + }); } }; } diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js index 2c73044d3e..1fdd7aee62 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js @@ -1,5 +1,5 @@ describe('contentEditingHelper tests', function () { - var contentEditingHelper, $routeParams, serverValidationManager, mocksUtils, notificationsService; + var contentEditingHelper, $routeParams, serverValidationManager, mocksUtils, notificationsService, formHelper; beforeEach(module('umbraco.services')); beforeEach(module('umbraco.mocks')); @@ -14,6 +14,7 @@ describe('contentEditingHelper tests', function () { serverValidationManager = $injector.get('serverValidationManager'); mocksUtils = $injector.get('mocksUtils'); notificationsService = $injector.get('notificationsService'); + formHelper = $injector.get('formHelper'); })); describe('handles http validation errors', function () { @@ -88,7 +89,7 @@ describe('contentEditingHelper tests', function () { var allProps = contentEditingHelper.getAllProps(content); //act - contentEditingHelper.handleValidationErrors(allProps, { Name: ["Required"] }); + formHelper.handleServerValidation({ Name: ["Required"] }); //assert expect(serverValidationManager.items.length).toBe(1); @@ -104,7 +105,7 @@ describe('contentEditingHelper tests', function () { var allProps = contentEditingHelper.getAllProps(content); //act - contentEditingHelper.handleValidationErrors(allProps, { "Property.bodyText": ["Required"] }); + formHelper.handleServerValidation({ "Property.bodyText": ["Required"] }); //assert expect(serverValidationManager.items.length).toBe(1); @@ -120,7 +121,7 @@ describe('contentEditingHelper tests', function () { var allProps = contentEditingHelper.getAllProps(content); //act - contentEditingHelper.handleValidationErrors(allProps, { "Property.bodyText.value": ["Required"] }); + formHelper.handleServerValidation({ "Property.bodyText.value": ["Required"] }); //assert expect(serverValidationManager.items.length).toBe(1); @@ -136,8 +137,7 @@ describe('contentEditingHelper tests', function () { var allProps = contentEditingHelper.getAllProps(content); //act - contentEditingHelper.handleValidationErrors( - allProps, + formHelper.handleServerValidation( { "Name": ["Required"], "UpdateDate": ["Invalid date"], diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index fb36cfeca6..bd8895d3a0 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Linq; using System.Net; using System.Net.Http; using System.Text; @@ -250,132 +251,18 @@ namespace Umbraco.Web.Editors //password changes ? if (contentItem.Password == null) return null; - //Are we resetting the password?? - if (contentItem.Password.Reset.HasValue && contentItem.Password.Reset.Value) + var passwordChangeResult = Security.ChangePassword(membershipUser.UserName, contentItem.Password, Membership.Provider); + if (passwordChangeResult.Success) { - if (Membership.Provider.EnablePasswordReset == false) - { - ModelState.AddPropertyError( - new ValidationResult("Password reset is not enabled", new[] { "resetPassword" }), - string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - } - else if (Membership.Provider.RequiresQuestionAndAnswer && contentItem.Password.Answer.IsNullOrWhiteSpace()) - { - ModelState.AddPropertyError( - new ValidationResult("Password reset requires a password answer", new[] {"resetPassword"}), - string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - } - else - { - //ok, we should be able to reset it - try - { - var newPass = Membership.Provider.ResetPassword( - membershipUser.UserName, - Membership.Provider.RequiresQuestionAndAnswer ? contentItem.Password.Answer : null); - - //return the generated pword - return newPass; - } - catch (Exception ex) - { - LogHelper.WarnWithException("Could not reset member password", ex); - ModelState.AddPropertyError( - new ValidationResult("Could not reset password, error: " + ex.Message + " (see log for full details)", new[] {"resetPassword"}), - string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - } - } + //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 + return passwordChangeResult.Result.ResetPassword; } - else if (contentItem.Password.NewPassword.IsNullOrWhiteSpace() == false) - { - //we're not resetting it so we need to try to change it. - if (contentItem.Password.OldPassword.IsNullOrWhiteSpace() && Membership.Provider.EnablePasswordRetrieval == false) - { - //if password retrieval is not enabled but there is no old password we cannot continue + //it wasn't successful, so add the change error to the model state + ModelState.AddPropertyError( + passwordChangeResult.Result.ChangeError, + string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - ModelState.AddPropertyError( - new ValidationResult("Password cannot be changed without the old password", new[] {"value"}), - string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - } - else if (contentItem.Password.OldPassword.IsNullOrWhiteSpace() == false) - { - //if an old password is suplied try to change it - - try - { - var result = Membership.Provider.ChangePassword(membershipUser.UserName, contentItem.Password.OldPassword, contentItem.Password.NewPassword); - if (result == false) - { - ModelState.AddPropertyError( - new ValidationResult("Could not change password", new[] {"value"}), - string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - } - } - catch (Exception ex) - { - LogHelper.WarnWithException("Could not change member password", ex); - ModelState.AddPropertyError( - new ValidationResult("Could not change password, error: " + ex.Message + " (see log for full details)", new[] {"value"}), - string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - } - } - else if (Membership.Provider.EnablePasswordRetrieval == false) - { - //we cannot continue if we cannot get the current password - - ModelState.AddPropertyError( - new ValidationResult("Password cannot be changed without the old password", new[] {"value"}), - string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - } - else if (Membership.Provider.RequiresQuestionAndAnswer && contentItem.Password.Answer.IsNullOrWhiteSpace()) - { - //if the question answer is required but there isn't one, we cannot continue - - ModelState.AddPropertyError( - new ValidationResult("Password cannot be changed without the password answer", new[] {"value"}), - string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - - } - else - { - //lets try to get the old one so we can change it - - try - { - var oldPassword = Membership.Provider.GetPassword( - membershipUser.UserName, - Membership.Provider.RequiresQuestionAndAnswer ? contentItem.Password.Answer : null); - - try - { - var result = Membership.Provider.ChangePassword(membershipUser.UserName, oldPassword, contentItem.Password.NewPassword); - if (result == false) - { - ModelState.AddPropertyError( - new ValidationResult("Could not change password", new[] {"value"}), - string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - } - } - catch (Exception ex1) - { - LogHelper.WarnWithException("Could not change member password", ex1); - ModelState.AddPropertyError( - new ValidationResult("Could not change password, error: " + ex1.Message + " (see log for full details)", new[] { "value" }), - string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - } - - } - catch (Exception ex2) - { - LogHelper.WarnWithException("Could not retrieve member password", ex2); - ModelState.AddPropertyError( - new ValidationResult("Could not change password, error: " + ex2.Message + " (see log for full details)", new[] { "value" }), - string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - } - - } - } return null; } diff --git a/src/Umbraco.Web/Editors/UserController.cs b/src/Umbraco.Web/Editors/UserController.cs index 31944dcc62..5014c53eb3 100644 --- a/src/Umbraco.Web/Editors/UserController.cs +++ b/src/Umbraco.Web/Editors/UserController.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Configuration; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Mapping; using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi; using legacyUser = umbraco.BusinessLogic.User; using System.Net.Http; using System.Collections.Specialized; @@ -60,18 +61,36 @@ namespace Umbraco.Web.Editors /// Changes the users password /// /// - /// - public HttpResponseMessage PostChangePassword(ChangePasswordModel data) - { - - var u = UmbracoContext.Security.CurrentUser; - if (!UmbracoContext.Security.ValidateBackOfficeCredentials(u.Username, data.OldPassword)) - return new HttpResponseMessage(HttpStatusCode.Forbidden); + /// + /// If the password is being reset it will return the newly reset password, otherwise will return null; + /// + public string PostChangePassword(ChangingPasswordModel data) + { + var userProvider = Membership.Providers[UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider]; + if (userProvider == null) + { + throw new InvalidOperationException("No membership provider found with the name " + UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider); + } - if(!UmbracoContext.Security.ChangePassword(data.OldPassword, data.NewPassword)) - return new HttpResponseMessage(HttpStatusCode.InternalServerError); - - return new HttpResponseMessage(HttpStatusCode.OK); + //TODO: WE need to support this! - requires UI updates, etc... + if (userProvider.RequiresQuestionAndAnswer) + { + throw new NotSupportedException("Currently the user editor does not support providers that have RequiresQuestionAndAnswer specified"); + } + + var passwordChangeResult = Security.ChangePassword(Security.CurrentUser.Username, data, userProvider); + 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 + return passwordChangeResult.Result.ResetPassword; + } + + //it wasn't successful, so add the change error to the model state + ModelState.AddPropertyError( + passwordChangeResult.Result.ChangeError, + string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + + throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); } /// diff --git a/src/Umbraco.Web/Models/ContentEditing/ChangePasswordModel.cs b/src/Umbraco.Web/Models/ContentEditing/ChangingPasswordModel.cs similarity index 67% rename from src/Umbraco.Web/Models/ContentEditing/ChangePasswordModel.cs rename to src/Umbraco.Web/Models/ContentEditing/ChangingPasswordModel.cs index e4876b0d00..362894ca42 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ChangePasswordModel.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ChangingPasswordModel.cs @@ -1,11 +1,28 @@ -using System.Runtime.Serialization; +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing { + /// + /// A model representing an attempt at changing a password + /// + public class PasswordChangedModel + { + /// + /// The error affiliated with the failing password changes, null if changing was successful + /// + public ValidationResult ChangeError { get; set; } + + /// + /// If the password was reset, this is the value it has been changed to + /// + public string ResetPassword { get; set; } + } + /// /// A model representing the data required to set a member/user password depending on the provider installed. /// - public class ChangePasswordModel + public class ChangingPasswordModel { /// /// The password value diff --git a/src/Umbraco.Web/Models/ContentEditing/MemberSave.cs b/src/Umbraco.Web/Models/ContentEditing/MemberSave.cs index 2826b53869..ba2d91ab23 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MemberSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MemberSave.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.Models.ContentEditing { public MemberSave() { - Password = new ChangePasswordModel(); + Password = new ChangingPasswordModel(); } [DataMember(Name = "username", IsRequired = true)] @@ -24,6 +24,6 @@ namespace Umbraco.Web.Models.ContentEditing public string Email { get; set; } [DataMember(Name = "password")] - public ChangePasswordModel Password { get; set; } + public ChangingPasswordModel Password { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Security/WebSecurity.cs b/src/Umbraco.Web/Security/WebSecurity.cs index 7cef53ef78..5b236da7f1 100644 --- a/src/Umbraco.Web/Security/WebSecurity.cs +++ b/src/Umbraco.Web/Security/WebSecurity.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Web; using System.Web.Security; @@ -10,6 +11,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models.Membership; using Umbraco.Core.Security; +using Umbraco.Web.Models.ContentEditing; using umbraco; using umbraco.DataLayer; using umbraco.businesslogic.Exceptions; @@ -190,15 +192,119 @@ namespace Umbraco.Web.Security } /// - /// Changes password for a back office user + /// Changes password for a member/user given the membership provider and the password change model /// - /// - /// + /// + /// + /// /// - internal bool ChangePassword(string oldpassword, string newpassword) + /// + /// YES! It is completely insane how many options you have to take into account based on the membership provider. yikes! + /// + internal Attempt ChangePassword(string username, ChangingPasswordModel passwordModel, MembershipProvider membershipProvider) { - var membershipProvider = Membership.Providers[UmbracoConfig.For.UmbracoSettings().Providers.DefaultBackOfficeUserProvider]; - return membershipProvider.GetUser(CurrentUser.Username, true).ChangePassword(oldpassword, newpassword); + 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("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" }) }); + } + } + if (passwordModel.NewPassword.IsNullOrWhiteSpace() == false) + { + //we're not resetting it so we need to try to change it. + + 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); + if (result == false) + { + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, invalid username or password", new[] { "value" }) }); + } + } + catch (Exception ex) + { + LogHelper.WarnWithException("Could not change member password", ex); + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex.Message + " (see log for full details)", new[] { "value" }) }); + } + } + else 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" }) }); + } + else 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" }) }); + } + else + { + //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); + if (result == false) + { + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password", new[] { "value" }) }); + } + } + catch (Exception ex1) + { + LogHelper.WarnWithException("Could not change member password", ex1); + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex1.Message + " (see log for full details)", new[] { "value" }) }); + } + + } + catch (Exception ex2) + { + LogHelper.WarnWithException("Could not retrieve member password", ex2); + return Attempt.Fail(new PasswordChangedModel { ChangeError = new ValidationResult("Could not change password, error: " + ex2.Message + " (see log for full details)", new[] { "value" }) }); + } + } + } + + //woot! + return Attempt.Succeed(new PasswordChangedModel()); } /// diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index d545ee8365..3ca7c79119 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -313,7 +313,7 @@ - +